diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index 87d0b7677fbec..e45f774797511 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -1568,6 +1568,7 @@ public function testDateTimeWithWidgetSingleText() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', '2011-02-03 04:05:06', array( 'input' => 'string', 'widget' => 'single_text', + 'strict_format' => true, 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', )); @@ -1589,6 +1590,7 @@ public function testDateTimeWithWidgetSingleTextIgnoreDateAndTimeWidgets() 'date_widget' => 'choice', 'time_widget' => 'choice', 'widget' => 'single_text', + 'strict_format' => true, 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', )); diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 99fe30663a70e..e703329df0fa5 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -23,7 +23,7 @@ "symfony/asset": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", - "symfony/form": "^4.1.5", + "symfony/form": "^4.2", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/polyfill-intl-icu": "~1.0", @@ -42,7 +42,7 @@ }, "conflict": { "symfony/console": "<3.4", - "symfony/form": "<4.1.2", + "symfony/form": "<4.2", "symfony/translation": "<4.2" }, "suggest": { diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index a3d2f6ab3656d..40a8967e3bd2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -39,7 +39,7 @@ "symfony/dom-crawler": "~3.4|~4.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/security": "~3.4|~4.0", - "symfony/form": "^4.1", + "symfony/form": "^4.2", "symfony/expression-language": "~3.4|~4.0", "symfony/messenger": "^4.2", "symfony/process": "~3.4|~4.0", @@ -66,7 +66,7 @@ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/asset": "<3.4", "symfony/console": "<3.4", - "symfony/form": "<4.1", + "symfony/form": "<4.2", "symfony/messenger": "<4.2", "symfony/property-info": "<3.4", "symfony/serializer": "<4.1", diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php index ed90331266443..a8f33b93961de 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformer.php @@ -22,6 +22,19 @@ class DateTimeToHtml5LocalDateTimeTransformer extends BaseDateTimeTransformer { const HTML5_FORMAT = 'Y-m-d\\TH:i:s'; + private $strict; + + public function __construct(string $inputTimezone = null, string $outputTimezone = null, bool $strict = false) + { + parent::__construct($inputTimezone, $outputTimezone); + + $this->strict = $strict; + + if (!$this->strict) { + @trigger_error(sprintf('Parsing dates in %s that are not formatted according to the HTML5 specifications is deprecated since Symfony 4.2.', self::class), E_USER_DEPRECATED); + } + } + /** * Transforms a \DateTime into a local date and time string. * @@ -81,7 +94,15 @@ public function reverseTransform($dateTimeLocal) return; } - if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})[T ]\d{2}:\d{2}(?::\d{2})?$/', $dateTimeLocal, $matches)) { + // to maintain backwards compatibility we do not strictly validate the submitted date + // see https://github.com/symfony/symfony/issues/28699 + if (!$this->strict) { + $regex = '/^(\d{4})-(\d{2})-(\d{2})[T ]\d{2}:\d{2}(?::\d{2})?/'; + } else { + $regex = '/^(\d{4})-(\d{2})-(\d{2})[T ]\d{2}:\d{2}(?::\d{2})?$/'; + } + + if (!preg_match($regex, $dateTimeLocal, $matches)) { throw new TransformationFailedException(sprintf('The date "%s" is not a valid date.', $dateTimeLocal)); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index c0a5e6ab1a50c..844ed6f27392e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -78,7 +78,8 @@ public function buildForm(FormBuilderInterface $builder, array $options) if (self::HTML5_FORMAT === $pattern) { $builder->addViewTransformer(new DateTimeToHtml5LocalDateTimeTransformer( $options['model_timezone'], - $options['view_timezone'] + $options['view_timezone'], + $options['strict_format'] )); } else { $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( @@ -218,6 +219,7 @@ public function configureOptions(OptionsResolver $resolver) 'model_timezone' => null, 'view_timezone' => null, 'format' => self::HTML5_FORMAT, + 'strict_format' => false, 'date_format' => null, 'widget' => null, 'date_widget' => $dateWidget, @@ -278,6 +280,14 @@ public function configureOptions(OptionsResolver $resolver) 'text', 'choice', )); + + $resolver->setNormalizer('strict_format', function (Options $options, $strictFormat) { + if (!$strictFormat && 'single_text' === 'widget' && self::HTML5_FORMAT === $options['format']) { + @trigger_error(sprintf('Setting the "strict_format" option of %s to "false" is deprecated since Symfony 4.2.', self::class), E_USER_DEPRECATED); + } + + return $strictFormat; + }); } /** diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 40d4e5688c94a..26637fda5c3d5 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -1493,6 +1493,7 @@ public function testDateTimeWithWidgetSingleText() $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\DateTimeType', '2011-02-03 04:05:06', array( 'input' => 'string', 'widget' => 'single_text', + 'strict_format' => true, 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', )); @@ -1513,6 +1514,7 @@ public function testDateTimeWithWidgetSingleTextIgnoreDateAndTimeWidgets() 'date_widget' => 'choice', 'time_widget' => 'choice', 'widget' => 'single_text', + 'strict_format' => true, 'model_timezone' => 'UTC', 'view_timezone' => 'UTC', )); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php index 8573ddf818d2d..dfa84ff93b790 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToHtml5LocalDateTimeTransformerTest.php @@ -59,7 +59,7 @@ public function reverseTransformProvider() */ public function testTransform($fromTz, $toTz, $from, $to) { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz, true); $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTime($from) : null)); } @@ -69,7 +69,7 @@ public function testTransform($fromTz, $toTz, $from, $to) */ public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to) { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($fromTz, $toTz, true); $this->assertSame($to, $transformer->transform(null !== $from ? new \DateTimeImmutable($from) : null)); } @@ -79,7 +79,7 @@ public function testTransformDateTimeImmutable($fromTz, $toTz, $from, $to) */ public function testTransformRequiresValidDateTime() { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer(); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer(null, null, true); $transformer->transform('2010-01-01'); } @@ -87,6 +87,21 @@ public function testTransformRequiresValidDateTime() * @dataProvider reverseTransformProvider */ public function testReverseTransform($toTz, $fromTz, $to, $from) + { + $transformer = new DateTimeToHtml5LocalDateTimeTransformer($toTz, $fromTz, true); + + if (null !== $to) { + $this->assertEquals(new \DateTime($to), $transformer->reverseTransform($from)); + } else { + $this->assertNull($transformer->reverseTransform($from)); + } + } + + /** + * @group legacy + * @dataProvider nonHtml5DatesProvider + */ + public function testReverseTransformNonHtml5Dates($toTz, $fromTz, $to, $from) { $transformer = new DateTimeToHtml5LocalDateTimeTransformer($toTz, $fromTz); @@ -97,12 +112,22 @@ public function testReverseTransform($toTz, $fromTz, $to, $from) } } + public function nonHtml5DatesProvider() + { + return array( + array('UTC', 'UTC', '2018-09-15T10:00:00Z', '2018-09-15T10:00:00Z'), + array('Europe/Berlin', 'Europe/Berlin', '2018-09-15T10:00:00+02:00', '2018-09-15T10:00:00+02:00'), + array('Europe/Berlin', 'Europe/Berlin', '2018-09-15T10:00:00+0200', '2018-09-15T10:00:00+0200'), + array('UTC', 'UTC', '2018-10-03T10:00:00.000Z', '2018-10-03T10:00:00.000Z'), + ); + } + /** * @expectedException \Symfony\Component\Form\Exception\TransformationFailedException */ public function testReverseTransformRequiresString() { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer(); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer(null, null, true); $transformer->reverseTransform(12345); } @@ -111,7 +136,7 @@ public function testReverseTransformRequiresString() */ public function testReverseTransformWithNonExistingDate() { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer('UTC', 'UTC'); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer('UTC', 'UTC', true); $transformer->reverseTransform('2010-04-31T04:05'); } @@ -121,7 +146,7 @@ public function testReverseTransformWithNonExistingDate() */ public function testReverseTransformExpectsValidDateString() { - $transformer = new DateTimeToHtml5LocalDateTimeTransformer('UTC', 'UTC'); + $transformer = new DateTimeToHtml5LocalDateTimeTransformer('UTC', 'UTC', true); $transformer->reverseTransform('2010-2010-2010'); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php index a45b183142c3d..f9e2073b926fa 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -235,6 +235,7 @@ public function testSubmitDifferentTimezonesDateTime() 'view_timezone' => 'Pacific/Tahiti', 'widget' => 'single_text', 'input' => 'datetime', + 'strict_format' => true, )); $outputTime = new \DateTime('2010-06-02 03:04:00 Pacific/Tahiti'); @@ -254,6 +255,7 @@ public function testSubmitDifferentTimezonesDateTimeImmutable() 'view_timezone' => 'Pacific/Tahiti', 'widget' => 'single_text', 'input' => 'datetime_immutable', + 'strict_format' => true, )); $outputTime = new \DateTimeImmutable('2010-06-02 03:04:00 Pacific/Tahiti'); @@ -274,6 +276,7 @@ public function testSubmitStringSingleText() 'view_timezone' => 'UTC', 'input' => 'string', 'widget' => 'single_text', + 'strict_format' => true, )); $form->submit('2010-06-02T03:04:00'); @@ -290,6 +293,7 @@ public function testSubmitStringSingleTextWithSeconds() 'input' => 'string', 'widget' => 'single_text', 'with_seconds' => true, + 'strict_format' => true, )); $form->submit('2010-06-02T03:04:05'); @@ -328,6 +332,7 @@ public function testSingleTextWidgetShouldUseTheRightInputType() { $view = $this->factory->create(static::TESTED_TYPE, null, array( 'widget' => 'single_text', + 'strict_format' => true, )) ->createView(); @@ -453,6 +458,7 @@ public function testPassHtml5TypeIfSingleTextAndHtml5Format() { $view = $this->factory->create(static::TESTED_TYPE, null, array( 'widget' => 'single_text', + 'strict_format' => true, )) ->createView(); @@ -464,6 +470,7 @@ public function testDontPassHtml5TypeIfHtml5NotAllowed() $view = $this->factory->create(static::TESTED_TYPE, null, array( 'widget' => 'single_text', 'html5' => false, + 'strict_format' => true, )) ->createView(); @@ -621,6 +628,7 @@ public function testSubmitNullWithSingleText() { $form = $this->factory->create(static::TESTED_TYPE, null, array( 'widget' => 'single_text', + 'strict_format' => true, )); $form->submit(null);