florentingarnier / sylius-spam-protection-plugin
Protects the Sylius shop contact, registration and password reset forms with florentingarnier/spam-protection-bundle.
Package info
github.com/FlorentinGarnier/sylius-spam-protection-plugin
Type:sylius-plugin
pkg:composer/florentingarnier/sylius-spam-protection-plugin
Requires
- php: ^8.2
- florentingarnier/spam-protection-bundle: ^0.2.2
- sylius/sylius: ^1.14 || ^2.0
- symfony/config: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/dependency-injection: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/form: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/http-foundation: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/http-kernel: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
Requires (Dev)
- behat/behat: ^3.16
- dmore/behat-chrome-extension: ^1.4
- dmore/chrome-mink-driver: ^2.9
- friends-of-behat/mink: ^1.11
- friends-of-behat/mink-browserkit-driver: ^1.6
- friends-of-behat/mink-debug-extension: ^2.1
- friends-of-behat/mink-extension: ^2.7
- friends-of-behat/page-object-extension: ^0.3
- friends-of-behat/symfony-extension: ^2.6
- friends-of-behat/variadic-extension: ^1.6
- phpstan/phpstan: ^2.1
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^10.5 || ^11.5 || ^12.0
- sylius-labs/coding-standard: ^4.4
- sylius/test-application: ^1.14.0@alpha || ^2.0.0@alpha
- symfony/browser-kit: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/cache: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/dotenv: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/framework-bundle: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/runtime: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
- symfony/twig-bridge: ^5.4.21 || ^6.4 || ^7.4 || ^8.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-07 22:28:41 UTC
README
Protects the public forms of your Sylius shop against spam bots, without reCAPTCHA and without any puzzle for your customers. It relies on florentingarnier/spam-protection-bundle: a honeypot, single-use timed tokens, a proof of work solved by the browser, per-form rate limiting, IP reputation and gibberish detection.
| Sylius form | Scope | Free text checked |
|---|---|---|
Contact (ContactType) |
contact |
message |
Registration (CustomerRegistrationType) |
registration |
— |
Password reset request (UserRequestPasswordResetType) |
password_reset |
— |
Requirements
-
Sylius 1.14 or 2.x. Sylius 1.12 and 1.13 are no longer maintained: their dependencies have security advisories that will never be fixed. Use version 0.1 of the plugin with them.
-
PHP 8.2 or later, and Symfony 5.4, 6.4, 7.4 or 8, within the versions supported by your Sylius version (Symfony 7.0 to 7.3 are not supported by spam-protection-bundle):
Sylius Symfony PHP 1.14 5.4, 6.4 8.2, 8.3 2.0 6.4, 7.4 8.2, 8.3 2.1, 2.2 6.4, 7.4 8.3 to 8.5 2.3 6.4, 7.4, 8 8.3 to 8.5 (8.4 or later with Symfony 8) These are the combinations tested by Sylius itself, and by the CI of the plugin.
-
JavaScript in the customer's browser
Sylius 1.14 requires api-platform/core 2.7, whose releases are all affected by security advisories. Composer 2.9 and later refuses to install them unless your project ignores these advisories, as
this plugin does for its own CI in composer.json (config.policy.advisories.ignore-id). This
concerns Sylius itself, not the plugin, which does not use API Platform.
Sylius 2
The registration form of Sylius 2 is a Live Component, which submits the form every time it re-renders it, to validate the fields. These re-renders save nothing, so the plugin does not verify them: otherwise they would consume the token and count as rejected attempts. Live actions are still verified, as they may save the form.
Installation
-
Require the plugin:
composer require florentingarnier/sylius-spam-protection-plugin
-
If you do not use Symfony Flex, enable the bundle and the plugin:
// config/bundles.php return [ // ... FlorentinGarnier\SpamProtectionBundle\FlorentinGarnierSpamProtectionBundle::class => ['all' => true], FlorentinGarnier\SyliusSpamProtectionPlugin\FlorentinGarnierSyliusSpamProtectionPlugin::class => ['all' => true], ];
-
Load the JavaScript solver in your shop theme, as explained in the bundle documentation. Without it, the protected forms cannot be submitted.
-
Download the IP reputation lists, then schedule the command daily:
bin/console spam-protection:refresh-ip-lists
Configuration is optional: see the bundle documentation.
How the fields are rendered
The Sylius shop templates end their forms with render_rest: false, so a field added to a form is not rendered
automatically. The plugin renders the protection with a priority of -100 in the forms' template events on
Sylius 1:
sylius.shop.contact.request.formsylius.shop.register.formsylius.shop.request_password_reset_token.form
and in the forms' Twig hooks on Sylius 2:
sylius_shop.contact.contact_request.content.formsylius_shop.account.register.content.formsylius_shop.account.forgotten_password.content.form_container.form
The Sylius shop does not render the errors of these forms, so the plugin also renders its rejection message at the
top of each form, with a priority of 1000 (block florentin_garnier_spam_protection_error). If your theme renders
the errors of a form itself ({{ form_errors(form) }}), remove this block from its event or hook, as shown below,
so that the message appears once.
If your theme overrides these templates:
- It still calls the template events or hooks: nothing to do.
- It renders
form.spam_protectionitself: nothing to do either. The plugin skips a field that is already rendered. - It calls neither: render the field in the form with
{{ form_row(form.spam_protection) }}.
To remove a block from an event or a hook, for example when your theme renders the field elsewhere
(florentin_garnier_spam_protection) or the errors of the form (florentin_garnier_spam_protection_error):
# Sylius 1: config/packages/sylius_ui.yaml sylius_ui: events: sylius.shop.contact.request.form: blocks: florentin_garnier_spam_protection: false
# Sylius 2: config/packages/sylius_twig_hooks.yaml sylius_twig_hooks: hooks: sylius_shop.contact.contact_request.content.form: florentin_garnier_spam_protection: enabled: false
Protecting other forms
Any Symfony form can be protected with the bundle's form type, including your own shop forms:
use FlorentinGarnier\SpamProtectionBundle\Form\SpamProtectionType; $builder->add('spam_protection', SpamProtectionType::class, [ 'protection_scope' => 'quotation', 'content_fields' => ['message'], ]);
Testing
The integration tests render the shop forms with the Sylius Test Application. They need no database.
composer install vendor/bin/phpunit # unit and integration tests vendor/bin/ecs check # Sylius coding standard vendor/bin/phpstan analyse # static analysis
Without restriction, Composer may mix Symfony versions (Behat does not support Symfony 8 yet, for instance). Choose
the Sylius and Symfony versions to test, among the combinations listed in the requirements;
Symfony Flex applies SYMFONY_REQUIRE to every Symfony package:
SYMFONY_REQUIRE="~6.4.0" composer update --with "sylius/sylius:~1.14.0" -W rm -rf var/cache
Behat supports neither Symfony 5.4 nor Symfony 8: to test them, remove it first, as the CI does (see ci.yml).
Behat
The Behat scenarios submit the shop forms like a visitor: from Chrome, which solves the proof of work, and without JavaScript, like a bot. They run on Sylius 2 and need MySQL, Chrome and the Symfony CLI.
docker compose up -d # MySQL on port 3306 (MYSQL_PORT=3307 docker compose up -d to change it) (cd vendor/sylius/test-application && yarn install && yarn build) APP_ENV=test vendor/bin/console doctrine:database:create APP_ENV=test vendor/bin/console doctrine:migrations:migrate -n APP_ENV=test symfony server:start --port=8080 --daemon --no-tls google-chrome --headless=new --remote-debugging-port=9222 & vendor/bin/behat
With another database URL, set DATABASE_URL in tests/TestApplication/.env.test.local. Failed steps leave a
screenshot and the page in etc/build/, and the verdicts of the protection are logged to
var/log/spam_protection.log.
Contributing
Contributions are welcome. Please read CONTRIBUTING.md and the Code of Conduct. Report security issues privately, as described in SECURITY.md.
License
Released under the MIT License.