From 4939f0e32304329207e4248de35406bfc0c23c61 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Fri, 25 Oct 2019 19:00:46 -0400 Subject: [PATCH 01/13] Fix handling of empty_data's \Closure value in Date/Time form types --- .../Form/Extension/Core/Type/DateTimeType.php | 16 ++++++++-- .../Form/Extension/Core/Type/DateType.php | 30 ++++++++++++++----- .../Form/Extension/Core/Type/TimeType.php | 20 +++++++++++-- .../Extension/Core/Type/DateTimeTypeTest.php | 10 +++++++ .../Extension/Core/Type/DateTypeTest.php | 10 +++++++ .../Extension/Core/Type/TimeTypeTest.php | 10 +++++++ 6 files changed, 83 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php index 04b0221fdc..6edefd622e 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -107,7 +107,17 @@ class DateTimeType extends AbstractType 'invalid_message_parameters', ])); - if (isset($emptyData['date'])) { + if ($emptyData instanceof \Closure) { + $lazyEmptyData = static function ($option) use ($emptyData) { + return static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); + + return isset($emptyData[$option]) ? $emptyData[$option] : ''; + }; + }; + + $dateOptions['empty_data'] = $lazyEmptyData('date'); + } elseif (isset($emptyData['date'])) { $dateOptions['empty_data'] = $emptyData['date']; } @@ -126,7 +136,9 @@ class DateTimeType extends AbstractType 'invalid_message_parameters', ])); - if (isset($emptyData['time'])) { + if ($emptyData instanceof \Closure) { + $timeOptions['empty_data'] = $lazyEmptyData('time'); + } elseif (isset($emptyData['time'])) { $timeOptions['empty_data'] = $emptyData['time']; } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index 464c262c13..5aea4418b2 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -81,14 +81,28 @@ class DateType extends AbstractType // so we need to handle the cascade setting here $emptyData = $builder->getEmptyData() ?: []; - if (isset($emptyData['year'])) { - $yearOptions['empty_data'] = $emptyData['year']; - } - if (isset($emptyData['month'])) { - $monthOptions['empty_data'] = $emptyData['month']; - } - if (isset($emptyData['day'])) { - $dayOptions['empty_data'] = $emptyData['day']; + if ($emptyData instanceof \Closure) { + $lazyEmptyData = static function ($option) use ($emptyData) { + return static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); + + return isset($emptyData[$option]) ? $emptyData[$option] : ''; + }; + }; + + $yearOptions['empty_data'] = $lazyEmptyData('year'); + $monthOptions['empty_data'] = $lazyEmptyData('month'); + $dayOptions['empty_data'] = $lazyEmptyData('day'); + } else { + if (isset($emptyData['year'])) { + $yearOptions['empty_data'] = $emptyData['year']; + } + if (isset($emptyData['month'])) { + $monthOptions['empty_data'] = $emptyData['month']; + } + if (isset($emptyData['day'])) { + $dayOptions['empty_data'] = $emptyData['day']; + } } if (isset($options['invalid_message'])) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php index c88bea812b..f0b5ac2ac4 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -76,7 +76,17 @@ class TimeType extends AbstractType // so we need to handle the cascade setting here $emptyData = $builder->getEmptyData() ?: []; - if (isset($emptyData['hour'])) { + if ($emptyData instanceof \Closure) { + $lazyEmptyData = static function ($option) use ($emptyData) { + return static function (FormInterface $form) use ($emptyData, $option) { + $emptyData = $emptyData($form->getParent()); + + return isset($emptyData[$option]) ? $emptyData[$option] : ''; + }; + }; + + $hourOptions['empty_data'] = $lazyEmptyData('hour'); + } elseif (isset($emptyData['hour'])) { $hourOptions['empty_data'] = $emptyData['hour']; } @@ -143,14 +153,18 @@ class TimeType extends AbstractType $builder->add('hour', self::$widgets[$options['widget']], $hourOptions); if ($options['with_minutes']) { - if (isset($emptyData['minute'])) { + if ($emptyData instanceof \Closure) { + $minuteOptions['empty_data'] = $lazyEmptyData('minute'); + } elseif (isset($emptyData['minute'])) { $minuteOptions['empty_data'] = $emptyData['minute']; } $builder->add('minute', self::$widgets[$options['widget']], $minuteOptions); } if ($options['with_seconds']) { - if (isset($emptyData['second'])) { + if ($emptyData instanceof \Closure) { + $secondOptions['empty_data'] = $lazyEmptyData('second'); + } elseif (isset($emptyData['second'])) { $secondOptions['empty_data'] = $emptyData['second']; } $builder->add('second', self::$widgets[$options['widget']], $secondOptions); 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 e3f3b729d3..8af524f1fc 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTimeTypeTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormInterface; class DateTimeTypeTest extends BaseTypeTest { @@ -608,6 +609,9 @@ class DateTimeTypeTest extends BaseTypeTest ]); $form->submit(null); + if ($emptyData instanceof \Closure) { + $emptyData = $emptyData($form); + } $this->assertSame($emptyData, $form->getViewData()); $this->assertEquals($expectedData, $form->getNormData()); $this->assertEquals($expectedData, $form->getData()); @@ -616,11 +620,17 @@ class DateTimeTypeTest extends BaseTypeTest public function provideEmptyData() { $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '2018-11-11 21:23'); + $lazyEmptyData = static function (FormInterface $form) { + return $form->getConfig()->getCompound() ? ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']] : '2018-11-11T21:23:00'; + }; return [ 'Simple field' => ['single_text', '2018-11-11T21:23:00', $expectedData], 'Compound text field' => ['text', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData], 'Compound choice field' => ['choice', ['date' => ['year' => '2018', 'month' => '11', 'day' => '11'], 'time' => ['hour' => '21', 'minute' => '23']], $expectedData], + 'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData], + 'Compound text field lazy' => ['text', $lazyEmptyData, $expectedData], + 'Compound choice field lazy' => ['choice', $lazyEmptyData, $expectedData], ]; } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index 96c74fe0e4..cb2c2d0a30 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormInterface; use Symfony\Component\Intl\Util\IntlTestHelper; class DateTypeTest extends BaseTypeTest @@ -985,6 +986,9 @@ class DateTypeTest extends BaseTypeTest ]); $form->submit(null); + if ($emptyData instanceof \Closure) { + $emptyData = $emptyData($form); + } $this->assertSame($emptyData, $form->getViewData()); $this->assertEquals($expectedData, $form->getNormData()); $this->assertEquals($expectedData, $form->getData()); @@ -993,11 +997,17 @@ class DateTypeTest extends BaseTypeTest public function provideEmptyData() { $expectedData = \DateTime::createFromFormat('Y-m-d H:i:s', '2018-11-11 00:00:00'); + $lazyEmptyData = static function (FormInterface $form) { + return $form->getConfig()->getCompound() ? ['year' => '2018', 'month' => '11', 'day' => '11'] : '2018-11-11'; + }; return [ 'Simple field' => ['single_text', '2018-11-11', $expectedData], 'Compound text fields' => ['text', ['year' => '2018', 'month' => '11', 'day' => '11'], $expectedData], 'Compound choice fields' => ['choice', ['year' => '2018', 'month' => '11', 'day' => '11'], $expectedData], + 'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData], + 'Compound text fields lazy' => ['text', $lazyEmptyData, $expectedData], + 'Compound choice fields lazy' => ['choice', $lazyEmptyData, $expectedData], ]; } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php index e89dd8d20c..1f0797f000 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/TimeTypeTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Tests\Extension\Core\Type; use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormInterface; class TimeTypeTest extends BaseTypeTest { @@ -785,6 +786,9 @@ class TimeTypeTest extends BaseTypeTest ]); $form->submit(null); + if ($emptyData instanceof \Closure) { + $emptyData = $emptyData($form); + } $this->assertSame($emptyData, $form->getViewData()); $this->assertEquals($expectedData, $form->getNormData()); $this->assertEquals($expectedData, $form->getData()); @@ -793,11 +797,17 @@ class TimeTypeTest extends BaseTypeTest public function provideEmptyData() { $expectedData = \DateTime::createFromFormat('Y-m-d H:i', '1970-01-01 21:23'); + $lazyEmptyData = static function (FormInterface $form) { + return $form->getConfig()->getCompound() ? ['hour' => '21', 'minute' => '23'] : '21:23'; + }; return [ 'Simple field' => ['single_text', '21:23', $expectedData], 'Compound text field' => ['text', ['hour' => '21', 'minute' => '23'], $expectedData], 'Compound choice field' => ['choice', ['hour' => '21', 'minute' => '23'], $expectedData], + 'Simple field lazy' => ['single_text', $lazyEmptyData, $expectedData], + 'Compound text field lazy' => ['text', $lazyEmptyData, $expectedData], + 'Compound choice field lazy' => ['choice', $lazyEmptyData, $expectedData], ]; } } From f4622853819462281753b7dd88ce51ff94db5b0c Mon Sep 17 00:00:00 2001 From: Wouter J Date: Sat, 1 Feb 2020 19:15:26 +0100 Subject: [PATCH 02/13] Show both missing packages in the same error message --- src/Symfony/Bridge/Twig/Mime/NotificationEmail.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index b5118b7f08..c9d54d72e9 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -41,12 +41,17 @@ class NotificationEmail extends TemplatedEmail public function __construct(Headers $headers = null, AbstractPart $body = null) { + $missingPackages = []; if (!class_exists(CssInlinerExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the CSS Inliner Twig extension is not available; try running "composer require twig/cssinliner-extra".', static::class)); + $missingPackages['twig/cssinliner-extra'] = ' CSS Inliner'; } if (!class_exists(InkyExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the Inky Twig extension is not available; try running "composer require twig/inky-extra".', static::class)); + $missingPackages['twig/inky-extra'] = 'Inky'; + } + + if ([] !== $missingPackages) { + throw new \LogicException(sprintf('You cannot use "%s" if the %s Twig extension%s not available; try running "composer require %s".', static::class, implode(' and ', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', implode(' ', array_keys($missingPackages)))); } parent::__construct($headers, $body); From 427bc3aa18dc67f0f952ee70dcca362c48463b98 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Fri, 31 Jan 2020 18:44:35 +0100 Subject: [PATCH 03/13] [Validator] try to call __get method if property is uninitialized --- .../Validator/Mapping/PropertyMetadata.php | 17 +++++++++++++++-- .../Tests/Fixtures/Entity_74_Proxy.php | 18 ++++++++++++++++++ .../Tests/Mapping/PropertyMetadataTest.php | 15 +++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Validator/Tests/Fixtures/Entity_74_Proxy.php diff --git a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php index 872bd067be..1adad95b80 100644 --- a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php +++ b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php @@ -50,8 +50,21 @@ class PropertyMetadata extends MemberMetadata { $reflProperty = $this->getReflectionMember($object); - if (\PHP_VERSION_ID >= 70400 && !$reflProperty->isInitialized($object)) { - return null; + if (\PHP_VERSION_ID >= 70400 && $reflProperty->hasType() && !$reflProperty->isInitialized($object)) { + // There is no way to check if a property has been unset or if it is uninitialized. + // When trying to access an uninitialized property, __get method is triggered. + + // If __get method is not present, no fallback is possible + // Otherwise we need to catch an Error in case we are trying to access an uninitialized but set property. + if (!method_exists($object, '__get')) { + return null; + } + + try { + return $reflProperty->getValue($object); + } catch (\Error $e) { + return null; + } } return $reflProperty->getValue($object); diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Entity_74_Proxy.php b/src/Symfony/Component/Validator/Tests/Fixtures/Entity_74_Proxy.php new file mode 100644 index 0000000000..d74badc7d7 --- /dev/null +++ b/src/Symfony/Component/Validator/Tests/Fixtures/Entity_74_Proxy.php @@ -0,0 +1,18 @@ +uninitialized); + } + + public function __get($name) + { + return 42; + } +} diff --git a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php index 8d9e67881a..8868ec64aa 100644 --- a/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php +++ b/src/Symfony/Component/Validator/Tests/Mapping/PropertyMetadataTest.php @@ -15,11 +15,13 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Validator\Mapping\PropertyMetadata; use Symfony\Component\Validator\Tests\Fixtures\Entity; use Symfony\Component\Validator\Tests\Fixtures\Entity_74; +use Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy; class PropertyMetadataTest extends TestCase { const CLASSNAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity'; const CLASSNAME_74 = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74'; + const CLASSNAME_74_PROXY = 'Symfony\Component\Validator\Tests\Fixtures\Entity_74_Proxy'; const PARENTCLASS = 'Symfony\Component\Validator\Tests\Fixtures\EntityParent'; public function testInvalidPropertyName() @@ -66,4 +68,17 @@ class PropertyMetadataTest extends TestCase $this->assertNull($metadata->getPropertyValue($entity)); } + + /** + * @requires PHP 7.4 + */ + public function testGetPropertyValueFromUninitializedPropertyShouldNotReturnNullIfMagicGetIsPresent() + { + $entity = new Entity_74_Proxy(); + $metadata = new PropertyMetadata(self::CLASSNAME_74_PROXY, 'uninitialized'); + $notUnsetMetadata = new PropertyMetadata(self::CLASSNAME_74_PROXY, 'notUnset'); + + $this->assertNull($notUnsetMetadata->getPropertyValue($entity)); + $this->assertEquals(42, $metadata->getPropertyValue($entity)); + } } From b52b7b9fd643fff1d17c7204fc3ba55bb5ec8f55 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 1 Feb 2020 14:25:57 +0100 Subject: [PATCH 04/13] [Translation][Debug] Add installation and minimal example to README --- src/Symfony/Component/Debug/README.md | 14 +++++++++++++- src/Symfony/Component/Translation/README.md | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Debug/README.md b/src/Symfony/Component/Debug/README.md index 38bc800c58..f0878df3fa 100644 --- a/src/Symfony/Component/Debug/README.md +++ b/src/Symfony/Component/Debug/README.md @@ -3,10 +3,22 @@ Debug Component The Debug component provides tools to ease debugging PHP code. +Getting Started +--------------- + +``` +$ composer install symfony/debug +``` + +```php +use Symfony\Component\Debug\Debug; + +Debug::enable(); +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/debug.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) diff --git a/src/Symfony/Component/Translation/README.md b/src/Symfony/Component/Translation/README.md index e80a70cad0..f4f1706675 100644 --- a/src/Symfony/Component/Translation/README.md +++ b/src/Symfony/Component/Translation/README.md @@ -3,10 +3,28 @@ Translation Component The Translation component provides tools to internationalize your application. +Getting Started +--------------- + +``` +$ composer require symfony/translation +``` + +```php +use Symfony\Component\Translation\Translator; + +$translator = new Translator('fr_FR'); +$translator->addResource('array', [ + 'Hello World!' => 'Bonjour !', +], 'fr_FR'); + +echo $translator->trans('Hello World!'); // outputs « Bonjour ! » +``` + Resources --------- - * [Documentation](https://symfony.com/doc/current/components/translation.html) + * [Documentation](https://symfony.com/doc/current/translation.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) From 4d920f04d0b2d9cd55fb1ff6201f4c67cc72337f Mon Sep 17 00:00:00 2001 From: Stefan Kruppa Date: Tue, 28 Jan 2020 14:44:57 +0100 Subject: [PATCH 05/13] Fail on empty password verification (without warning on any implementation) --- .../Component/Security/Core/Encoder/NativePasswordEncoder.php | 3 +++ .../Component/Security/Core/Encoder/SodiumPasswordEncoder.php | 3 +++ .../Security/Core/Tests/Encoder/NativePasswordEncoderTest.php | 1 + .../Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php | 1 + 4 files changed, 8 insertions(+) diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php index 3b158a72f4..cbfe4c0a08 100644 --- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php @@ -76,6 +76,9 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti */ public function isPasswordValid($encoded, $raw, $salt): bool { + if ('' === $raw) { + return false; + } if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { return false; } diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index 39f41dd990..5391361af3 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -76,6 +76,9 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti */ public function isPasswordValid($encoded, $raw, $salt): bool { + if ('' === $raw) { + return false; + } if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { return false; } diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php index 965bf3751c..47b8ac09ea 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/NativePasswordEncoderTest.php @@ -53,6 +53,7 @@ class NativePasswordEncoderTest extends TestCase $result = $encoder->encodePassword('password', null); $this->assertTrue($encoder->isPasswordValid($result, 'password', null)); $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); + $this->assertFalse($encoder->isPasswordValid($result, '', null)); } public function testNonArgonValidation() diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php index 8fa0813115..2c4527fef7 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/SodiumPasswordEncoderTest.php @@ -29,6 +29,7 @@ class SodiumPasswordEncoderTest extends TestCase $result = $encoder->encodePassword('password', null); $this->assertTrue($encoder->isPasswordValid($result, 'password', null)); $this->assertFalse($encoder->isPasswordValid($result, 'anotherPassword', null)); + $this->assertFalse($encoder->isPasswordValid($result, '', null)); } public function testBCryptValidation() From 327ee1a956c3a6b4f6c9a054650dc4020c75f029 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 3 Feb 2020 17:31:58 +0100 Subject: [PATCH 06/13] Fix CS --- .../Component/Security/Core/Encoder/NativePasswordEncoder.php | 1 + .../Component/Security/Core/Encoder/SodiumPasswordEncoder.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php index cbfe4c0a08..4ffa38d38e 100644 --- a/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/NativePasswordEncoder.php @@ -79,6 +79,7 @@ final class NativePasswordEncoder implements PasswordEncoderInterface, SelfSalti if ('' === $raw) { return false; } + if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { return false; } diff --git a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php index 5391361af3..35291ed31d 100644 --- a/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php +++ b/src/Symfony/Component/Security/Core/Encoder/SodiumPasswordEncoder.php @@ -79,6 +79,7 @@ final class SodiumPasswordEncoder implements PasswordEncoderInterface, SelfSalti if ('' === $raw) { return false; } + if (\strlen($raw) > self::MAX_PASSWORD_LENGTH) { return false; } From a4544c25716b3b886be80462a5bd8de647afdd3b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 3 Feb 2020 17:40:51 +0100 Subject: [PATCH 07/13] Fix CS --- src/Symfony/Bridge/Twig/Mime/NotificationEmail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index c9d54d72e9..7329163e17 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -50,7 +50,7 @@ class NotificationEmail extends TemplatedEmail $missingPackages['twig/inky-extra'] = 'Inky'; } - if ([] !== $missingPackages) { + if ($missingPackages) { throw new \LogicException(sprintf('You cannot use "%s" if the %s Twig extension%s not available; try running "composer require %s".', static::class, implode(' and ', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', implode(' ', array_keys($missingPackages)))); } From b72b7d34132b8e954c4caec7accbc8706286aaeb Mon Sep 17 00:00:00 2001 From: Guite Date: Wed, 22 Jan 2020 09:28:22 +0100 Subject: [PATCH 08/13] [Translation] prefer intl domain when adding messages to catalogue --- .../Component/Translation/MessageCatalogue.php | 14 +++++++++++--- .../Tests/Catalogue/TargetOperationTest.php | 13 +++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Translation/MessageCatalogue.php b/src/Symfony/Component/Translation/MessageCatalogue.php index 2bad9c2c2f..cde056a736 100644 --- a/src/Symfony/Component/Translation/MessageCatalogue.php +++ b/src/Symfony/Component/Translation/MessageCatalogue.php @@ -158,9 +158,17 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf public function add($messages, $domain = 'messages') { if (!isset($this->messages[$domain])) { - $this->messages[$domain] = $messages; - } else { - foreach ($messages as $id => $message) { + $this->messages[$domain] = []; + } + $intlDomain = $domain; + $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX); + if (\strlen($domain) > $suffixLength && false !== strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) { + $intlDomain .= self::INTL_DOMAIN_SUFFIX; + } + foreach ($messages as $id => $message) { + if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) { + $this->messages[$intlDomain][$id] = $message; + } else { $this->messages[$domain][$id] = $message; } } diff --git a/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php b/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php index 570b503aea..d4608f6ef6 100644 --- a/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php +++ b/src/Symfony/Component/Translation/Tests/Catalogue/TargetOperationTest.php @@ -67,6 +67,19 @@ class TargetOperationTest extends AbstractOperationTest ); } + public function testGetResultWithMixedDomains() + { + $this->assertEquals( + new MessageCatalogue('en', [ + 'messages+intl-icu' => ['a' => 'old_a'], + ]), + $this->createOperation( + new MessageCatalogue('en', ['messages' => ['a' => 'old_a']]), + new MessageCatalogue('en', ['messages+intl-icu' => ['a' => 'new_a']]) + )->getResult() + ); + } + public function testGetResultWithMetadata() { $leftCatalogue = new MessageCatalogue('en', ['messages' => ['a' => 'old_a', 'b' => 'old_b']]); From fbfe1ed42383b46c7c0fc707d01e2f6b7126f8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vilius=20Grigali=C5=ABnas?= Date: Mon, 6 Jan 2020 17:36:06 +0200 Subject: [PATCH 09/13] [Mailer] Fix broken mandrill http send for recipients with names --- .../Mailchimp/Http/MandrillTransport.php | 71 +++++++++++++++++++ .../Transport/MandrillHttpTransport.php | 14 +++- 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php new file mode 100644 index 0000000000..b6b3bef07e --- /dev/null +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Http/MandrillTransport.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Mailer\Bridge\Mailchimp\Http; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Mailer\Exception\TransportException; +use Symfony\Component\Mailer\SentMessage; +use Symfony\Component\Mailer\SmtpEnvelope; +use Symfony\Component\Mailer\Transport\Http\AbstractHttpTransport; +use Symfony\Component\Mime\Address; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Kevin Verschaeve + * + * @experimental in 4.3 + */ +class MandrillTransport extends AbstractHttpTransport +{ + private const ENDPOINT = 'https://mandrillapp.com/api/1.0/messages/send-raw.json'; + private $key; + + public function __construct(string $key, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null) + { + $this->key = $key; + + parent::__construct($client, $dispatcher, $logger); + } + + protected function doSend(SentMessage $message): void + { + $envelope = $message->getEnvelope(); + $response = $this->client->request('POST', self::ENDPOINT, [ + 'json' => [ + 'key' => $this->key, + 'to' => $this->getRecipients($envelope), + 'from_email' => $envelope->getSender()->getAddress(), + 'raw_message' => $message->toString(), + ], + ]); + + if (200 !== $response->getStatusCode()) { + $result = $response->toArray(false); + if ('error' === ($result['status'] ?? false)) { + throw new TransportException(sprintf('Unable to send an email: %s (code %s).', $result['message'], $result['code'])); + } + + throw new TransportException(sprintf('Unable to send an email (code %s).', $result['code'])); + } + } + + /** + * @return string[] + */ + private function getRecipients(SmtpEnvelope $envelope): array + { + return array_map(function (Address $recipient): string { + return $recipient->getAddress(); + }, $envelope->getRecipients()); + } +} diff --git a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php index 17f7a7fcf3..09e728862c 100644 --- a/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php +++ b/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillHttpTransport.php @@ -12,9 +12,11 @@ namespace Symfony\Component\Mailer\Bridge\Mailchimp\Transport; use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\Exception\HttpTransportException; use Symfony\Component\Mailer\SentMessage; use Symfony\Component\Mailer\Transport\AbstractHttpTransport; +use Symfony\Component\Mime\Address; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\ResponseInterface; @@ -45,7 +47,7 @@ class MandrillHttpTransport extends AbstractHttpTransport $response = $this->client->request('POST', 'https://'.$this->getEndpoint().'/api/1.0/messages/send-raw.json', [ 'json' => [ 'key' => $this->key, - 'to' => $this->stringifyAddresses($envelope->getRecipients()), + 'to' => $this->getRecipients($envelope->getRecipients()), 'from_email' => $envelope->getSender()->toString(), 'raw_message' => $message->toString(), ], @@ -69,4 +71,14 @@ class MandrillHttpTransport extends AbstractHttpTransport { return ($this->host ?: self::HOST).($this->port ? ':'.$this->port : ''); } + + /** + * @return string[] + */ + private function getRecipients(Envelope $envelope): array + { + return array_map(function (Address $recipient): string { + return $recipient->getAddress(); + }, $envelope->getRecipients()); + } } From e8ba15ed27d080ac3385292d3b57ce3621e987bf Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 31 Jan 2020 12:46:11 +0100 Subject: [PATCH 10/13] [Config][XmlReferenceDumper] Prevent potential \TypeError --- .../Component/Config/Definition/Dumper/XmlReferenceDumper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php index ff8c353888..da0a971213 100644 --- a/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php +++ b/src/Symfony/Component/Config/Definition/Dumper/XmlReferenceDumper.php @@ -91,7 +91,7 @@ class XmlReferenceDumper } if ($prototype instanceof PrototypedArrayNode) { - $prototype->setName($key); + $prototype->setName($key ?? ''); $children = [$key => $prototype]; } elseif ($prototype instanceof ArrayNode) { $children = $prototype->getChildren(); From 00baa290e880a44d1c97e527024e175bc64dbe80 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 4 Feb 2020 08:15:38 +0100 Subject: [PATCH 11/13] [Translation] Add missing use statement --- src/Symfony/Component/Translation/Command/XliffLintCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Translation/Command/XliffLintCommand.php b/src/Symfony/Component/Translation/Command/XliffLintCommand.php index 770a15e209..3f8fe73218 100644 --- a/src/Symfony/Component/Translation/Command/XliffLintCommand.php +++ b/src/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -18,6 +18,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Exception\InvalidArgumentException; use Symfony\Component\Translation\Util\XliffUtils; /** From ce29631cd827634342ac5bd806c6809e6aed8d7e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 4 Feb 2020 08:15:38 +0100 Subject: [PATCH 12/13] [Translation] Add missing use statement --- src/Symfony/Component/Translation/Command/XliffLintCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Translation/Command/XliffLintCommand.php b/src/Symfony/Component/Translation/Command/XliffLintCommand.php index 922e026c48..6b1976dc6b 100644 --- a/src/Symfony/Component/Translation/Command/XliffLintCommand.php +++ b/src/Symfony/Component/Translation/Command/XliffLintCommand.php @@ -17,6 +17,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Translation\Exception\InvalidArgumentException; /** * Validates XLIFF files syntax and outputs encountered errors. From cd27b9d06f7e08716c5598cfddc2d50f5367fd4f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 4 Feb 2020 08:35:15 +0100 Subject: [PATCH 13/13] Add missing use statements --- src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php | 1 + src/Symfony/Component/Cache/Simple/TraceableCache.php | 1 + .../Session/Storage/Handler/SessionHandlerFactory.php | 2 +- .../Component/Mime/Test/Constraint/EmailAttachmentCount.php | 1 + .../Component/Mime/Test/Constraint/EmailHtmlBodyContains.php | 2 ++ .../Component/Mime/Test/Constraint/EmailTextBodyContains.php | 2 ++ src/Symfony/Component/Security/Core/User/LdapUserProvider.php | 4 ++-- src/Symfony/Component/Security/Http/Firewall.php | 1 + 8 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php index 7f92164976..ba00249902 100644 --- a/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php +++ b/src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\SecurityBundle\Debug; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\Firewall\ListenerInterface; /** diff --git a/src/Symfony/Component/Cache/Simple/TraceableCache.php b/src/Symfony/Component/Cache/Simple/TraceableCache.php index eac77badd4..2214f1c818 100644 --- a/src/Symfony/Component/Cache/Simple/TraceableCache.php +++ b/src/Symfony/Component/Cache/Simple/TraceableCache.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Cache\Simple; use Psr\SimpleCache\CacheInterface as Psr16CacheInterface; +use Symfony\Component\Cache\Adapter\TraceableAdapter; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Contracts\Cache\CacheInterface; diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php index 1f017c8afd..b92bd0ffde 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/SessionHandlerFactory.php @@ -54,7 +54,7 @@ class SessionHandlerFactory case 0 === strpos($connection, 'rediss:'): case 0 === strpos($connection, 'memcached:'): if (!class_exists(AbstractAdapter::class)) { - throw new InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); + throw new \InvalidArgumentException(sprintf('Unsupported DSN "%s". Try running "composer require symfony/cache".', $connection)); } $handlerClass = 0 === strpos($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class; $connection = AbstractAdapter::createConnection($connection, ['lazy' => true]); diff --git a/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php b/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php index b219f28b9d..c0adbe3a0c 100644 --- a/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php +++ b/src/Symfony/Component/Mime/Test/Constraint/EmailAttachmentCount.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Mime\Test\Constraint; use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Message; use Symfony\Component\Mime\RawMessage; final class EmailAttachmentCount extends Constraint diff --git a/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php b/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php index 8965195144..3c61376e1a 100644 --- a/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php +++ b/src/Symfony/Component/Mime/Test/Constraint/EmailHtmlBodyContains.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Mime\Test\Constraint; use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; final class EmailHtmlBodyContains extends Constraint { diff --git a/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php b/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php index b5e87f96f5..063d96306b 100644 --- a/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php +++ b/src/Symfony/Component/Mime/Test/Constraint/EmailTextBodyContains.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Mime\Test\Constraint; use PHPUnit\Framework\Constraint\Constraint; +use Symfony\Component\Mime\Message; +use Symfony\Component\Mime\RawMessage; final class EmailTextBodyContains extends Constraint { diff --git a/src/Symfony/Component/Security/Core/User/LdapUserProvider.php b/src/Symfony/Component/Security/Core/User/LdapUserProvider.php index 406d141c47..1770551fc9 100644 --- a/src/Symfony/Component/Security/Core/User/LdapUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/LdapUserProvider.php @@ -11,12 +11,12 @@ namespace Symfony\Component\Security\Core\User; -@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', LdapUserProvider::class, BaseLdapUserProvider::class), E_USER_DEPRECATED); - use Symfony\Component\Ldap\Entry; use Symfony\Component\Ldap\Security\LdapUserProvider as BaseLdapUserProvider; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +@trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.4, use "%s" instead.', LdapUserProvider::class, BaseLdapUserProvider::class), E_USER_DEPRECATED); + /** * LdapUserProvider is a simple user provider on top of ldap. * diff --git a/src/Symfony/Component/Security/Http/Firewall.php b/src/Symfony/Component/Security/Http/Firewall.php index ee769496a6..133c4486b9 100644 --- a/src/Symfony/Component/Security/Http/Firewall.php +++ b/src/Symfony/Component/Security/Http/Firewall.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Security\Http\Firewall\AbstractListener; use Symfony\Component\Security\Http\Firewall\AccessListener; use Symfony\Component\Security\Http\Firewall\LogoutListener;