eekes / sulu-spam-protection-bundle
Invisible reCAPTCHA v3 spam protection for Sulu and Symfony website forms, with a log of what it blocked
Package info
github.com/eekes/sulu-spam-protection-bundle
Type:symfony-bundle
pkg:composer/eekes/sulu-spam-protection-bundle
Requires
- php: ^8.3
- doctrine/dbal: ^3.8 || ^4.0
- doctrine/doctrine-bundle: ^2.13
- doctrine/orm: ^3.3
- karser/karser-recaptcha3-bundle: ^0.3.0
- psr/log: ^3.0
- sulu/sulu: ^3.0
- symfony/config: ^7.2
- symfony/console: ^7.2
- symfony/dependency-injection: ^7.2
- symfony/event-dispatcher: ^7.2
- symfony/form: ^7.2
- symfony/framework-bundle: ^7.2
- symfony/http-foundation: ^7.2
- symfony/http-kernel: ^7.2
- symfony/translation-contracts: ^3.5
Requires (Dev)
- dama/doctrine-test-bundle: ^8.2
- doctrine/doctrine-migrations-bundle: ^3.7
- php-cs-fixer/shim: ^3.58
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-doctrine: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.5 || ^12.1
- sulu/form-bundle: ^3.0
- symfony/browser-kit: ^7.2
- symfony/monolog-bundle: ^3.10 || ^4.0
- symfony/twig-bundle: ^7.2
- symfony/yaml: ^7.2
Suggests
- sulu/form-bundle: To offer spam protection as a field in the forms editors build in the Sulu admin
Provides
None
Conflicts
None
Replaces
None
README
Invisible reCAPTCHA v3 in front of website form submissions — the dynamic forms editors build in the Sulu admin, a Live Component form, or any Symfony form.
The visitor never sees a challenge. A submission that fails the check is answered with the ordinary success page, so a bot never learns it was caught, and everything the sender typed is logged instead — so a genuine visitor who was flagged by mistake can still be helped.
Installation
composer require eekes/sulu-spam-protection-bundle
Register the bundle in config/bundles.php:
Eekes\Sulu\SpamProtectionBundle\EekesSuluSpamProtectionBundle::class => ['all' => true],
1. reCAPTCHA v3 keys
Create a v3 site at https://www.google.com/recaptcha/admin. A v2 key does not work on v3.
# .env RECAPTCHA3_KEY= RECAPTCHA3_SECRET= RECAPTCHA3_SCORE_THRESHOLD=0.5 RECAPTCHA3_ENABLED=true
karser/karser-recaptcha3-bundle comes with this bundle; configure it once:
# config/packages/karser_recaptcha3.yaml karser_recaptcha3: site_key: '%env(RECAPTCHA3_KEY)%' secret_key: '%env(RECAPTCHA3_SECRET)%' score_threshold: '%env(float:RECAPTCHA3_SCORE_THRESHOLD)%' enabled: '%env(bool:RECAPTCHA3_ENABLED)%'
enabled: false turns the whole check off — handy on a local machine. The score threshold decides
how strict it is; 0.5 is Google's own advice. Start there, then look at the blocked submissions
before tightening it.
2. Database
The entity mapping is registered by the bundle, so only a migration is left:
bin/console doctrine:migrations:diff bin/console doctrine:migrations:migrate
3. Admin routes
Add one import so the administration interface can reach the list:
# config/routes/sulu_admin.yaml eekes_sulu_spam_protection_api: resource: "@EekesSuluSpamProtectionBundle/config/routing_admin_api.php" prefix: /admin/api
Then give the role permission on Settings → Blocked submissions in the Sulu user management.
Using it
In a Sulu form
Open the form in the admin and add the Spam protection field. From then on every submission of that form is checked. The field is invisible to visitors and does not affect the layout, so it can sit anywhere in the form.
Adding the field also switches Sulu's CSRF protection off for that form — Sulu does that itself, because a cached page cannot carry a valid CSRF token. The reCAPTCHA check takes its place.
Anywhere else
Inject SpamCheckerInterface, check the token, and decide what to answer. See
docs/custom-forms.md for the full pattern.
Documentation
Compatibility
PHP 8.3+, Symfony 7.2+, sulu/sulu 3.0. sulu/form-bundle is optional: its integration is only
loaded when the bundle is registered, so the checker and the log work in a plain Symfony
application too.
Contributing
See CONTRIBUTING.md for the layout of the bundle, the conventions it follows, and how to run the tests.
License
MIT. See LICENSE.