From 549a308a37c0b2913afab3ed2791bc123c087914 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 2 May 2013 11:05:04 +0200 Subject: [PATCH] [Form] Fixed CSRF error messages to be translated and added "csrf_message" option --- .../Resources/config/form_csrf.xml | 2 + src/Symfony/Component/Form/CHANGELOG.md | 2 + .../Form/Extension/Csrf/CsrfExtension.php | 26 +++++++++-- .../EventListener/CsrfValidationListener.php | 30 ++++++++++++- .../Csrf/Type/FormTypeCsrfExtension.php | 36 ++++++++++++++- .../Csrf/Type/FormTypeCsrfExtensionTest.php | 45 ++++++++++++++++++- 6 files changed, 133 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 72442bcbba68..57cad204aa38 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -19,6 +19,8 @@ %form.type_extension.csrf.enabled% %form.type_extension.csrf.field_name% + + %validator.translation_domain% diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index 88a5714053d6..6cc800d0af35 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -31,6 +31,8 @@ CHANGELOG * [BC BREAK] initialization for Form instances added to a form tree must be manually disabled * PRE_SET_DATA is now guaranteed to be called after children were added by the form builder, unless FormInterface::setData() is called manually + * fixed CSRF error message to be translated + * custom CSRF error messages can now be set through the "csrf_message" option 2.2.0 ----- diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index 71d06381ad76..f9d9e40aa8f0 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -14,22 +14,42 @@ use Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Translation\TranslatorInterface; /** * This extension protects forms by using a CSRF token. + * + * @author Bernhard Schussek */ class CsrfExtension extends AbstractExtension { + /** + * @var CsrfProviderInterface + */ private $csrfProvider; + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var null|string + */ + private $translationDomain; + /** * Constructor. * - * @param CsrfProviderInterface $csrfProvider The CSRF provider + * @param CsrfProviderInterface $csrfProvider The CSRF provider + * @param TranslatorInterface $translator The translator for translating error messages. + * @param null|string $translationDomain The translation domain for translating. */ - public function __construct(CsrfProviderInterface $csrfProvider) + public function __construct(CsrfProviderInterface $csrfProvider, TranslatorInterface $translator = null, $translationDomain = null) { $this->csrfProvider = $csrfProvider; + $this->translator = $translator; + $this->translationDomain = $translationDomain; } /** @@ -38,7 +58,7 @@ public function __construct(CsrfProviderInterface $csrfProvider) protected function loadTypeExtensions() { return array( - new Type\FormTypeCsrfExtension($this->csrfProvider), + new Type\FormTypeCsrfExtension($this->csrfProvider, true, '_token', $this->translator, $this->translationDomain), ); } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index 9d062f95466e..547e9d756c38 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Translation\TranslatorInterface; /** * @author Bernhard Schussek @@ -44,6 +45,22 @@ class CsrfValidationListener implements EventSubscriberInterface */ private $intention; + /** + * The message displayed in case of an error. + * @var string + */ + private $errorMessage; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var null|string + */ + private $translationDomain; + public static function getSubscribedEvents() { return array( @@ -51,11 +68,14 @@ public static function getSubscribedEvents() ); } - public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention) + public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) { $this->fieldName = $fieldName; $this->csrfProvider = $csrfProvider; $this->intention = $intention; + $this->errorMessage = $errorMessage; + $this->translator = $translator; + $this->translationDomain = $translationDomain; } public function preSubmit(FormEvent $event) @@ -65,7 +85,13 @@ public function preSubmit(FormEvent $event) if ($form->isRoot() && $form->getConfig()->getOption('compound')) { if (!isset($data[$this->fieldName]) || !$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) { - $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form.')); + $errorMessage = $this->errorMessage; + + if (null !== $this->translator) { + $errorMessage = $this->translator->trans($errorMessage, array(), $this->translationDomain); + } + + $form->addError(new FormError($errorMessage)); } if (is_array($data)) { diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index ea13056c86e5..336cf04756e0 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -18,21 +18,45 @@ use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; +use Symfony\Component\Translation\TranslatorInterface; /** * @author Bernhard Schussek */ class FormTypeCsrfExtension extends AbstractTypeExtension { + /** + * @var CsrfProviderInterface + */ private $defaultCsrfProvider; + + /** + * @var Boolean + */ private $defaultEnabled; + + /** + * @var string + */ private $defaultFieldName; - public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token') + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var null|string + */ + private $translationDomain; + + public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) { $this->defaultCsrfProvider = $defaultCsrfProvider; $this->defaultEnabled = $defaultEnabled; $this->defaultFieldName = $defaultFieldName; + $this->translator = $translator; + $this->translationDomain = $translationDomain; } /** @@ -49,7 +73,14 @@ public function buildForm(FormBuilderInterface $builder, array $options) $builder ->setAttribute('csrf_factory', $builder->getFormFactory()) - ->addEventSubscriber(new CsrfValidationListener($options['csrf_field_name'], $options['csrf_provider'], $options['intention'])) + ->addEventSubscriber(new CsrfValidationListener( + $options['csrf_field_name'], + $options['csrf_provider'], + $options['intention'], + $options['csrf_message'], + $this->translator, + $this->translationDomain + )) ; } @@ -83,6 +114,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver) 'csrf_protection' => $this->defaultEnabled, 'csrf_field_name' => $this->defaultFieldName, 'csrf_provider' => $this->defaultCsrfProvider, + 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', 'intention' => 'unknown', )); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php index 13b305fe4bba..0a1f0dc4817b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -13,6 +13,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormError; use Symfony\Component\Form\Test\TypeTestCase; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; @@ -33,11 +34,20 @@ public function getName() class FormTypeCsrfExtensionTest extends TypeTestCase { + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ protected $csrfProvider; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $translator; + protected function setUp() { $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); + $this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); parent::setUp(); } @@ -45,6 +55,7 @@ protected function setUp() protected function tearDown() { $this->csrfProvider = null; + $this->translator = null; parent::tearDown(); } @@ -52,7 +63,7 @@ protected function tearDown() protected function getExtensions() { return array_merge(parent::getExtensions(), array( - new CsrfExtension($this->csrfProvider), + new CsrfExtension($this->csrfProvider, $this->translator), )); } @@ -255,4 +266,36 @@ public function testNoCsrfProtectionOnPrototype() $this->assertFalse(isset($prototypeView['csrf'])); $this->assertCount(1, $prototypeView); } + + public function testsTranslateCustomErrorMessage() + { + $this->csrfProvider->expects($this->once()) + ->method('isCsrfTokenValid') + ->with('%INTENTION%', 'token') + ->will($this->returnValue(false)); + + $this->translator->expects($this->once()) + ->method('trans') + ->with('Foobar') + ->will($this->returnValue('[trans]Foobar[/trans]')); + + $form = $this->factory + ->createBuilder('form', null, array( + 'csrf_field_name' => 'csrf', + 'csrf_provider' => $this->csrfProvider, + 'csrf_message' => 'Foobar', + 'intention' => '%INTENTION%', + 'compound' => true, + )) + ->getForm(); + + $form->submit(array( + 'csrf' => 'token', + )); + + $errors = $form->getErrors(); + + $this->assertGreaterThan(0, count($errors)); + $this->assertEquals(new FormError('[trans]Foobar[/trans]'), $errors[0]); + } }