Live Demo: Try this and other plugins live β no installation, no risk: demo.markus-michalski.net
The class SpamProtectionFacade runs the checks in this order. The first failed check ends the chain:
A submission has to pass all enabled checks. Content validation only runs when the form supplies evaluable fields. The ALTCHA solution is verified before the rate limit and marked as used in the process. The rate limit only counts submissions that passed the earlier checks.
The extensions are registered in metadata.php under extend:
| OXID class | Module class | Purpose |
|---|---|---|
OxidEsales\Eshop\Core\ViewConfig |
Mmd\AltchaIntegration\Extension\Core\ViewConfig |
Helper methods for templates |
OxidEsales\Eshop\Application\Controller\ContactController |
Mmd\AltchaIntegration\Extension\Controller\ContactController |
Check before send() |
OxidEsales\Eshop\Application\Controller\NewsletterController |
Mmd\AltchaIntegration\Extension\Controller\NewsletterController |
Check before send() |
OxidEsales\Eshop\Application\Component\UserComponent |
Mmd\AltchaIntegration\Extension\Component\UserComponent |
Check before createUser(), only for cl=register |
In addition the module registers the controller mmd_altcha_challenge (Mmd\AltchaIntegration\Controller\AltchaChallengeController) and the template @mmd_altcha/widget/altcha_widget.
oxid7-altcha-integration/
βββ metadata.php # module ID mmd_altcha, settings, events
βββ services.yaml # services (autowire, public)
βββ src/
β βββ Controller/ # AltchaChallengeController
β βββ Core/ # ModuleEvents (onActivate, onDeactivate)
β βββ Exception/ # three exception classes, not used anywhere in the module
β βββ Extension/ # extensions of the OXID classes
β βββ Repository/ # RateLimitRepository, UsedChallengeRepository
β βββ Service/ # check services and settings
β βββ Struct/ # SpamCheckRequest, SpamCheckResult, AltchaChallenge, AltchaSolution
βββ translations/ # frontend translations (de, en)
βββ views/
βββ admin_twig/ # admin translations (de, en)
βββ twig/ # widget and Apex overrides
| Service | Purpose |
|---|---|
SpamProtectionFacade |
Runs the check chain |
HoneypotService |
Checks that the honeypot field is not empty |
ContentValidationService |
Gibberish and disposable domain checks |
AltchaChallengeService |
Creates challenges and verifies solutions |
RateLimitService |
Rate limit for forms |
ChallengeRateLimitService |
Rate limit for the challenge endpoint |
ModuleSettingsService |
Reads the settings, falling back to defaults |
RateLimitRepository, UsedChallengeRepository |
Database access |
If a setting cannot be read, ModuleSettingsService uses the default value.
Activation creates both tables with CREATE TABLE IF NOT EXISTS (InnoDB, utf8mb4).
One row per request:
| Column | Type | Content |
|---|---|---|
id |
BIGINT UNSIGNED | Primary key, built from a microsecond timestamp and a random number |
ip_hash |
CHAR(64) | HMAC-SHA256 of ip: plus the client address, keyed with the HMAC secret |
action |
VARCHAR(50) | contact_form, newsletter_subscribe, registration or challenge |
requested_at |
DATETIME | Time of the request |
created_at |
DATETIME | Time of the entry |
Indexes: idx_rate_check (ip_hash, action, requested_at) and idx_cleanup (requested_at). The limit counts the rows with matching hash and action whose requested_at lies within the time window. Only when the count is below the maximum is a new row written.
| Column | Type | Content |
|---|---|---|
id |
BIGINT UNSIGNED | Primary key |
challenge_hash |
CHAR(64) | SHA-256 of the used challenge, unique |
expires_at |
DATETIME | Time of use plus "Challenge validity (seconds)" |
created_at |
DATETIME | Time of the entry |
The unique index on challenge_hash makes recording atomic: submitting the same challenge a second time adds no row and is rejected as a replay. The index idx_cleanup is on expires_at.
The module deletes no rows. The methods RateLimitService::cleanup(), RateLimitRepository::deleteOlderThan() and UsedChallengeRepository::deleteExpired() exist but are never called, there is neither a cron job nor a hook. Both tables grow without limit unless you clean them up yourself (instructions on the Privacy page).
| Event | Behaviour |
|---|---|
onActivate |
Creates the two tables |
onDeactivate |
Does nothing |
The frontend texts (widget labels and error messages, keys with the prefix MMD_ALTCHA_) are in translations/de/mmd_altcha_lang.php and translations/en/mmd_altcha_lang.php. The admin labels of the settings are in views/admin_twig/de/mmd_altcha_lang.php and views/admin_twig/en/mmd_altcha_lang.php.