From 935a3b23e2bb112e77dd9d8c78f6c5353d9e2e99 Mon Sep 17 00:00:00 2001 From: hugovms <38090843+hugovms@users.noreply.github.com> Date: Sat, 31 Oct 2020 14:29:46 -0300 Subject: [PATCH 01/46] fix: solving pt-br translation issues --- .../Resources/translations/validators.pt.xlf | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf index e5cf660686358..f140e1a45c00e 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf @@ -176,27 +176,27 @@ This value should be the user's current password. - Este valor deveria de ser a password atual do utilizador. + Este valor deveria de ser a senha atual do usuário. This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. - Este valor tem de ter exatamente {{ limit }} carateres. + Este valor deve possuir exatamente {{ limit }} carateres. The file was only partially uploaded. - Só foi enviado parte do ficheiro. + Só foi enviado uma parte do arquivo. No file was uploaded. - Nenhum ficheiro foi enviado. + Nenhum arquivo foi enviado. No temporary folder was configured in php.ini. - Não existe nenhum directório temporária configurado no ficheiro php.ini. + Não existe nenhuma pasta temporária configurada no arquivo do php.ini. Cannot write temporary file to disk. - Não foi possível escrever ficheiros temporários no disco. + Não foi possível escrever os arquivos temporários no disco. A PHP extension caused the upload to fail. @@ -292,15 +292,15 @@ The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. - A imagem está orientada à paisagem ({{ width }}x{{ height }}px). Imagens orientadas à paisagem não são permitidas. + A imagem está em orientação de paisagem com ({{ width }}x{{ height }}px). Imagens orientadas em paisagem não são permitidas. The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. - A imagem está orientada ao retrato ({{ width }}x{{ height }}px). Imagens orientadas ao retrato não são permitidas. + A imagem está em orientação de retrato com ({{ width }}x{{ height }}px). Imagens orientadas em retrato não são permitidas. An empty file is not allowed. - Ficheiro vazio não é permitido. + Um arquivo vazio não é permitido. The host could not be resolved. From e7698e74349764615ad3799737dcfb13f40c6fea Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 17 Nov 2020 17:13:30 +0100 Subject: [PATCH 02/46] ignore the pattern attribute for textareas --- .../Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php | 2 +- src/Symfony/Bridge/Twig/composer.json | 2 +- src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php | 1 + src/Symfony/Component/Form/Tests/AbstractLayoutTest.php | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php index 765e1592d1628..44cf60db3ac5c 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/AbstractBootstrap3LayoutTest.php @@ -2379,7 +2379,7 @@ public function testTextarea() $this->assertWidgetMatchesXpath($form->createView(), ['attr' => ['class' => 'my&class']], '/textarea [@name="name"] - [@pattern="foo"] + [not(@pattern)] [@class="my&class form-control"] [.="foo&bar"] ' diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 75860d1ed465b..84045151c8ab7 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -26,7 +26,7 @@ "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/error-handler": "^4.4|^5.0", "symfony/finder": "^3.4|^4.0|^5.0", - "symfony/form": "^4.3.5", + "symfony/form": "^4.4.17", "symfony/http-foundation": "^4.3|^5.0", "symfony/http-kernel": "^4.4", "symfony/mime": "^4.3|^5.0", diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php index 7db19d8aedc65..173b7ef53c8a2 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/TextareaType.php @@ -23,6 +23,7 @@ class TextareaType extends AbstractType public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['pattern'] = null; + unset($view->vars['attr']['pattern']); } /** diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index e860557468024..38ba3bf53198c 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -2068,7 +2068,7 @@ public function testTextarea() $this->assertWidgetMatchesXpath($form->createView(), [], '/textarea [@name="name"] - [@pattern="foo"] + [not(@pattern)] [.="foo&bar"] ' ); From 4c15f80d8439ab12327e60f3432a231c8477c611 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 24 Sep 2019 00:08:17 +0200 Subject: [PATCH 03/46] fix lexing nested sequences/mappings --- src/Symfony/Component/Yaml/Parser.php | 187 +++++++++++------- .../Component/Yaml/Tests/ParserTest.php | 184 +++++++++++++++++ 2 files changed, 303 insertions(+), 68 deletions(-) diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index e8f951a1eca4d..259e3e6b4946f 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -355,7 +355,7 @@ private function doParse(string $value, int $flags) } try { - return Inline::parse($this->parseQuotedString($this->currentLine), $flags, $this->refs); + return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); @@ -368,7 +368,7 @@ private function doParse(string $value, int $flags) } try { - $parsedMapping = Inline::parse($this->lexInlineMapping($this->currentLine), $flags, $this->refs); + $parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs); while ($this->moveToNextLine()) { if (!$this->isCurrentLineEmpty()) { @@ -389,7 +389,7 @@ private function doParse(string $value, int $flags) } try { - $parsedSequence = Inline::parse($this->lexInlineSequence($this->currentLine), $flags, $this->refs); + $parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs); while ($this->moveToNextLine()) { if (!$this->isCurrentLineEmpty()) { @@ -659,6 +659,11 @@ private function getNextEmbedBlock(int $indentation = null, bool $inSequence = f return implode("\n", $data); } + private function hasMoreLines(): bool + { + return (\count($this->lines) - 1) > $this->currentLineNb; + } + /** * Moves the parser to the next line. */ @@ -736,9 +741,13 @@ private function parseValue(string $value, int $flags, string $context) try { if ('' !== $value && '{' === $value[0]) { - return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs); + $cursor = \strlen($this->currentLine) - \strlen($value); + + return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs); } elseif ('' !== $value && '[' === $value[0]) { - return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs); + $cursor = \strlen($this->currentLine) - \strlen($value); + + return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); } $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null; @@ -1137,106 +1146,148 @@ private function getLineTag(string $value, int $flags, bool $nextLineCheck = tru throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename); } - private function parseQuotedString(string $yaml): ?string + private function lexInlineQuotedString(int &$cursor = 0): string { - if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) { - throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml)); - } + $quotation = $this->currentLine[$cursor]; + $value = $quotation; + ++$cursor; - $lines = [$yaml]; - - while ($this->moveToNextLine()) { - $lines[] = $this->currentLine; + $previousLineWasNewline = true; + $previousLineWasTerminatedWithBackslash = false; - if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) { - break; - } - } - - $value = ''; - - for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) { - if ('' === trim($lines[$i])) { + do { + if ($this->isCurrentLineBlank()) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { $value .= ' '; } - if ('' !== trim($lines[$i]) && '\\' === substr($lines[$i], -1)) { - $value .= ltrim(substr($lines[$i], 0, -1)); - } elseif ('' !== trim($lines[$i])) { - $value .= trim($lines[$i]); + for (; \strlen($this->currentLine) > $cursor; ++$cursor) { + switch ($this->currentLine[$cursor]) { + case '\\': + if (isset($this->currentLine[++$cursor])) { + $value .= '\\'.$this->currentLine[$cursor]; + } + + break; + case $quotation: + ++$cursor; + + if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) { + $value .= "''"; + break; + } + + return $value.$quotation; + default: + $value .= $this->currentLine[$cursor]; + } } - if ('' === trim($lines[$i])) { + if ($this->isCurrentLineBlank()) { $previousLineWasNewline = true; $previousLineWasTerminatedWithBackslash = false; - } elseif ('\\' === substr($lines[$i], -1)) { + } elseif ('\\' === $this->currentLine[-1]) { $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = true; } else { $previousLineWasNewline = false; $previousLineWasTerminatedWithBackslash = false; } - } - return $value; + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + throw new ParseException('Malformed inline YAML string'); } - private function lexInlineMapping(string $yaml): string + private function lexUnquotedString(int &$cursor): string { - if ('' === $yaml || '{' !== $yaml[0]) { - throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml)); - } - - for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) { - } + $offset = $cursor; + $cursor += strcspn($this->currentLine, '[]{},: ', $cursor); - if (isset($yaml[$i]) && '}' === $yaml[$i]) { - return $yaml; - } - - $lines = [$yaml]; - - while ($this->moveToNextLine()) { - $lines[] = $this->currentLine; - } + return substr($this->currentLine, $offset, $cursor - $offset); + } - return implode("\n", $lines); + private function lexInlineMapping(int &$cursor = 0): string + { + return $this->lexInlineStructure($cursor, '}'); } - private function lexInlineSequence(string $yaml): string + private function lexInlineSequence(int &$cursor = 0): string { - if ('' === $yaml || '[' !== $yaml[0]) { - throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml)); - } + return $this->lexInlineStructure($cursor, ']'); + } - for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) { - } + private function lexInlineStructure(int &$cursor, string $closingTag): string + { + $value = $this->currentLine[$cursor]; + ++$cursor; - if (isset($yaml[$i]) && ']' === $yaml[$i]) { - return $yaml; - } + do { + $this->consumeWhitespaces($cursor); + + while (isset($this->currentLine[$cursor])) { + switch ($this->currentLine[$cursor]) { + case '"': + case "'": + $value .= $this->lexInlineQuotedString($cursor); + break; + case ':': + case ',': + $value .= $this->currentLine[$cursor]; + ++$cursor; + break; + case '{': + $value .= $this->lexInlineMapping($cursor); + break; + case '[': + $value .= $this->lexInlineSequence($cursor); + break; + case $closingTag: + $value .= $this->currentLine[$cursor]; + ++$cursor; + + return $value; + case '#': + break 2; + default: + $value .= $this->lexUnquotedString($cursor); + } - $value = $yaml; + if ($this->consumeWhitespaces($cursor)) { + $value .= ' '; + } + } - while ($this->moveToNextLine()) { - for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) { + if ($this->hasMoreLines()) { + $cursor = 0; } + } while ($this->moveToNextLine()); - $trimmedValue = trim($this->currentLine); + throw new ParseException('Malformed inline YAML string'); + } - if ('' !== $trimmedValue && '#' === $trimmedValue[0]) { - continue; - } + private function consumeWhitespaces(int &$cursor): bool + { + $whitespacesConsumed = 0; - $value .= $trimmedValue; + do { + $whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor); + $whitespacesConsumed += $whitespaceOnlyTokenLength; + $cursor += $whitespaceOnlyTokenLength; - if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) { - break; + if (isset($this->currentLine[$cursor])) { + return 0 < $whitespacesConsumed; } - } - return $value; + if ($this->hasMoreLines()) { + $cursor = 0; + } + } while ($this->moveToNextLine()); + + return 0 < $whitespacesConsumed; } } diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index 5f3b8f311d060..f9e4765a5d2af 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -1660,6 +1660,16 @@ public function inlineNotationSpanningMultipleLinesProvider(): array 'foo': 'bar', 'bar': 'baz' } +YAML + , + ], + 'mapping with unquoted strings and values' => [ + ['foo' => 'bar', 'bar' => 'baz'], + << [ + ['foo', 'bar'], + << [ + [ + 'foo' => [ + 'bar' => 'foobar', + ], + ], + << [ + [ + 'foo', + [ + 'bar', + 'baz', + ], + ], + << [ + [ + ['entry1', []], + ['entry2'], + ], + << [ ['foo' => ['bar', 'foobar'], 'bar' => ['baz']], << [ + [ + 'foobar' => [ + 'foo', + 'bar', + ], + 'bar' => 'baz', + ], + << [ [ 'foo' => [ @@ -1824,6 +1897,110 @@ public function inlineNotationSpanningMultipleLinesProvider(): array foo: 'bar baz' +YAML + ], + 'mixed mapping with inline notation having separated lines' => [ + [ + 'map' => [ + 'key' => 'value', + 'a' => 'b', + ], + 'param' => 'some', + ], + << [ + [ + 'map' => [ + 'key' => 'value', + 'a' => 'b', + ], + 'param' => 'some', + ], + << [ + [ + 'map' => [ + 'key' => 'value', + 'a' => 'b', + ], + 'param' => 'some', + ], + << [ + [ + [']'], + ['}'], + ['ba[r'], + ['[ba]r'], + ['bar]'], + ['foo' => 'bar{'], + ['foo' => 'b{ar}'], + ['foo' => 'bar}'], + ], + << [ + [ + ['te"st'], + ['test'], + ["te'st"], + ['te"st]'], + ['te"st'], + ['test'], + ["te'st"], + ['te"st]'], + ], + <<assertEquals(new TaggedValue('foo', ['foo' => 'bar']), $this->parser->parse('!foo {foo: bar}', Yaml::PARSE_CUSTOM_TAGS)); } + public function testInvalidInlineSequenceContainingStringWithEscapedQuotationCharacter() + { + $this->expectException(ParseException::class); + + $this->parser->parse('["\\"]'); + } + /** * @dataProvider taggedValuesProvider */ From 0c92bc5a8324d5c0a0e247b5f4b8fa90551362b5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 19 Nov 2020 01:16:02 +0100 Subject: [PATCH 04/46] [HttpClient] don't fallback to HTTP/1.1 when HTTP/2 streams break --- src/Symfony/Component/HttpClient/Response/CurlResponse.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpClient/Response/CurlResponse.php b/src/Symfony/Component/HttpClient/Response/CurlResponse.php index 435f6402c796d..9709a189f5926 100644 --- a/src/Symfony/Component/HttpClient/Response/CurlResponse.php +++ b/src/Symfony/Component/HttpClient/Response/CurlResponse.php @@ -283,10 +283,7 @@ private static function perform(ClientState $multi, array &$responses = null): v curl_multi_remove_handle($multi->handle, $ch); $waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor); - - if ('1' === $waitFor[1]) { - curl_setopt($ch, \CURLOPT_HTTP_VERSION, \CURL_HTTP_VERSION_1_1); - } + curl_setopt($ch, \CURLOPT_FORBID_REUSE, true); if (0 === curl_multi_add_handle($multi->handle, $ch)) { continue; From 574a184b9a44b967d7e834316ab2f895292e4f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Thu, 19 Nov 2020 19:43:29 +0100 Subject: [PATCH 05/46] Require doctrine/persistence 2 This allows us to remove autoload calls that are necessary for the persistence 1 backwards-compatibility layer to work. The require-dev constraints on doctrine/orm are bumped to ^2.7.3 because lower versions are not compatible with doctrine/persistence 2. --- composer.json | 4 ++-- src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php | 2 -- src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php | 2 -- .../Bridge/Doctrine/Security/User/EntityUserProvider.php | 3 --- src/Symfony/Bridge/Doctrine/composer.json | 4 ++-- 5 files changed, 4 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 3ca90e56d1d68..765c2d6577360 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-xml": "*", "doctrine/event-manager": "~1.0", - "doctrine/persistence": "^1.3|^2", + "doctrine/persistence": "^2", "twig/twig": "^2.10|^3.0", "psr/cache": "~1.0", "psr/container": "^1.0", @@ -116,7 +116,7 @@ "doctrine/collections": "~1.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.10|^3.0", - "doctrine/orm": "~2.4,>=2.4.5", + "doctrine/orm": "^2.7.3", "doctrine/doctrine-bundle": "^2.0", "guzzlehttp/promises": "^1.3.1", "masterminds/html5": "^2.6", diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php index 21b78bada3019..e8e3e71b585cc 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/DoctrineType.php @@ -269,5 +269,3 @@ private function getCachedEntityLoader(ObjectManager $manager, $queryBuilder, st return $this->entityLoaders[$hash] ?? ($this->entityLoaders[$hash] = $this->getLoader($manager, $queryBuilder, $class)); } } - -interface_exists(ObjectManager::class); diff --git a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php index 7cbe648f9b868..6627630675b53 100644 --- a/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php +++ b/src/Symfony/Bridge/Doctrine/Form/Type/EntityType.php @@ -96,5 +96,3 @@ private function parameterToArray(Parameter $parameter): array return [$parameter->getName(), $parameter->getType(), $parameter->getValue()]; } } - -interface_exists(ObjectManager::class); diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index defc2cb2af438..b49b373444c75 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -153,6 +153,3 @@ private function getClassMetadata(): ClassMetadata return $this->getObjectManager()->getClassMetadata($this->classOrAlias); } } - -interface_exists(ObjectManager::class); -interface_exists(ObjectRepository::class); diff --git a/src/Symfony/Bridge/Doctrine/composer.json b/src/Symfony/Bridge/Doctrine/composer.json index 48be4f9215efa..fee16fa8ec2d0 100644 --- a/src/Symfony/Bridge/Doctrine/composer.json +++ b/src/Symfony/Bridge/Doctrine/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "doctrine/event-manager": "~1.0", - "doctrine/persistence": "^1.3|^2", + "doctrine/persistence": "^2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php80": "^1.15", @@ -48,7 +48,7 @@ "doctrine/collections": "~1.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.10|^3.0", - "doctrine/orm": "^2.6.3" + "doctrine/orm": "^2.7.3" }, "conflict": { "doctrine/dbal": "<2.10", From 9ce2e86207f6ba5b65606b00aada1d462bba1758 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 20 Nov 2020 16:42:02 +0100 Subject: [PATCH 06/46] [HttpFoundation] Deprecate BinaryFileResponse::create(). --- UPGRADE-5.2.md | 1 + UPGRADE-6.0.md | 4 +- .../HttpFoundation/BinaryFileResponse.php | 4 ++ .../Component/HttpFoundation/CHANGELOG.md | 1 + .../Tests/BinaryFileResponseTest.php | 43 ++++++++++++++----- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/UPGRADE-5.2.md b/UPGRADE-5.2.md index e62963fe2d50b..c49e55445e34d 100644 --- a/UPGRADE-5.2.md +++ b/UPGRADE-5.2.md @@ -44,6 +44,7 @@ HttpFoundation * Deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead. * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO` or `Request::HEADER_X_FORWARDED_AWS_ELB` or `Request::HEADER_X_FORWARDED_TRAEFIK`constants instead. + * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead Lock ---- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 6e84d2c0dee2f..ba4be59b39a9d 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -64,8 +64,8 @@ HttpFoundation -------------- * Removed `Response::create()`, `JsonResponse::create()`, - `RedirectResponse::create()`, and `StreamedResponse::create()` methods (use - `__construct()` instead) + `RedirectResponse::create()`, `StreamedResponse::create()` and + `BinaryFileResponse::create()` methods (use `__construct()` instead) * Not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()` throws an `InvalidArgumentException`; wrap your filter in a closure instead. * Removed the `Request::HEADER_X_FORWARDED_ALL` constant, use either `Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO` or `Request::HEADER_X_FORWARDED_AWS_ELB` or `Request::HEADER_X_FORWARDED_TRAEFIK`constants instead. diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index 930ea23f39091..b2e2d9e40b55c 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -65,9 +65,13 @@ public function __construct($file, int $status = 200, array $headers = [], bool * @param bool $autoLastModified Whether the Last-Modified header should be automatically set * * @return static + * + * @deprecated since Symfony 5.2, use __construct() instead. */ public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true) { + trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class); + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); } diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index fef21b8723594..a5ba6f720672e 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter` * deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead. * Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead. + * Deprecated `BinaryFileResponse::create()`, use `__construct()` instead 5.1.0 diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index 2efbc2b8aec88..31d6bc2c007bb 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation\Tests; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\Stream; use Symfony\Component\HttpFoundation\Request; @@ -19,6 +20,8 @@ class BinaryFileResponseTest extends ResponseTestCase { + use ExpectDeprecationTrait; + public function testConstruction() { $file = __DIR__.'/../README.md'; @@ -29,6 +32,26 @@ public function testConstruction() $this->assertTrue($response->headers->has('Last-Modified')); $this->assertFalse($response->headers->has('Content-Disposition')); + $response = new BinaryFileResponse($file, 404, [], true, ResponseHeaderBag::DISPOSITION_INLINE); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertFalse($response->headers->has('ETag')); + $this->assertEquals('inline; filename=README.md', $response->headers->get('Content-Disposition')); + } + + /** + * @group legacy + */ + public function testConstructionLegacy() + { + $file = __DIR__.'/../README.md'; + $this->expectDeprecation('Since symfony/http-foundation 5.2: The "Symfony\Component\HttpFoundation\BinaryFileResponse::create()" method is deprecated, use "new Symfony\Component\HttpFoundation\BinaryFileResponse()" instead.'); + $response = BinaryFileResponse::create($file, 404, ['X-Header' => 'Foo'], true, null, true, true); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('Foo', $response->headers->get('X-Header')); + $this->assertTrue($response->headers->has('ETag')); + $this->assertTrue($response->headers->has('Last-Modified')); + $this->assertFalse($response->headers->has('Content-Disposition')); + $response = BinaryFileResponse::create($file, 404, [], true, ResponseHeaderBag::DISPOSITION_INLINE); $this->assertEquals(404, $response->getStatusCode()); $this->assertFalse($response->headers->has('ETag')); @@ -83,7 +106,7 @@ public function testSetContentDispositionGeneratesSafeFallbackFilenameForWrongly */ public function testRequests($requestRange, $offset, $length, $responseRange) { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'])->setAutoEtag(); + $response = (new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']))->setAutoEtag(); // do a request to get the ETag $request = Request::create('/'); @@ -115,7 +138,7 @@ public function testRequests($requestRange, $offset, $length, $responseRange) */ public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange) { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); + $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); // do a request to get the LastModified $request = Request::create('/'); @@ -156,7 +179,7 @@ public function provideRanges() public function testRangeRequestsWithoutLastModifiedDate() { // prevent auto last modified - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'], true, null, false, false); + $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'], true, null, false, false); // prepare a request for a range of the testing file $request = Request::create('/'); @@ -177,7 +200,7 @@ public function testRangeRequestsWithoutLastModifiedDate() */ public function testFullFileRequests($requestRange) { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'])->setAutoEtag(); + $response = (new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']))->setAutoEtag(); // prepare a request for a range of the testing file $request = Request::create('/'); @@ -213,7 +236,7 @@ public function testRangeOnPostMethod() { $request = Request::create('/', 'POST'); $request->headers->set('Range', 'bytes=10-20'); - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); + $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); $data = fread($file, 35); @@ -231,7 +254,7 @@ public function testRangeOnPostMethod() public function testUnpreparedResponseSendsFullFile() { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200); + $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200); $data = file_get_contents(__DIR__.'/File/Fixtures/test.gif'); @@ -247,7 +270,7 @@ public function testUnpreparedResponseSendsFullFile() */ public function testInvalidRequests($requestRange) { - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream'])->setAutoEtag(); + $response = (new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']))->setAutoEtag(); // prepare a request for a range of the testing file $request = Request::create('/'); @@ -278,7 +301,7 @@ public function testXSendfile($file) $request->headers->set('X-Sendfile-Type', 'X-Sendfile'); BinaryFileResponse::trustXSendfileTypeHeader(); - $response = BinaryFileResponse::create($file, 200, ['Content-Type' => 'application/octet-stream']); + $response = new BinaryFileResponse($file, 200, ['Content-Type' => 'application/octet-stream']); $response->prepare($request); $this->expectOutputString(''); @@ -338,7 +361,7 @@ public function testDeleteFileAfterSend() public function testAcceptRangeOnUnsafeMethods() { $request = Request::create('/', 'POST'); - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); + $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); $response->prepare($request); $this->assertEquals('none', $response->headers->get('Accept-Ranges')); @@ -347,7 +370,7 @@ public function testAcceptRangeOnUnsafeMethods() public function testAcceptRangeNotOverriden() { $request = Request::create('/', 'POST'); - $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); + $response = new BinaryFileResponse(__DIR__.'/File/Fixtures/test.gif', 200, ['Content-Type' => 'application/octet-stream']); $response->headers->set('Accept-Ranges', 'foo'); $response->prepare($request); From fcbf0bf76ea165cb66c7a9d4b84bd4441994d8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Fri, 20 Nov 2020 22:20:21 +0100 Subject: [PATCH 07/46] Display debug info --- .github/workflows/tests.yml | 12 ++++++------ .../Cache/Adapter/CouchbaseBucketAdapter.php | 6 +++--- .../Tests/Adapter/CouchbaseBucketAdapterTest.php | 7 ++++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1387e5ac9c130..b9f5438e2e061 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -93,17 +93,12 @@ jobs: - name: Install system dependencies run: | - echo "::group::add apt sources" - sudo wget -O - http://packages.couchbase.com/ubuntu/couchbase.key | sudo apt-key add - - echo "deb http://packages.couchbase.com/ubuntu bionic bionic/main" | sudo tee /etc/apt/sources.list.d/couchbase.list - echo "::endgroup::" - echo "::group::apt-get update" sudo apt-get update echo "::endgroup::" echo "::group::install tools & libraries" - sudo apt-get install libcouchbase-dev librdkafka-dev + sudo apt-get install librdkafka-dev echo "::endgroup::" - name: Configure Couchbase @@ -122,6 +117,11 @@ jobs: php-version: "${{ matrix.php }}" tools: pecl + - name: Display versions + run: | + php -r 'foreach (get_loaded_extensions() as $extension) echo $extension . " " . phpversion($extension) . PHP_EOL;' + php -i + - name: Load fixtures uses: docker://bitnami/openldap with: diff --git a/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php b/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php index a0e8f4027181c..36667f2a0dfb9 100644 --- a/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/CouchbaseBucketAdapter.php @@ -42,7 +42,7 @@ class CouchbaseBucketAdapter extends AbstractAdapter public function __construct(\CouchbaseBucket $bucket, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null) { if (!static::isSupported()) { - throw new CacheException('Couchbase >= 2.6.0 is required.'); + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); } $this->maxIdLength = static::MAX_KEY_LENGTH; @@ -66,7 +66,7 @@ public static function createConnection($servers, array $options = []): \Couchba } if (!static::isSupported()) { - throw new CacheException('Couchbase >= 2.6.0 is required.'); + throw new CacheException('Couchbase >= 2.6.0 < 3.0.0 is required.'); } set_error_handler(function ($type, $msg, $file, $line) { throw new \ErrorException($msg, 0, $type, $file, $line); }); @@ -125,7 +125,7 @@ public static function createConnection($servers, array $options = []): \Couchba public static function isSupported(): bool { - return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>='); + return \extension_loaded('couchbase') && version_compare(phpversion('couchbase'), '2.6.0', '>=') && version_compare(phpversion('couchbase'), '3.0', '<'); } private static function getOptions(string $options): array diff --git a/src/Symfony/Component/Cache/Tests/Adapter/CouchbaseBucketAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/CouchbaseBucketAdapterTest.php index 120d0d94c0cc5..c72d6710f22e9 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/CouchbaseBucketAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/CouchbaseBucketAdapterTest.php @@ -16,7 +16,8 @@ use Symfony\Component\Cache\Adapter\CouchbaseBucketAdapter; /** - * @requires extension couchbase 2.6.0 + * @requires extension couchbase <3.0.0 + * @requires extension couchbase >=2.6.0 * @group integration * * @author Antonio Jose Cerezo Aranda @@ -32,6 +33,10 @@ class CouchbaseBucketAdapterTest extends AdapterTestCase public static function setupBeforeClass(): void { + if (!CouchbaseBucketAdapter::isSupported()) { + self::markTestSkipped('Couchbase >= 2.6.0 < 3.0.0 is required.'); + } + self::$client = AbstractAdapter::createConnection('couchbase://'.getenv('COUCHBASE_HOST').'/cache', ['username' => getenv('COUCHBASE_USER'), 'password' => getenv('COUCHBASE_PASS')] ); From af8bd4bd2615bb839703378a439cee2d29afaaba Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 21 Nov 2020 09:49:39 +0100 Subject: [PATCH 08/46] Bump Symfony version to 5.2.0 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index aa069bb7e952e..3522aaa770665 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -74,12 +74,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.2.0-RC2'; + const VERSION = '5.2.0-DEV'; const VERSION_ID = 50200; const MAJOR_VERSION = 5; const MINOR_VERSION = 2; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'RC2'; + const EXTRA_VERSION = 'DEV'; const END_OF_MAINTENANCE = '07/2021'; const END_OF_LIFE = '07/2021'; From fe6a2dd64fc7f703d7bcd6e3b9448dc881debf60 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 19 Nov 2020 19:56:09 +0100 Subject: [PATCH 09/46] prevent duplicated error message for file upload limits --- .../Form/Extension/Core/Type/FileType.php | 4 +- .../ViolationMapper/ViolationMapper.php | 19 ++++ .../Component/Form/FileUploadError.php | 19 ++++ .../ViolationMapper/ViolationMapperTest.php | 92 +++++++++++++++++++ 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Form/FileUploadError.php diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index 207944bf5ad6d..d8b93f6fc77b0 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FileUploadError; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; @@ -171,7 +171,7 @@ private function getFileUploadError(int $errorCode) $message = strtr($messageTemplate, $messageParameters); } - return new FormError($message, $messageTemplate, $messageParameters); + return new FileUploadError($message, $messageTemplate, $messageParameters); } /** diff --git a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php index 8799fc196cc95..4c19e9850a444 100644 --- a/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php +++ b/src/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php @@ -11,12 +11,14 @@ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; +use Symfony\Component\Form\FileUploadError; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Util\InheritDataAwareIterator; use Symfony\Component\PropertyAccess\PropertyPathBuilder; use Symfony\Component\PropertyAccess\PropertyPathIterator; use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface; +use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\ConstraintViolation; /** @@ -124,6 +126,23 @@ public function mapViolation(ConstraintViolation $violation, FormInterface $form // Only add the error if the form is synchronized if ($this->acceptsErrors($scope)) { + if ($violation->getConstraint() instanceof File && (string) \UPLOAD_ERR_INI_SIZE === $violation->getCode()) { + $errorsTarget = $scope; + + while (null !== $errorsTarget->getParent() && $errorsTarget->getConfig()->getErrorBubbling()) { + $errorsTarget = $errorsTarget->getParent(); + } + + $errors = $errorsTarget->getErrors(); + $errorsTarget->clearErrors(); + + foreach ($errors as $error) { + if (!$error instanceof FileUploadError) { + $errorsTarget->addError($error); + } + } + } + $scope->addError(new FormError( $violation->getMessage(), $violation->getMessageTemplate(), diff --git a/src/Symfony/Component/Form/FileUploadError.php b/src/Symfony/Component/Form/FileUploadError.php new file mode 100644 index 0000000000000..20142b20337ea --- /dev/null +++ b/src/Symfony/Component/Form/FileUploadError.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +/** + * @internal + */ +class FileUploadError extends FormError +{ +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php index 822e36e6a1dcc..8c71b7bfacefa 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/ViolationMapper/ViolationMapperTest.php @@ -18,12 +18,14 @@ use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper; +use Symfony\Component\Form\FileUploadError; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormConfigBuilder; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Tests\Extension\Validator\ViolationMapper\Fixtures\Issue; use Symfony\Component\PropertyAccess\PropertyPath; +use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationInterface; @@ -81,6 +83,7 @@ protected function getForm($name = 'name', $propertyPath = null, $dataClass = nu $config->setPropertyPath($propertyPath); $config->setCompound(true); $config->setDataMapper(new PropertyPathMapper()); + $config->setErrorBubbling($options['error_bubbling'] ?? false); if (!$synchronized) { $config->addViewTransformer(new CallbackTransformer( @@ -1590,4 +1593,93 @@ public function testBacktrackIfSeveralSubFormsWithSamePropertyPath() $this->assertEquals([$this->getFormError($violation2, $grandChild2)], iterator_to_array($grandChild2->getErrors()), $grandChild2->getName().' should have an error, but has none'); $this->assertEquals([$this->getFormError($violation3, $grandChild3)], iterator_to_array($grandChild3->getErrors()), $grandChild3->getName().' should have an error, but has none'); } + + public function testFileUploadErrorIsNotRemovedIfNoFileSizeConstraintViolationWasRaised() + { + $form = $this->getForm('form'); + $form->addError(new FileUploadError( + 'The file is too large. Allowed maximum size is 2 MB.', + 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.', + [ + '{{ limit }}' => '2', + '{{ suffix }}' => 'MB', + ] + )); + + $this->mapper->mapViolation($this->getConstraintViolation('data'), $form); + + $this->assertCount(2, $form->getErrors()); + } + + public function testFileUploadErrorIsRemovedIfFileSizeConstraintViolationWasRaised() + { + $form = $this->getForm('form'); + $form->addError(new FileUploadError( + 'The file is too large. Allowed maximum size is 2 MB.', + 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.', + [ + '{{ limit }}' => '2', + '{{ suffix }}' => 'MB', + ] + )); + + $violation = new ConstraintViolation( + 'The file is too large (3 MB). Allowed maximum size is 2 MB.', + 'The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.', + [ + '{{ limit }}' => '2', + '{{ size }}' => '3', + '{{ suffix }}' => 'MB', + ], + '', + 'data', + null, + null, + (string) \UPLOAD_ERR_INI_SIZE, + new File() + ); + $this->mapper->mapViolation($this->getConstraintViolation('data'), $form); + $this->mapper->mapViolation($violation, $form); + + $this->assertCount(2, $form->getErrors()); + } + + public function testFileUploadErrorIsRemovedIfFileSizeConstraintViolationWasRaisedOnFieldWithErrorBubbling() + { + $parent = $this->getForm('parent'); + $child = $this->getForm('child', 'file', null, [], false, true, [ + 'error_bubbling' => true, + ]); + $parent->add($child); + $child->addError(new FileUploadError( + 'The file is too large. Allowed maximum size is 2 MB.', + 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.', + [ + '{{ limit }}' => '2', + '{{ suffix }}' => 'MB', + ] + )); + + $violation = new ConstraintViolation( + 'The file is too large (3 MB). Allowed maximum size is 2 MB.', + 'The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.', + [ + '{{ limit }}' => '2', + '{{ size }}' => '3', + '{{ suffix }}' => 'MB', + ], + null, + 'data.file', + null, + null, + (string) \UPLOAD_ERR_INI_SIZE, + new File() + ); + $this->mapper->mapViolation($this->getConstraintViolation('data'), $parent); + $this->mapper->mapViolation($this->getConstraintViolation('data.file'), $parent); + $this->mapper->mapViolation($violation, $parent); + + $this->assertCount(3, $parent->getErrors()); + $this->assertCount(0, $child->getErrors()); + } } From 79571e0e7722aa57ac86f67df2220b366e4d3d18 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 22 Nov 2020 09:18:58 -0500 Subject: [PATCH 10/46] Removing AnonymousPassport This is not used - is leftover from the security component authenticator changes --- .../Authentication/AuthenticatorManager.php | 5 ---- .../Passport/AnonymousPassport.php | 25 ------------------- .../UserProviderListenerTest.php | 3 --- 3 files changed, 33 deletions(-) delete mode 100644 src/Symfony/Component/Security/Http/Authenticator/Passport/AnonymousPassport.php diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php index 2b6328ea3507b..318fd7bd21193 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php @@ -22,7 +22,6 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface; -use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\BadgeInterface; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; @@ -219,10 +218,6 @@ private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, $this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN); } - if ($passport instanceof AnonymousPassport) { - return $response; - } - $this->eventDispatcher->dispatch($loginSuccessEvent = new LoginSuccessEvent($authenticator, $passport, $authenticatedToken, $request, $response, $this->firewallName)); return $loginSuccessEvent->getResponse(); diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/AnonymousPassport.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/AnonymousPassport.php deleted file mode 100644 index 678745eea00a9..0000000000000 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/AnonymousPassport.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Http\Authenticator\Passport; - -/** - * A passport used during anonymous authentication. - * - * @author Wouter de Jong - * - * @internal - * @experimental in 5.2 - */ -class AnonymousPassport implements PassportInterface -{ - use PassportTrait; -} diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php index b43aebde96aab..95f99de8d0fde 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php @@ -16,9 +16,7 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; -use Symfony\Component\Security\Http\Authenticator\Passport\AnonymousPassport; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; -use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Event\CheckPassportEvent; use Symfony\Component\Security\Http\EventListener\UserProviderListener; @@ -61,7 +59,6 @@ public function testNotOverrideUserLoader($passport) public function provideCompletePassports() { - yield [new AnonymousPassport()]; yield [new SelfValidatingPassport(new UserBadge('wouter', function () {}))]; } From 76a077d947d4c4d52c230d1395badc2a08ed7f74 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 24 Nov 2020 10:55:37 +0100 Subject: [PATCH 11/46] [VarDumper] fix casting resources turned into objects on PHP 8 --- src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 9d50da90577a7..178237905e37d 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -149,7 +149,10 @@ abstract class AbstractCloner implements ClonerInterface ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + + 'GdImage' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'], ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], @@ -157,9 +160,14 @@ abstract class AbstractCloner implements ClonerInterface ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + + 'OpenSSLCertificate' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], + + 'XmlParser' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], ]; From 673b8e941a11344ad95037fa7b01600ae8e0ca86 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 17 Nov 2020 17:51:58 +0100 Subject: [PATCH 12/46] fix lexing strings containing escaped quotation characters --- src/Symfony/Component/Yaml/Parser.php | 81 ++++++++++--------- .../Component/Yaml/Tests/ParserTest.php | 48 +++++++++++ 2 files changed, 91 insertions(+), 38 deletions(-) diff --git a/src/Symfony/Component/Yaml/Parser.php b/src/Symfony/Component/Yaml/Parser.php index 259e3e6b4946f..0c4a76e2b189e 100644 --- a/src/Symfony/Component/Yaml/Parser.php +++ b/src/Symfony/Component/Yaml/Parser.php @@ -750,54 +750,54 @@ private function parseValue(string $value, int $flags, string $context) return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs); } - $quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null; - - // do not take following lines into account when the current line is a quoted single line value - if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) { - return Inline::parse($value, $flags, $this->refs); - } + switch ($value[0] ?? '') { + case '"': + case "'": + $cursor = \strlen($this->currentLine) - \strlen($value); + $parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs); + + if (isset($this->currentLine[$cursor]) && preg_replace('/\s*#.*$/A', '', substr($this->currentLine, $cursor))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor))); + } - $lines = []; + return $parsedValue; + default: + $lines = []; - while ($this->moveToNextLine()) { - // unquoted strings end before the first unindented line - if (null === $quotation && 0 === $this->getCurrentLineIndentation()) { - $this->moveToPreviousLine(); + while ($this->moveToNextLine()) { + // unquoted strings end before the first unindented line + if (0 === $this->getCurrentLineIndentation()) { + $this->moveToPreviousLine(); - break; - } + break; + } - $lines[] = trim($this->currentLine); + $lines[] = trim($this->currentLine); + } - // quoted string values end with a line that is terminated with the quotation character - $escapedLine = str_replace(['\\\\', '\\"'], '', $this->currentLine); - if ('' !== $escapedLine && substr($escapedLine, -1) === $quotation) { - break; - } - } + for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { + if ('' === $lines[$i]) { + $value .= "\n"; + $previousLineBlank = true; + } elseif ($previousLineBlank) { + $value .= $lines[$i]; + $previousLineBlank = false; + } else { + $value .= ' '.$lines[$i]; + $previousLineBlank = false; + } + } - for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) { - if ('' === $lines[$i]) { - $value .= "\n"; - $previousLineBlank = true; - } elseif ($previousLineBlank) { - $value .= $lines[$i]; - $previousLineBlank = false; - } else { - $value .= ' '.$lines[$i]; - $previousLineBlank = false; - } - } + Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); - Inline::$parsedLineNumber = $this->getRealCurrentLineNb(); + $parsedValue = Inline::parse($value, $flags, $this->refs); - $parsedValue = Inline::parse($value, $flags, $this->refs); + if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { + throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + } - if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { - throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename); + return $parsedValue; } - - return $parsedValue; } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); @@ -1154,8 +1154,13 @@ private function lexInlineQuotedString(int &$cursor = 0): string $previousLineWasNewline = true; $previousLineWasTerminatedWithBackslash = false; + $lineNumber = 0; do { + if (++$lineNumber > 1) { + $cursor += strspn($this->currentLine, ' ', $cursor); + } + if ($this->isCurrentLineBlank()) { $value .= "\n"; } elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) { diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php index f9e4765a5d2af..a9715151f1b03 100644 --- a/src/Symfony/Component/Yaml/Tests/ParserTest.php +++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php @@ -1570,6 +1570,54 @@ public function testParseMultiLineUnquotedString() $this->assertSame(['foo' => 'bar baz foobar foo', 'bar' => 'baz'], $this->parser->parse($yaml)); } + /** + * @dataProvider escapedQuotationCharactersInQuotedStrings + */ + public function testParseQuotedStringContainingEscapedQuotationCharacters(string $yaml, array $expected) + { + $this->assertSame($expected, $this->parser->parse($yaml)); + } + + public function escapedQuotationCharactersInQuotedStrings() + { + return [ + 'single quoted string' => [ + << [ + [ + 'message' => 'No emails received before timeout - Address: \'test@testemail.company.com\' Keyword: \'Your Order confirmation\' ttl: 50', + 'outcome' => 'failed', + ], + ], + ], + ], + 'double quoted string' => [ + << [ + [ + 'message' => 'No emails received before timeout - Address: "test@testemail.company.com" Keyword: "Your Order confirmation" ttl: 50', + 'outcome' => 'failed', + ], + ], + ], + ], + ]; + } + public function testParseMultiLineString() { $this->assertEquals("foo bar\nbaz", $this->parser->parse("foo\nbar\n\nbaz")); From ca93ae5b1affaa15d120f11c045d901a3946414f Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Tue, 24 Nov 2020 21:09:17 +0900 Subject: [PATCH 13/46] Fix typo in comment possibe -> possible --- .../Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php b/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php index e77d24aecf9c1..402ac51351d55 100644 --- a/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php +++ b/src/Symfony/Component/Routing/Matcher/Dumper/CompiledUrlMatcherDumper.php @@ -242,7 +242,7 @@ private function compileStaticRoutes(array $staticRoutes, array &$conditions): a * Paths that can match two or more routes, or have user-specified conditions are put in separate switch's cases. * * Last but not least: - * - Because it is not possibe to mix unicode/non-unicode patterns in a single regexp, several of them can be generated. + * - Because it is not possible to mix unicode/non-unicode patterns in a single regexp, several of them can be generated. * - The same regexp can be used several times when the logic in the switch rejects the match. When this happens, the * matching-but-failing subpattern is excluded by replacing its name by "(*F)", which forces a failure-to-match. * To ease this backlisting operation, the name of subpatterns is also the string offset where the replacement should occur. From 2834c279d7f26e2ef45739f7468cdd23654c7adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Wed, 25 Nov 2020 01:21:23 +0100 Subject: [PATCH 14/46] Fix console closing tag --- .../Component/Console/Formatter/OutputFormatter.php | 8 ++++++++ .../Style/SymfonyStyle/command/command_20.php | 13 +++++++++++++ .../Style/SymfonyStyle/output/output_20.txt | 1 + 3 files changed, 22 insertions(+) create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_20.php create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php index 5d52896ac995c..26288ce62f473 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php @@ -25,6 +25,14 @@ class OutputFormatter implements WrappableOutputFormatterInterface private $styles = []; private $styleStack; + public function __clone() + { + $this->styleStack = clone $this->styleStack; + foreach ($this->styles as $key => $value) { + $this->styles[$key] = clone $value; + } + } + /** * Escapes "<" special char in given text. * diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_20.php b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_20.php new file mode 100644 index 0000000000000..6b47969eeeba6 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/command/command_20.php @@ -0,0 +1,13 @@ +setDecorated(true); + $output = new SymfonyStyle($input, $output); + $output->write('do you want something'); + $output->writeln('?'); +}; diff --git a/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt new file mode 100644 index 0000000000000..c082985309229 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/Style/SymfonyStyle/output/output_20.txt @@ -0,0 +1 @@ +do you want something? From 18fca2984d97f90b291c5e3a9cd163a524d716ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Tue, 24 Nov 2020 13:45:24 +0100 Subject: [PATCH 15/46] Use a partial buffer in SymfonyStyle --- .../Console/Output/TrimmedBufferOutput.php | 67 +++++++++++++++++++ .../Component/Console/Style/SymfonyStyle.php | 9 ++- .../Console/Tests/Style/SymfonyStyleTest.php | 15 +++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/Console/Output/TrimmedBufferOutput.php diff --git a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php new file mode 100644 index 0000000000000..c014d43633cf8 --- /dev/null +++ b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * A BufferedOutput that keeps only the last N chars. + * + * @author Jérémy Derussé + */ +class TrimmedBufferOutput extends Output +{ + private $maxLength; + private $buffer = ''; + + public function __construct( + ?int $verbosity = self::VERBOSITY_NORMAL, + bool $decorated = false, + OutputFormatterInterface $formatter = null, + int $maxLength + ) { + if ($maxLength <= 0) { + throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); + } + + parent::__construct($verbosity, $decorated, $formatter); + $this->maxLength = $maxLength; + } + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= \PHP_EOL; + } + + $this->buffer = substr($this->buffer, 0 - $this->maxLength); + } +} diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index b40c16e99d005..a5edac3c4a1f1 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -21,8 +21,8 @@ use Symfony\Component\Console\Helper\TableCell; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\TrimmedBufferOutput; use Symfony\Component\Console\Question\ChoiceQuestion; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Question\Question; @@ -46,7 +46,7 @@ class SymfonyStyle extends OutputStyle public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; - $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); + $this->bufferedOutput = new TrimmedBufferOutput($output->getVerbosity(), false, clone $output->getFormatter(), \DIRECTORY_SEPARATOR === '\\' ? 4 : 2); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); @@ -449,9 +449,8 @@ private function autoPrependText(): void private function writeBuffer(string $message, bool $newLine, int $type): void { - // We need to know if the two last chars are PHP_EOL - // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer - $this->bufferedOutput->write(substr($message, -4), $newLine, $type); + // We need to know if the last chars are PHP_EOL + $this->bufferedOutput->write($message, $newLine, $type); } private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array diff --git a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php index 943b94172a609..2444d89ba001a 100644 --- a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php +++ b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php @@ -14,8 +14,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Tester\CommandTester; @@ -115,4 +117,17 @@ public function testGetErrorStyleUsesTheCurrentOutputIfNoErrorOutputIsAvailable( $this->assertInstanceOf(SymfonyStyle::class, $style->getErrorStyle()); } + + public function testMemoryConsumption() + { + $io = new SymfonyStyle(new ArrayInput([]), new NullOutput()); + $str = 'teststr'; + $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); + $start = memory_get_usage(); + for ($i = 0; $i < 100; ++$i) { + $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); + } + + $this->assertSame(0, memory_get_usage() - $start); + } } From f2713d6580e2e3451da8ffb5b3b7706eeb414bca Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 25 Nov 2020 12:55:08 +0100 Subject: [PATCH 16/46] Improve return phpdoc for Normalizer --- .../Component/Serializer/Normalizer/ArrayDenormalizer.php | 2 ++ .../Normalizer/ConstraintViolationListNormalizer.php | 2 ++ .../Component/Serializer/Normalizer/DataUriNormalizer.php | 4 ++++ .../Serializer/Normalizer/DateIntervalNormalizer.php | 4 ++++ .../Component/Serializer/Normalizer/DateTimeNormalizer.php | 4 ++++ .../Serializer/Normalizer/DateTimeZoneNormalizer.php | 4 ++++ .../Component/Serializer/Normalizer/ProblemNormalizer.php | 2 ++ 7 files changed, 22 insertions(+) diff --git a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php index 759dbcd889ce7..65b1c2e82175b 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php @@ -35,6 +35,8 @@ class ArrayDenormalizer implements ContextAwareDenormalizerInterface, Serializer * {@inheritdoc} * * @throws NotNormalizableValueException + * + * @return array */ public function denormalize($data, $type, $format = null, array $context = []) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php index 36373d9f5c32b..81a5416ff0633 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php @@ -40,6 +40,8 @@ public function __construct($defaultContext = [], NameConverterInterface $nameCo /** * {@inheritdoc} + * + * @return array */ public function normalize($object, $format = null, array $context = []) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php index 87bb96aad81bb..4646d6ce0d249 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php @@ -61,6 +61,8 @@ public function __construct($mimeTypeGuesser = null) /** * {@inheritdoc} + * + * @return string */ public function normalize($object, $format = null, array $context = []) { @@ -102,6 +104,8 @@ public function supportsNormalization($data, $format = null) * * @throws InvalidArgumentException * @throws NotNormalizableValueException + * + * @return \SplFileInfo */ public function denormalize($data, $type, $format = null, array $context = []) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php index c4b7c55ab170c..31cadaa88beeb 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php @@ -46,6 +46,8 @@ public function __construct($defaultContext = []) * {@inheritdoc} * * @throws InvalidArgumentException + * + * @return string */ public function normalize($object, $format = null, array $context = []) { @@ -77,6 +79,8 @@ public function hasCacheableSupportsMethod(): bool * * @throws InvalidArgumentException * @throws UnexpectedValueException + * + * @return \DateInterval */ public function denormalize($data, $type, $format = null, array $context = []) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index 34e1083bc7e08..996700cf1406d 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -57,6 +57,8 @@ public function __construct($defaultContext = [], \DateTimeZone $timezone = null * {@inheritdoc} * * @throws InvalidArgumentException + * + * @return string */ public function normalize($object, $format = null, array $context = []) { @@ -87,6 +89,8 @@ public function supportsNormalization($data, $format = null) * {@inheritdoc} * * @throws NotNormalizableValueException + * + * @return \DateTimeInterface */ public function denormalize($data, $type, $format = null, array $context = []) { diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php index 519381b22388a..1080393240a4d 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeZoneNormalizer.php @@ -25,6 +25,8 @@ class DateTimeZoneNormalizer implements NormalizerInterface, DenormalizerInterfa * {@inheritdoc} * * @throws InvalidArgumentException + * + * @return string */ public function normalize($object, $format = null, array $context = []) { @@ -47,6 +49,8 @@ public function supportsNormalization($data, $format = null) * {@inheritdoc} * * @throws NotNormalizableValueException + * + * @return \DateTimeZone */ public function denormalize($data, $type, $format = null, array $context = []) { diff --git a/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php index 17f4500f26fda..ca7b78b5c68ee 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php @@ -37,6 +37,8 @@ public function __construct(bool $debug = false, array $defaultContext = []) /** * {@inheritdoc} + * + * @return array */ public function normalize($exception, $format = null, array $context = []) { From 98cf389fb9651fc5e087bfd5c716aeb173fc18dc Mon Sep 17 00:00:00 2001 From: Camille Dejoye Date: Thu, 26 Nov 2020 14:01:08 +0100 Subject: [PATCH 17/46] fix denormalizing scalar with UnwrappingDenormalizer --- src/Symfony/Component/Serializer/Serializer.php | 7 +++++-- src/Symfony/Component/Serializer/Tests/SerializerTest.php | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index b8c33a2fe56c5..6414caf900472 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -189,7 +189,10 @@ public function normalize($data, string $format = null, array $context = []) */ public function denormalize($data, string $type, string $format = null, array $context = []) { - if (isset(self::SCALAR_TYPES[$type])) { + $normalizer = $this->getDenormalizer($data, $type, $format, $context); + + // Check for a denormalizer first, e.g. the data is wrapped + if (!$normalizer && isset(self::SCALAR_TYPES[$type])) { if (!('is_'.$type)($data)) { throw new NotNormalizableValueException(sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data))); } @@ -201,7 +204,7 @@ public function denormalize($data, string $type, string $format = null, array $c throw new LogicException('You must register at least one normalizer to be able to denormalize objects.'); } - if ($normalizer = $this->getDenormalizer($data, $type, $format, $context)) { + if ($normalizer) { return $normalizer->denormalize($data, $type, $format, $context); } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 80c430a7d4323..b72d9030d2155 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -613,6 +613,13 @@ public function testDeserializeInconsistentScalarArray() $serializer->deserialize('["42"]', 'int[]', 'json'); } + public function testDeserializeWrappedScalar() + { + $serializer = new Serializer([new UnwrappingDenormalizer()], ['json' => new JsonEncoder()]); + + $this->assertSame(42, $serializer->deserialize('{"wrapper": 42}', 'int', 'json', [UnwrappingDenormalizer::UNWRAP_PATH => '[wrapper]'])); + } + private function serializerWithClassDiscriminator() { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); From dff539434e2f553fc5d73298870b995d8e7fb51a Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 16 Oct 2020 10:44:52 +0200 Subject: [PATCH 18/46] [PhpUnitBridge] Fix qualification of deprecations triggered by the debug class loader --- src/Symfony/Bridge/PhpUnit/.gitignore | 1 + .../DeprecationErrorHandler/Deprecation.php | 14 +++++ .../debug_class_loader_autoload.phpt | 51 +++++++++++++++++++ .../fake_app/BarService.php | 13 +++++ .../ExtendsDeprecatedClassFromOtherVendor.php | 10 ++++ .../fake_vendor/composer/autoload_real.php | 16 ++++-- .../fake_vendor/fcy/lib/DeprecatedClass.php | 10 ++++ .../symfony/error-handler/.gitkeep | 0 src/Symfony/Bridge/PhpUnit/composer.json | 3 ++ 9 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/BarService.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/acme/lib/ExtendsDeprecatedClassFromOtherVendor.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/fcy/lib/DeprecatedClass.php create mode 100644 src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/symfony/error-handler/.gitkeep diff --git a/src/Symfony/Bridge/PhpUnit/.gitignore b/src/Symfony/Bridge/PhpUnit/.gitignore index c49a5d8df5c65..9d8c4aadaf9f5 100644 --- a/src/Symfony/Bridge/PhpUnit/.gitignore +++ b/src/Symfony/Bridge/PhpUnit/.gitignore @@ -1,3 +1,4 @@ vendor/ composer.lock phpunit.xml +Tests/DeprecationErrorHandler/fake_vendor/symfony/error-handler/DebugClassLoader.php diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php index 911d37495e15a..9eded1a14e124 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler/Deprecation.php @@ -13,6 +13,8 @@ use PHPUnit\Util\Test; use Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerFor; +use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader; +use Symfony\Component\ErrorHandler\DebugClassLoader; /** * @internal @@ -53,6 +55,18 @@ class Deprecation public function __construct($message, array $trace, $file) { $this->trace = $trace; + + if ('trigger_error' === ($trace[1]['function'] ?? null) + && (DebugClassLoader::class === ($class = $trace[2]['class'] ?? null) || LegacyDebugClassLoader::class === $class) + && 'checkClass' === ($trace[2]['function'] ?? null) + && null !== ($extraFile = $trace[2]['args'][1] ?? null) + && '' !== $extraFile + && false !== $extraFile = realpath($extraFile) + ) { + $this->getOriginalFilesStack(); + array_splice($this->originalFilesStack, 2, 1, $extraFile); + } + $this->message = $message; $i = \count($trace); while (1 < $i && $this->lineShouldBeSkipped($trace[--$i])) { diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt new file mode 100644 index 0000000000000..781027e84fe66 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/debug_class_loader_autoload.phpt @@ -0,0 +1,51 @@ +--TEST-- +Test that a deprecation from the DebugClassLoader on a vendor class autoload triggered by an app class is considered indirect. +--FILE-- + +--EXPECTF-- +Remaining indirect deprecation notices (1) + + 1x: The "acme\lib\ExtendsDeprecatedClassFromOtherVendor" class extends "fcy\lib\DeprecatedClass" that is deprecated. + 1x in BarService::__construct from App\Services diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/BarService.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/BarService.php new file mode 100644 index 0000000000000..868de5bd443db --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_app/BarService.php @@ -0,0 +1,13 @@ + [__DIR__.'/../../fake_app/'], 'acme\\lib\\' => [__DIR__.'/../acme/lib/'], + 'fcy\\lib\\' => [__DIR__.'/../fcy/lib/'], ]; } public function loadClass($className) + { + if ($file = $this->findFile($className)) { + require $file; + } + } + + public function findFile($class) { foreach ($this->getPrefixesPsr4() as $prefix => $baseDirs) { - if (strpos($className, $prefix) !== 0) { + if (strpos($class, $prefix) !== 0) { continue; } foreach ($baseDirs as $baseDir) { - $file = str_replace([$prefix, '\\'], [$baseDir, '/'], $className.'.php'); + $file = str_replace([$prefix, '\\'], [$baseDir, '/'], $class.'.php'); if (file_exists($file)) { - require $file; + return $file; } } } + + return false; } } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/fcy/lib/DeprecatedClass.php b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/fcy/lib/DeprecatedClass.php new file mode 100644 index 0000000000000..f6672cea20400 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/fake_vendor/fcy/lib/DeprecatedClass.php @@ -0,0 +1,10 @@ +=5.5.9" }, + "require-dev": { + "symfony/error-handler": "^4.4|^5.0" + }, "suggest": { "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, From b047064842c534d5d417d22294b89825c1753967 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 27 Nov 2020 01:30:48 +0100 Subject: [PATCH 19/46] Fix test. --- src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php index 2444d89ba001a..16bb2baec4ac7 100644 --- a/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php +++ b/src/Symfony/Component/Console/Tests/Style/SymfonyStyleTest.php @@ -123,6 +123,7 @@ public function testMemoryConsumption() $io = new SymfonyStyle(new ArrayInput([]), new NullOutput()); $str = 'teststr'; $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); + $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); $start = memory_get_usage(); for ($i = 0; $i < 100; ++$i) { $io->writeln($str, SymfonyStyle::VERBOSITY_QUIET); From 01bea3c0852aeb3b3ec07caf4dd46e0526a9d08c Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 27 Nov 2020 01:11:54 +0100 Subject: [PATCH 20/46] Support for SwitchUserToken instances serialized with 4.4/5.1. --- .../Authentication/Token/SwitchUserToken.php | 7 ++++++- .../Token/Fixtures/switch-user-token-4.4.txt | Bin 0 -> 1917 bytes .../Authentication/Token/SwitchUserTokenTest.php | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Security/Core/Tests/Authentication/Token/Fixtures/switch-user-token-4.4.txt diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php index e575999374893..ccccb5b51c04b 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php @@ -59,7 +59,12 @@ public function __serialize(): array */ public function __unserialize(array $data): void { - [$this->originalToken, $this->originatedFromUri, $parentData] = $data; + if (3 > \count($data)) { + // Support for tokens serialized with version 5.1 or lower of symfony/security-core. + [$this->originalToken, $parentData] = $data; + } else { + [$this->originalToken, $this->originatedFromUri, $parentData] = $data; + } $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); parent::__unserialize($parentData); } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Fixtures/switch-user-token-4.4.txt b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Fixtures/switch-user-token-4.4.txt new file mode 100644 index 0000000000000000000000000000000000000000..7b3f7c40920dbe5d8d51326d95d3bdc721a4b2b6 GIT binary patch literal 1917 zcmeHI!D_-l6!cs63yj8?zMP8KlV~yZ7AR|6aci@{j?qY`JFSGuqfo?!kvTmX5j_|A247{&bE#GIrnf>x;a=wPW|0X~ z-x)Z5--VJx4@wE$U<5<=m()b6cq(3bmhH7!wF7+5BmREF&%uE*!y8*`&4T*0ZediiHS#g~;O?13H zOL8&57LtrkM8+@>m>!C01}I}bn~dKV;dqYe!ByLt6o=gK7b%ie&D({tsv}67Z?e~p zx-WZk6d2J58%5c3hj;ipfjZ=m!gk>b74^|HiId>|V83d_|Hqo?4YvJJvx~|;YIgOX Lc52L@)vWpivwNcV literal 0 HcmV?d00001 diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php index 00f1ac984a868..8138f7659639b 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php @@ -84,4 +84,18 @@ public function testSerializeNullImpersonateUrl() $this->assertNull($unserializedToken->getOriginatedFromUri()); } + + public function testUnserializeOldToken() + { + /** @var SwitchUserToken $token */ + $token = unserialize(file_get_contents(__DIR__.'/Fixtures/switch-user-token-4.4.txt')); + + self::assertInstanceOf(SwitchUserToken::class, $token); + self::assertInstanceOf(UsernamePasswordToken::class, $token->getOriginalToken()); + self::assertSame('john', $token->getUsername()); + self::assertSame(['foo' => 'bar'], $token->getCredentials()); + self::assertSame('main', $token->getFirewallName()); + self::assertEquals(['ROLE_USER'], $token->getRoleNames()); + self::assertNull($token->getOriginatedFromUri()); + } } From 114b7a543a7bd7ff77a8fe2f0668d517f565eb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Thu, 12 Nov 2020 21:59:34 +0100 Subject: [PATCH 21/46] [DependencyInjection][Translator] Silent deprecation triggered by libxml_disable_entity_loader --- .../Loader/XmlFileLoader.php | 33 ++++++++++++- .../Tests/Resources/TranslationFilesTest.php | 15 ++++++ .../Tests/Resources/TranslationFilesTest.php | 14 ++++++ .../Component/Translation/Util/XliffUtils.php | 49 ++++++++++++++----- .../Tests/Resources/TranslationFilesTest.php | 14 ++++++ 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 19566e9aa7050..2ebdbda090e31 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -634,14 +634,13 @@ public function validateSchema(\DOMDocument $dom) EOF ; - if (\LIBXML_VERSION < 20900) { + if ($this->shouldEnableEntityLoader()) { $disableEntities = libxml_disable_entity_loader(false); $valid = @$dom->schemaValidateSource($source); libxml_disable_entity_loader($disableEntities); } else { $valid = @$dom->schemaValidateSource($source); } - foreach ($tmpfiles as $tmpfile) { @unlink($tmpfile); } @@ -649,6 +648,36 @@ public function validateSchema(\DOMDocument $dom) return $valid; } + private function shouldEnableEntityLoader(): bool + { + // Version prior to 8.0 can be enabled without deprecation + if (\PHP_VERSION_ID < 80000) { + return true; + } + + static $dom, $schema; + if (null === $dom) { + $dom = new \DOMDocument(); + $dom->loadXML(''); + + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $schema = ' + + +'; + file_put_contents($tmpfile, ' + + + +'); + } + + return !@$dom->schemaValidateSource($schema); + } + private function validateAlias(\DOMElement $alias, string $file) { foreach ($alias->attributes as $name => $node) { diff --git a/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php b/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php index 53b2cee448805..5a9669e92b424 100644 --- a/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php +++ b/src/Symfony/Component/Form/Tests/Resources/TranslationFilesTest.php @@ -29,6 +29,21 @@ public function testTranslationFileIsValid($filePath) $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); } + /** + * @dataProvider provideTranslationFiles + * @group Legacy + */ + public function testTranslationFileIsValidWithoutEntityLoader($filePath) + { + $document = new \DOMDocument(); + $document->loadXML(file_get_contents($filePath)); + libxml_disable_entity_loader(true); + + $errors = XliffUtils::validateSchema($document); + + $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); + } + public function provideTranslationFiles() { return array_map( diff --git a/src/Symfony/Component/Security/Core/Tests/Resources/TranslationFilesTest.php b/src/Symfony/Component/Security/Core/Tests/Resources/TranslationFilesTest.php index 2402b0199824f..4255e91d926b8 100644 --- a/src/Symfony/Component/Security/Core/Tests/Resources/TranslationFilesTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Resources/TranslationFilesTest.php @@ -29,6 +29,20 @@ public function testTranslationFileIsValid($filePath) $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); } + /** + * @dataProvider provideTranslationFiles + */ + public function testTranslationFileIsValidWithoutEntityLoader($filePath) + { + $document = new \DOMDocument(); + $document->loadXML(file_get_contents($filePath)); + libxml_disable_entity_loader(true); + + $errors = XliffUtils::validateSchema($document); + + $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); + } + public function provideTranslationFiles() { return array_map( diff --git a/src/Symfony/Component/Translation/Util/XliffUtils.php b/src/Symfony/Component/Translation/Util/XliffUtils.php index a8c05c2244d47..e4373a7d5ba1b 100644 --- a/src/Symfony/Component/Translation/Util/XliffUtils.php +++ b/src/Symfony/Component/Translation/Util/XliffUtils.php @@ -61,21 +61,18 @@ public static function validateSchema(\DOMDocument $dom): array { $xliffVersion = static::getVersionNumber($dom); $internalErrors = libxml_use_internal_errors(true); - if (\LIBXML_VERSION < 20900) { + if ($shouldEnable = self::shouldEnableEntityLoader()) { $disableEntities = libxml_disable_entity_loader(false); } - - $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion)); - if (!$isValid) { - if (\LIBXML_VERSION < 20900) { + try { + $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion)); + if (!$isValid) { + return self::getXmlErrors($internalErrors); + } + } finally { + if ($shouldEnable) { libxml_disable_entity_loader($disableEntities); } - - return self::getXmlErrors($internalErrors); - } - - if (\LIBXML_VERSION < 20900) { - libxml_disable_entity_loader($disableEntities); } $dom->normalizeDocument(); @@ -86,6 +83,36 @@ public static function validateSchema(\DOMDocument $dom): array return []; } + private static function shouldEnableEntityLoader(): bool + { + // Version prior to 8.0 can be enabled without deprecation + if (\PHP_VERSION_ID < 80000) { + return true; + } + + static $dom, $schema; + if (null === $dom) { + $dom = new \DOMDocument(); + $dom->loadXML(''); + + $tmpfile = tempnam(sys_get_temp_dir(), 'symfony'); + register_shutdown_function(static function () use ($tmpfile) { + @unlink($tmpfile); + }); + $schema = ' + + +'; + file_put_contents($tmpfile, ' + + + +'); + } + + return !@$dom->schemaValidateSource($schema); + } + public static function getErrorsAsString(array $xmlErrors): string { $errorsAsString = ''; diff --git a/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php b/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php index 894ae55f10567..6e0620b517563 100644 --- a/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php +++ b/src/Symfony/Component/Validator/Tests/Resources/TranslationFilesTest.php @@ -29,6 +29,20 @@ public function testTranslationFileIsValid($filePath) $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); } + /** + * @dataProvider provideTranslationFiles + */ + public function testTranslationFileIsValidWithoutEntityLoader($filePath) + { + $document = new \DOMDocument(); + $document->loadXML(file_get_contents($filePath)); + libxml_disable_entity_loader(true); + + $errors = XliffUtils::validateSchema($document); + + $this->assertCount(0, $errors, sprintf('"%s" is invalid:%s', $filePath, \PHP_EOL.implode(\PHP_EOL, array_column($errors, 'message')))); + } + public function provideTranslationFiles() { return array_map( From 6aa31a13c72fd98d0bc06e109e90bc80eb262a0f Mon Sep 17 00:00:00 2001 From: Gabi Udrescu Date: Sun, 8 Nov 2020 17:07:20 +0200 Subject: [PATCH 22/46] Add Romanian missing translations --- .../Resources/translations/validators.ro.xlf | 126 +++++++++++++++++- .../Resources/translations/security.ro.xlf | 8 ++ 2 files changed, 131 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Form/Resources/translations/validators.ro.xlf b/src/Symfony/Component/Form/Resources/translations/validators.ro.xlf index 25abab3b6f148..a7dc62b579c6b 100644 --- a/src/Symfony/Component/Form/Resources/translations/validators.ro.xlf +++ b/src/Symfony/Component/Form/Resources/translations/validators.ro.xlf @@ -4,7 +4,7 @@ This form should not contain extra fields. - Aceast formular nu ar trebui să conțină câmpuri suplimentare. + Acest formular nu ar trebui să conțină câmpuri suplimentare. The uploaded file was too large. Please try to upload a smaller file. @@ -12,8 +12,128 @@ The CSRF token is invalid. Please try to resubmit the form. - Token-ul CSRF este invalid. Vă rugăm să trimiteți formularul incă o dată. + Token-ul CSRF este invalid. Vă rugăm să retrimiteți formularul. + + + This value is not a valid HTML5 color. + Această valoare nu este un cod de culoare HTML5 valid. + + + Please enter a valid birthdate. + Vă rugăm să introduceți o dată de naștere validă. + + + The selected choice is invalid. + Valoarea selectată este invalidă. + + + The collection is invalid. + Colecția nu este validă. + + + Please select a valid color. + Vă rugăm să selectați o culoare validă. + + + Please select a valid country. + Vă rugăm să selectați o țară validă. + + + Please select a valid currency. + Vă rugăm să selectați o monedă validă. + + + Please choose a valid date interval. + Vă rugăm să selectați un interval de zile valid. + + + Please enter a valid date and time. + Vă rugăm să introduceți o dată și o oră validă. + + + Please enter a valid date. + Vă rugăm să introduceți o dată validă. + + + Please select a valid file. + Vă rugăm să selectați un fișier valid. + + + The hidden field is invalid. + Câmpul ascuns este invalid. + + + Please enter an integer. + Vă rugăm să introduceți un număr întreg. + + + Please select a valid language. + Vă rugăm să selectați o limbă validă. + + + Please select a valid locale. + Vă rugăm să selectați o setare locală validă. + + + Please enter a valid money amount. + Vă rugăm să introduceți o valoare monetară corectă. + + + Please enter a number. + Vă rugăm să introduceți un număr. + + + The password is invalid. + Parola nu este validă. + + + Please enter a percentage value. + Vă rugăm să introduceți o valoare procentuală. + + + The values do not match. + Valorile nu coincid. + + + Please enter a valid time. + Vă rugăm să introduceți o oră validă. + + + Please select a valid timezone. + Vă rugăm să selectați un fus orar valid. + + + Please enter a valid URL. + Vă rugăm să introduceți un URL valid. + + + Please enter a valid search term. + Vă rugăm să introduceți un termen de căutare valid. + + + Please provide a valid phone number. + Vă rugăm să introduceți un număr de telefon valid. + + + The checkbox has an invalid value. + Bifa nu are o valoare validă. + + + Please enter a valid email address. + Vă rugăm să introduceți o adresă de email validă. + + + Please select a valid option. + Vă rugăm să selectați o opțiune validă. + + + Please select a valid range. + Vă rugăm să selectați un interval valid. + + + Please enter a valid week. + Vă rugăm să introduceți o săptămână validă. - \ No newline at end of file + diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.ro.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.ro.xlf index f35a2bb815878..1462e650e9c4b 100644 --- a/src/Symfony/Component/Security/Core/Resources/translations/security.ro.xlf +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.ro.xlf @@ -62,6 +62,14 @@ Account is locked. Contul este blocat. + + Too many failed login attempts, please try again later. + Prea multe încercări de autentificare eșuate, vă rugăm să încercați mai târziu. + + + Invalid or expired login link. + Link de autentificare invalid sau expirat. + From 0153c44975f89859a6f764384089eb0eb1685aa4 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 27 Nov 2020 09:41:25 +0100 Subject: [PATCH 23/46] Update CHANGELOG for 3.4.47 --- CHANGELOG-3.4.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG-3.4.md b/CHANGELOG-3.4.md index 95a2107528b52..23e1671360b2c 100644 --- a/CHANGELOG-3.4.md +++ b/CHANGELOG-3.4.md @@ -7,6 +7,10 @@ in 3.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v3.4.0...v3.4.1 +* 3.4.47 (2020-11-27) + + * bug #38628 [DoctrineBridge] indexBy could reference to association columns (juanmiguelbesada) + * 3.4.46 (2020-10-28) * bug #38669 [Serializer] fix decoding float XML attributes starting with 0 (Marcin Kruk) From f93df0ef1d6b9206c9ffe2f64ded2bfab982ee65 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 27 Nov 2020 09:41:54 +0100 Subject: [PATCH 24/46] Update CONTRIBUTORS for 3.4.47 --- CONTRIBUTORS.md | 84 +++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 5d3ae522834d7..3bed011d06243 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,9 +12,10 @@ Symfony is the result of the work of many people who made the code better - Robin Chalas (chalas_r) - Christophe Coevoet (stof) - Kévin Dunglas (dunglas) - - Jordi Boggiano (seldaek) - Maxime Steinhausser (ogizanagi) + - Jordi Boggiano (seldaek) - Victor Berchet (victor) + - Alexander M. Turek (derrabus) - Grégoire Pineau (lyrixx) - Ryan Weaver (weaverryan) - Javier Eguiluz (javier.eguiluz) @@ -22,33 +23,32 @@ Symfony is the result of the work of many people who made the code better - Jakub Zalas (jakubzalas) - Johannes S (johannes) - Kris Wallsmith (kriswallsmith) - - Alexander M. Turek (derrabus) - Wouter de Jong (wouterj) - Yonel Ceruto (yonelceruto) - Thomas Calvet (fancyweb) - Hugo Hamon (hhamon) + - Jérémy DERUSSÉ (jderusse) - Abdellatif Ait boudad (aitboudad) - Samuel ROZE (sroze) - Romain Neutron (romain) - Pascal Borreli (pborreli) - - Jérémy DERUSSÉ (jderusse) - Joseph Bielawski (stloyd) - Karma Dordrak (drak) - Jules Pietri (heah) - Lukas Kahwe Smith (lsmith) - Martin Hasoň (hason) - Hamza Amrouche (simperfit) + - Tobias Nyholm (tobias) - Jeremy Mikola (jmikola) - Jean-François Simon (jfsimon) - Benjamin Eberlei (beberlei) - Igor Wiedler (igorw) - - Tobias Nyholm (tobias) - Eriksen Costa (eriksencosta) - Guilhem Niot (energetick) - Sarah Khalil (saro0h) - Jonathan Wage (jwage) - - Lynn van der Berg (kjarli) - Jan Schädlich (jschaedl) + - Lynn van der Berg (kjarli) - Matthias Pigulla (mpdude) - Diego Saint Esteben (dosten) - Pierre du Plessis (pierredup) @@ -56,13 +56,13 @@ Symfony is the result of the work of many people who made the code better - William Durand (couac) - Valentin Udaltsov (vudaltsov) - ornicar + - Grégoire Paris (greg0ire) - Dany Maillard (maidmaid) - Francis Besset (francisbesset) - stealth35 ‏ (stealth35) - Alexander Mols (asm89) - Kevin Bond (kbond) - Konstantin Myakshin (koc) - - Grégoire Paris (greg0ire) - Bulat Shakirzyanov (avalanche123) - Saša Stamenković (umpirsky) - Peter Rehm (rpet) @@ -116,17 +116,17 @@ Symfony is the result of the work of many people who made the code better - Tim Nagel (merk) - Chris Wilkinson (thewilkybarkid) - Brice BERNARD (brikou) + - Alexander Schranz (alexander-schranz) - marc.weistroff - Tomáš Votruba (tomas_votruba) - Peter Kokot (maastermedia) - Lars Strojny (lstrojny) - lenar - Alexander Schwenn (xelaris) + - Massimiliano Arione (garak) - Włodzimierz Gajda (gajdaw) - - Alexander Schranz (alexander-schranz) - Oskar Stark (oskarstark) - Adrien Brault (adrienbrault) - - Massimiliano Arione (garak) - Jacob Dreesen (jdreesen) - Florian Voutzinos (florianv) - Teoh Han Hui (teohhanhui) @@ -139,11 +139,11 @@ Symfony is the result of the work of many people who made the code better - excelwebzone - Gordon Franke (gimler) - Joel Wurtz (brouznouf) + - Antoine Makdessi (amakdessi) - Fabien Pennequin (fabienpennequin) - Julien Falque (julienfalque) - Théo FIDRY (theofidry) - Eric GELOEN (gelo) - - Antoine Makdessi (amakdessi) - Jannik Zschiesche (apfelbox) - jeremyFreeAgent (jeremyfreeagent) - Robert Schönthal (digitalkaoz) @@ -166,6 +166,7 @@ Symfony is the result of the work of many people who made the code better - Guilherme Blanco (guilhermeblanco) - SpacePossum - Pablo Godel (pgodel) + - Andreas Braun - Matthieu Napoli (mnapoli) - Richard van Laak (rvanlaak) - Jérémie Augustin (jaugustin) @@ -175,7 +176,6 @@ Symfony is the result of the work of many people who made the code better - Rafael Dohms (rdohms) - jwdeitch - Ahmed TAILOULOUTE (ahmedtai) - - Andreas Braun - Mikael Pajunen - Arman Hosseini (arman) - Niels Keurentjes (curry684) @@ -295,7 +295,9 @@ Symfony is the result of the work of many people who made the code better - Lorenz Schori - Sébastien Lavoie (lavoiesl) - Dariusz + - Farhad Safarov (safarov) - Michael Babker (mbabker) + - Thomas Lallement (raziel057) - Francois Zaninotto - Colin O'Dell (colinodell) - Alexander Kotynia (olden) @@ -307,21 +309,25 @@ Symfony is the result of the work of many people who made the code better - Danny Berger (dpb587) - zairig imad (zairigimad) - Antonio J. García Lagar (ajgarlag) + - Alessandro Lai (jean85) - Adam Prager (padam87) - Benoît Burnichon (bburnichon) - Maciej Malarz (malarzm) - Roman Marintšenko (inori) - Xavier Montaña Carreras (xmontana) + - Timothée Barray (tyx) - Mickaël Andrieu (mickaelandrieu) - Xavier Perez - Arjen Brouwer (arjenjb) - Katsuhiro OGAWA - Patrick McDougle (patrick-mcdougle) + - Rokas Mikalkėnas (rokasm) - Marc Weistroff (futurecat) - Alif Rachmawadi - Anton Chernikov (anton_ch1989) - Kristen Gilden (kgilden) - Pierre-Yves LEBECQ (pylebecq) + - Benjamin Leveque (benji07) - Jordan Samouh (jordansamouh) - Jakub Kucharovic (jkucharovic) - Loick Piera (pyrech) @@ -337,6 +343,7 @@ Symfony is the result of the work of many people who made the code better - Ray - Chekote - Thomas Adam + - Chi-teck - Jhonny Lidfors (jhonne) - Diego Agulló (aeoris) - jdhoek @@ -348,7 +355,6 @@ Symfony is the result of the work of many people who made the code better - Wodor Wodorski - Timo Bakx (timobakx) - Joe Bennett (kralos) - - Thomas Lallement (raziel057) - soyuka - Giorgio Premi - renanbr @@ -362,7 +368,6 @@ Symfony is the result of the work of many people who made the code better - Alexander Menshchikov (zmey_kk) - Emanuele Panzeri (thepanz) - Kim Hemsø Rasmussen (kimhemsoe) - - Alessandro Lai (jean85) - Langlet Vincent (deviling) - Pascal Luna (skalpa) - Wouter Van Hecke @@ -375,6 +380,7 @@ Symfony is the result of the work of many people who made the code better - Christian Schmidt - Patrick Landolt (scube) - MatTheCat + - Denis Brumann (dbrumann) - Bohan Yang (brentybh) - Vilius Grigaliūnas - David Badura (davidbadura) @@ -383,8 +389,6 @@ Symfony is the result of the work of many people who made the code better - Chris Smith (cs278) - Thomas Bisignani (toma) - Florian Klein (docteurklein) - - Timothée Barray (tyx) - - Benjamin Leveque (benji07) - Manuel Kiessling (manuelkiessling) - Alexey Kopytko (sanmai) - Atsuhiro KUBO (iteman) @@ -404,7 +408,6 @@ Symfony is the result of the work of many people who made the code better - Emmanuel BORGES (eborges78) - Aurelijus Valeiša (aurelijus) - Jan Decavele (jandc) - - Chi-teck - Gustavo Piltcher - Jesse Rushlow (geeshoe) - Stepan Tanasiychuk (stfalcon) @@ -459,19 +462,22 @@ Symfony is the result of the work of many people who made the code better - Thomas Perez (scullwm) - Felix Labrecque - Yaroslav Kiliba + - Ben Hakim - Terje Bråten - Gonzalo Vilaseca (gonzalovilaseca) + - Marco Petersen (ocrampete16) - Markus Fasselt (digilist) - Daniel STANCU - Robbert Klarenbeek (robbertkl) - Eric Masoero (eric-masoero) - Ion Bazan (ionbazan) - - Denis Brumann (dbrumann) + - Vitalii Ekert (comrade42) - JhonnyL - Clara van Miert - Haralan Dobrev (hkdobrev) - hossein zolfi (ocean) - Clément Gautier (clementgautier) + - Jeroen Noten (jeroennoten) - Bastien Jaillot (bastnic) - Dāvis Zālītis (k0d3r1s) - Sanpi @@ -485,7 +491,6 @@ Symfony is the result of the work of many people who made the code better - Dimitri Gritsajuk (ottaviano) - Kirill chEbba Chebunin (chebba) - - - Rokas Mikalkėnas (rokasm) - Greg Thornton (xdissent) - Alex Bowers - Philipp Cordes @@ -551,7 +556,6 @@ Symfony is the result of the work of many people who made the code better - Nate Wiebe (natewiebe13) - Marcin Szepczynski (czepol) - Mohammad Emran Hasan (phpfour) - - Farhad Safarov - Dmitriy Mamontov (mamontovdmitriy) - Jan Schumann - Niklas Fiekas @@ -600,18 +604,20 @@ Symfony is the result of the work of many people who made the code better - Arkadius Stefanski (arkadius) - Tim Goudriaan (codedmonkey) - Jonas Flodén (flojon) + - AnneKir - Soner Sayakci - Tobias Weichart + - Miro Michalicka - Tarmo Leppänen (tarlepp) - Marcin Sikoń (marphi) - M. Vondano - Dominik Zogg (dominik.zogg) - Marek Pietrzak + - Tavo Nieves J - Luc Vieillescazes (iamluc) - Lukáš Holeczy (holicz) - franek (franek) - Raulnet - - Marco Petersen (ocrampete16) - Christian Wahler - Giso Stallenberg (gisostallenberg) - Gintautas Miselis @@ -660,6 +666,7 @@ Symfony is the result of the work of many people who made the code better - Roy Van Ginneken (rvanginneken) - ondrowan - Barry vd. Heuvel (barryvdh) + - Michael Voříšek - Evan S Kaufman (evanskaufman) - mcben - Jérôme Vieilledent (lolautruche) @@ -758,6 +765,7 @@ Symfony is the result of the work of many people who made the code better - Fred Cox - vitaliytv - Philippe Segatori + - fd6130 (fdtvui) - Dalibor Karlović (dkarlovi) - Andrey Sevastianov - Sebastian Blum @@ -803,8 +811,10 @@ Symfony is the result of the work of many people who made the code better - Jérôme Tamarelle (jtamarelle-prismamedia) - Geoffrey Brier (geoffrey-brier) - Alexandre Parent + - Roger Guasch (rogerguasch) - Vladimir Tsykun - Dustin Dobervich (dustin10) + - Luis Tacón (lutacon) - dantleech - Philipp Kolesnikov - Anne-Sophie Bachelard (annesophie) @@ -828,6 +838,7 @@ Symfony is the result of the work of many people who made the code better - Stefan Warman - Tristan Maindron (tmaindron) - Behnoush Norouzali (behnoush) + - Marko H. Tamminen (gzumba) - Wesley Lancel - Xavier Briand (xavierbriand) - Ke WANG (yktd26) @@ -853,8 +864,10 @@ Symfony is the result of the work of many people who made the code better - Michael Devery (mickadoo) - Antoine Corcy - Ahmed Ashraf (ahmedash95) + - Luca Saba (lucasaba) - Sascha Grossenbacher - Szijarto Tamas + - Thomas P - Robin Lehrmann (robinlehrmann) - Catalin Dan - Jaroslav Kuba @@ -918,6 +931,7 @@ Symfony is the result of the work of many people who made the code better - Peter Ward - Davide Borsatto (davide.borsatto) - Julien DIDIER (juliendidier) + - Randy Geraads - Dominik Ritter (dritter) - Andreas Leathley (iquito) - Sebastian Grodzicki (sgrodzicki) @@ -927,7 +941,6 @@ Symfony is the result of the work of many people who made the code better - Baldur Rensch (brensch) - Pierre Rineau - Fritz Michael Gschwantner - - Jeroen Noten (jeroennoten) - Vladyslav Petrovych - Alex Xandra Albert Sim - Carson Full @@ -1045,12 +1058,10 @@ Symfony is the result of the work of many people who made the code better - Daniel Gorgan - Tony Malzhacker - Mathieu MARCHOIS - - Tavo Nieves J - Cyril Quintin (cyqui) - Gerard van Helden (drm) - flack (flack) - Johnny Peck (johnnypeck) - - Michael Voříšek - Stefan Kruppa - Ivan Menshykov - David Romaní @@ -1058,6 +1069,7 @@ Symfony is the result of the work of many people who made the code better - Gustavo Falco (gfalco) - Matt Robinson (inanimatt) - Kristof Van Cauwenbergh (kristofvc) + - Marco Lipparini (liarco) - Peter Bowyer (pbowyer) - Aleksey Podskrebyshev - Calin Mihai Pristavu @@ -1095,6 +1107,7 @@ Symfony is the result of the work of many people who made the code better - Don Pinkster - Maksim Muruev - Emil Einarsson + - Anderson Müller - 243083df - Thibault Duplessis - Rimas Kudelis @@ -1195,7 +1208,9 @@ Symfony is the result of the work of many people who made the code better - Pieter - Michael Tibben - Hallison Boaventura (hallisonboaventura) + - Mas Iting - Billie Thompson + - Albion Bame (abame) - Ganesh Chandrasekaran - Sander Marechal - Franz Wilding (killerpoke) @@ -1216,11 +1231,15 @@ Symfony is the result of the work of many people who made the code better - Nicolas Martin (cocorambo) - Tom Panier (neemzy) - Fred Cox + - luffy1727 - Luciano Mammino (loige) - fabios - Sander Coolen (scoolen) + - Amirreza Shafaat (amirrezashafaat) - Laurent Clouet + - Adoni Pavlakis (adoni) - Nicolas Le Goff (nlegoff) + - Ahmed EBEN HASSINE (famas23) - Ben Oman - Chris de Kok - Eduard Bulava (nonanerz) @@ -1229,6 +1248,7 @@ Symfony is the result of the work of many people who made the code better - Guillaume (guill) - Igor Timoshenko (igor.timoshenko) - Manuele Menozzi + - “teerasak” - Anton Babenko (antonbabenko) - Irmantas Šiupšinskas (irmantas) - Benoit Mallo @@ -1238,6 +1258,7 @@ Symfony is the result of the work of many people who made the code better - pizzaminded - Arnaud PETITPAS (apetitpa) - Ken Stanley + - ivan - Zachary Tong (polyfractal) - linh - Guilherme Augusto Henschel @@ -1279,8 +1300,10 @@ Symfony is the result of the work of many people who made the code better - Danijel Obradović - Pablo Borowicz - Arjan Keeman + - Bruno Rodrigues de Araujo (brunosinister) - Máximo Cuadros (mcuadros) - Lukas Mencl + - Jacek Wilczyński (jacekwilczynski) - tamirvs - gauss - julien.galenski @@ -1342,7 +1365,6 @@ Symfony is the result of the work of many people who made the code better - Martijn Evers - Philipp Fritsche - tarlepp - - Luca Saba (lucasaba) - Benjamin Paap (benjaminpaap) - Claus Due (namelesscoder) - Christian @@ -1449,6 +1471,7 @@ Symfony is the result of the work of many people who made the code better - Makdessi Alex - James Gilliland - fduch (fduch) + - Juan Miguel Besada Vidal (soutlink) - Stuart Fyfe - David de Boer (ddeboer) - Eno Mullaraj (emullaraj) @@ -1677,7 +1700,6 @@ Symfony is the result of the work of many people who made the code better - WedgeSama - Hugo Sales - Felds Liscia - - Randy Geraads - Chihiro Adachi (chihiro-adachi) - Raphaëll Roussel - Tadcka @@ -1693,7 +1715,6 @@ Symfony is the result of the work of many people who made the code better - Emmanuel Vella (emmanuel.vella) - Guillaume BRETOU (guiguiboy) - Carsten Nielsen (phreaknerd) - - Roger Guasch (rogerguasch) - Jay Severson - Benny Born - Emirald Mateli @@ -1727,6 +1748,7 @@ Symfony is the result of the work of many people who made the code better - Michael Dowling (mtdowling) - Karlos Presumido (oneko) - Tony Vermeiren (tony) + - Bart Wach - Jos Elstgeest - Thomas Counsell - BilgeXA @@ -1772,7 +1794,6 @@ Symfony is the result of the work of many people who made the code better - Pablo Ogando Ferreira - Thomas Ploch - Simon Neidhold - - Ben Hakim - Valentin VALCIU - Jeremiah VALERIE - Julien Menth @@ -1826,8 +1847,8 @@ Symfony is the result of the work of many people who made the code better - Antonio Peric-Mazar (antonioperic) - César Suárez (csuarez) - Bjorn Twachtmann (dotbjorn) + - Marek Víger (freezy) - Tobias Genberg (lorceroth) - - Luis Tacón (lutacon) - Nicolas Badey (nico-b) - Shane Preece (shane) - Johannes Goslar @@ -1996,6 +2017,7 @@ Symfony is the result of the work of many people who made the code better - Felix Marezki - Normunds - Luiz “Felds” Liscia + - Johan - Thomas Rothe - Adrien Wilmet - Martin @@ -2095,6 +2117,8 @@ Symfony is the result of the work of many people who made the code better - Ali Tavafi - Trevor Suarez - gedrox + - hugovms + - Viet Pham - Alan Bondarchuk - Pchol - dropfen @@ -2200,7 +2224,6 @@ Symfony is the result of the work of many people who made the code better - Marin Nicolae - Alessandro Loffredo - Ian Phillips - - Marco Lipparini - Haritz - Matthieu Prat - Grummfy @@ -2246,7 +2269,6 @@ Symfony is the result of the work of many people who made the code better - Gyula Szucs - Tomas Liubinas - Alex - - Thomas P - Jan Hort - Klaas Naaijkens - Daniel González Cerviño @@ -2410,8 +2432,10 @@ Symfony is the result of the work of many people who made the code better - Daniel Bannert - Karim Miladi - Michael Genereux + - Wojciech Kania - patrick-mcdougle - Dariusz Czech + - Bruno Baguette - Jack Wright - MrNicodemuz - Anonymous User @@ -2430,7 +2454,6 @@ Symfony is the result of the work of many people who made the code better - n-aleha - Talha Zekeriya Durmuş - Anatol Belski - - Anderson Müller - Şəhriyar İmanov - Alexis BOYER - Kaipi Yann @@ -2485,6 +2508,7 @@ Symfony is the result of the work of many people who made the code better - Alex Nostadt - Michael Squires - Egor Gorbachev + - Fabien Villepinte - Derek Stephen McLean - Norman Soetbeer - zorn From 67f1f1e159e7f83f6b3497847462aae263c6b100 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 27 Nov 2020 09:42:42 +0100 Subject: [PATCH 25/46] Update VERSION for 3.4.47 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 815359e13201b..280401e919033 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -67,12 +67,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private $requestStackSize = 0; private $resetServices = false; - const VERSION = '3.4.47-DEV'; + const VERSION = '3.4.47'; const VERSION_ID = 30447; const MAJOR_VERSION = 3; const MINOR_VERSION = 4; const RELEASE_VERSION = 47; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2020'; const END_OF_LIFE = '11/2021'; From cab0672248f95a01b578eb69cc5ce712dcf6a49f Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Mon, 23 Nov 2020 22:10:01 +0100 Subject: [PATCH 26/46] [Security] Automatically register custom authenticator as entry_point (if supported) --- .../Bundle/SecurityBundle/CHANGELOG.md | 2 + .../Compiler/RegisterEntryPointPass.php | 77 +++++++++++++++++++ .../Factory/AuthenticatorFactoryInterface.php | 2 +- .../Factory/CustomAuthenticatorFactory.php | 2 +- .../Factory/EntryPointFactoryInterface.php | 30 -------- .../Security/Factory/FormLoginFactory.php | 15 ++-- .../Factory/GuardAuthenticationFactory.php | 15 ++-- .../Security/Factory/HttpBasicFactory.php | 19 ++--- .../DependencyInjection/SecurityExtension.php | 22 ++---- .../Security/UserAuthenticator.php | 2 +- .../Bundle/SecurityBundle/SecurityBundle.php | 2 + .../GuardAuthenticationFactoryTest.php | 3 - .../SecurityExtensionTest.php | 22 ++---- .../Bundle/SecurityBundle/composer.json | 2 +- .../GuardBridgeAuthenticator.php | 8 +- .../Authenticator/FormLoginAuthenticator.php | 22 ++++++ 16 files changed, 143 insertions(+), 102 deletions(-) create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php delete mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/EntryPointFactoryInterface.php diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 2fe6c20335385..4e1ccb8d2b9fb 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -8,6 +8,8 @@ CHANGELOG * Added `SortFirewallListenersPass` to make the execution order of firewall listeners configurable by leveraging `Symfony\Component\Security\Http\Firewall\FirewallListenerInterface` * Added ability to use comma separated ip address list for `security.access_control` + * [BC break] Removed `EntryPointFactoryInterface`, authenticators must now implement `AuthenticationEntryPointInterface` if + they require autoregistration of a Security entry point. 5.1.0 ----- diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php new file mode 100644 index 0000000000000..d76e7ecdbcb72 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterEntryPointPass.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; + +/** + * @author Wouter de Jong + */ +class RegisterEntryPointPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasParameter('security.firewalls')) { + return; + } + + $firewalls = $container->getParameter('security.firewalls'); + foreach ($firewalls as $firewallName) { + if (!$container->hasDefinition('security.authenticator.manager.'.$firewallName) || !$container->hasParameter('security.'.$firewallName.'._indexed_authenticators')) { + continue; + } + + $entryPoints = []; + $indexedAuthenticators = $container->getParameter('security.'.$firewallName.'._indexed_authenticators'); + // this is a compile-only parameter, removing it cleans up space and avoids unintended usage + $container->getParameterBag()->remove('security.'.$firewallName.'._indexed_authenticators'); + foreach ($indexedAuthenticators as $key => $authenticatorId) { + if (!$container->has($authenticatorId)) { + continue; + } + + $definition = $container->findDefinition($authenticatorId); + if (is_a($definition->getClass(), AuthenticationEntryPointInterface::class, true)) { + $entryPoints[$key] = $authenticatorId; + } + } + + if (!$entryPoints) { + return; + } + + $config = $container->getDefinition('security.firewall.map.config.'.$firewallName); + $configuredEntryPoint = $config->getArgument(7); + + if (null !== $configuredEntryPoint) { + // allow entry points to be configured by authenticator key (e.g. "http_basic") + $entryPoint = $entryPoints[$configuredEntryPoint] ?? $configuredEntryPoint; + } elseif (1 === \count($entryPoints)) { + $entryPoint = array_shift($entryPoints); + } else { + $entryPointNames = []; + foreach ($entryPoints as $key => $serviceId) { + $entryPointNames[] = is_numeric($key) ? $serviceId : $key; + } + + throw new InvalidConfigurationException(sprintf('Because you have multiple authenticators in firewall "%s", you need to set the "entry_point" key to one of your authenticators ("%s") or a service ID implementing "%s". The "entry_point" determines what should happen (e.g. redirect to "/login") when an anonymous user tries to access a protected page.', $firewallName, implode('", "', $entryPointNames), AuthenticationEntryPointInterface::class)); + } + + $config->replaceArgument(7, $entryPoint); + $container->getDefinition('security.exception_listener.'.$firewallName)->replaceArgument(4, new Reference($entryPoint)); + } + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php index cb65f31fe5efb..a94c988d6308e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AuthenticatorFactoryInterface.php @@ -16,7 +16,7 @@ /** * @author Wouter de Jong * - * @experimental in 5.1 + * @experimental in 5.2 */ interface AuthenticatorFactoryInterface { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php index d9245b0616022..67294b3111d63 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/CustomAuthenticatorFactory.php @@ -19,7 +19,7 @@ * @author Wouter de Jong * * @internal - * @experimental in Symfony 5.1 + * @experimental in 5.2 */ class CustomAuthenticatorFactory implements AuthenticatorFactoryInterface, SecurityFactoryInterface { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/EntryPointFactoryInterface.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/EntryPointFactoryInterface.php deleted file mode 100644 index f352e14755652..0000000000000 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/EntryPointFactoryInterface.php +++ /dev/null @@ -1,30 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory; - -use Symfony\Component\DependencyInjection\ContainerBuilder; - -/** - * @author Wouter de Jong - * - * @experimental in 5.1 - */ -interface EntryPointFactoryInterface -{ - /** - * Register the entry point on the container and returns the service ID. - * - * This does not mean that the entry point is also used. This is managed - * by the "entry_point" firewall setting. - */ - public function registerEntryPoint(ContainerBuilder $container, string $firewallName, array $config): ?string; -} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php index 76dde71424a9c..3f4f6a16909b1 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/FormLoginFactory.php @@ -25,7 +25,7 @@ * * @internal */ -class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryInterface, EntryPointFactoryInterface +class FormLoginFactory extends AbstractFactory implements AuthenticatorFactoryInterface { public function __construct() { @@ -94,12 +94,7 @@ protected function createListener(ContainerBuilder $container, string $id, array protected function createEntryPoint(ContainerBuilder $container, string $id, array $config, ?string $defaultEntryPointId) { - return $this->registerEntryPoint($container, $id, $config); - } - - public function registerEntryPoint(ContainerBuilder $container, string $firewallName, array $config): string - { - $entryPointId = 'security.authentication.form_entry_point.'.$firewallName; + $entryPointId = 'security.authentication.form_entry_point.'.$id; $container ->setDefinition($entryPointId, new ChildDefinition('security.authentication.form_entry_point')) ->addArgument(new Reference('security.http_utils')) @@ -118,13 +113,17 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal $authenticatorId = 'security.authenticator.form_login.'.$firewallName; $options = array_intersect_key($config, $this->options); - $container + $authenticator = $container ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.form_login')) ->replaceArgument(1, new Reference($userProviderId)) ->replaceArgument(2, new Reference($this->createAuthenticationSuccessHandler($container, $firewallName, $config))) ->replaceArgument(3, new Reference($this->createAuthenticationFailureHandler($container, $firewallName, $config))) ->replaceArgument(4, $options); + if ($options['use_forward'] ?? false) { + $authenticator->addMethodCall('setHttpKernel', [new Reference('http_kernel')]); + } + return $authenticatorId; } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php index 51a2b3bb97f54..f60666e9dc772 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php @@ -27,7 +27,7 @@ * * @internal */ -class GuardAuthenticationFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface, EntryPointFactoryInterface +class GuardAuthenticationFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface { public function getPosition() { @@ -102,6 +102,10 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal $userProvider = new Reference($userProviderId); $authenticatorIds = []; + if (isset($config['entry_point'])) { + throw new InvalidConfigurationException('The "security.firewall.'.$firewallName.'.guard.entry_point" option has no effect in the new authenticator system, configure "security.firewall.'.$firewallName.'.entry_point" instead.'); + } + $guardAuthenticatorIds = $config['authenticators']; foreach ($guardAuthenticatorIds as $i => $guardAuthenticatorId) { $container->setDefinition($authenticatorIds[] = 'security.authenticator.guard.'.$firewallName.'.'.$i, new Definition(GuardBridgeAuthenticator::class)) @@ -114,15 +118,6 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal return $authenticatorIds; } - public function registerEntryPoint(ContainerBuilder $container, string $firewallName, array $config): ?string - { - try { - return $this->determineEntryPoint(null, $config); - } catch (\LogicException $e) { - throw new InvalidConfigurationException(sprintf('Because you have multiple guard authenticators, you need to set the "entry_point" key to one of your authenticators (%s).', implode(', ', $config['authenticators']))); - } - } - private function determineEntryPoint(?string $defaultEntryPointId, array $config): string { if ($defaultEntryPointId) { diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php index 718076ced42f7..784878b9ed775 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/HttpBasicFactory.php @@ -23,7 +23,7 @@ * * @internal */ -class HttpBasicFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface, EntryPointFactoryInterface +class HttpBasicFactory implements SecurityFactoryInterface, AuthenticatorFactoryInterface { public function create(ContainerBuilder $container, string $id, array $config, string $userProvider, ?string $defaultEntryPoint) { @@ -38,7 +38,11 @@ public function create(ContainerBuilder $container, string $id, array $config, s // entry point $entryPointId = $defaultEntryPoint; if (null === $entryPointId) { - $entryPointId = $this->registerEntryPoint($container, $id, $config); + $entryPointId = 'security.authentication.basic_entry_point.'.$id; + $container + ->setDefinition($entryPointId, new ChildDefinition('security.authentication.basic_entry_point')) + ->addArgument($config['realm']) + ; } // listener @@ -81,15 +85,4 @@ public function addConfiguration(NodeDefinition $node) ->end() ; } - - public function registerEntryPoint(ContainerBuilder $container, string $firewallName, array $config): string - { - $entryPointId = 'security.authentication.basic_entry_point.'.$firewallName; - $container - ->setDefinition($entryPointId, new ChildDefinition('security.authentication.basic_entry_point')) - ->addArgument($config['realm']) - ; - - return $entryPointId; - } } diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 4d0232d9217b2..3c84bf34072d0 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -12,7 +12,6 @@ namespace Symfony\Bundle\SecurityBundle\DependencyInjection; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AuthenticatorFactoryInterface; -use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\EntryPointFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FirewallListenerFactoryInterface; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface; @@ -40,7 +39,6 @@ use Symfony\Component\Security\Core\User\ChainUserProvider; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Controller\UserValueResolver; -use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; use Symfony\Component\Security\Http\Event\CheckPassportEvent; use Twig\Extension\AbstractExtension; @@ -567,15 +565,13 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri $authenticators = $factory->createAuthenticator($container, $id, $firewall[$key], $userProvider); if (\is_array($authenticators)) { - foreach ($authenticators as $i => $authenticator) { + foreach ($authenticators as $authenticator) { $authenticationProviders[] = $authenticator; + $entryPoints[] = $authenticator; } } else { $authenticationProviders[] = $authenticators; - } - - if ($factory instanceof EntryPointFactoryInterface && ($entryPoint = $factory->registerEntryPoint($container, $id, $firewall[$key]))) { - $entryPoints[$key] = $entryPoint; + $entryPoints[$key] = $authenticators; } } else { [$provider, $listenerId, $defaultEntryPoint] = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint); @@ -596,16 +592,8 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri } } - if ($entryPoints) { - // we can be sure the authenticator system is enabled - if (null !== $defaultEntryPoint) { - $defaultEntryPoint = $entryPoints[$defaultEntryPoint] ?? $defaultEntryPoint; - } elseif (1 === \count($entryPoints)) { - $defaultEntryPoint = current($entryPoints); - } else { - throw new InvalidConfigurationException(sprintf('Because you have multiple authenticators in firewall "%s", you need to set the "entry_point" key to one of your authenticators (%s) or a service ID implementing "%s". The "entry_point" determines what should happen (e.g. redirect to "/login") when an anonymous user tries to access a protected page.', $id, implode(', ', $entryPoints), AuthenticationEntryPointInterface::class)); - } - } + // the actual entry point is configured by the RegisterEntryPointPass + $container->setParameter('security.'.$id.'._indexed_authenticators', $entryPoints); if (false === $hasListeners && !$this->authenticatorManagerEnabled) { throw new InvalidConfigurationException(sprintf('No authentication listener registered for firewall "%s".', $id)); diff --git a/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php b/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php index ab2dded7989a0..ba4af81acd603 100644 --- a/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php +++ b/src/Symfony/Bundle/SecurityBundle/Security/UserAuthenticator.php @@ -27,7 +27,7 @@ * @author Wouter de Jong * * @final - * @experimental in Symfony 5.1 + * @experimental in 5.2 */ class UserAuthenticator implements UserAuthenticatorInterface { diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 27ee29df549b9..409d116228622 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -15,6 +15,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterEntryPointPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterGlobalSecurityEventListenersPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterLdapLocatorPass; use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass; @@ -77,6 +78,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new RegisterCsrfFeaturesPass()); $container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200); $container->addCompilerPass(new RegisterLdapLocatorPass()); + $container->addCompilerPass(new RegisterEntryPointPass(), PassConfig::TYPE_BEFORE_REMOVING); // must be registered after RegisterListenersPass (in the FrameworkBundle) $container->addCompilerPass(new RegisterGlobalSecurityEventListenersPass(), PassConfig::TYPE_BEFORE_REMOVING, -200); // execute after ResolveChildDefinitionsPass optimization pass, to ensure class names are set diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php index 7d6f5f6591278..8dd7617d6d03a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php @@ -178,9 +178,6 @@ public function testAuthenticatorSystemCreate() $authenticators = $factory->createAuthenticator($container, $firewallName, $config, $userProviderId); $this->assertEquals('security.authenticator.guard.my_firewall.0', $authenticators[0]); - $entryPointId = $factory->registerEntryPoint($container, $firewallName, $config, null); - $this->assertEquals('authenticator123', $entryPointId); - $authenticatorDefinition = $container->getDefinition('security.authenticator.guard.my_firewall.0'); $this->assertEquals(GuardBridgeAuthenticator::class, $authenticatorDefinition->getClass()); $this->assertEquals('authenticator123', (string) $authenticatorDefinition->getArgument(0)); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php index 616abed29e65b..61e6287768cf6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php @@ -438,6 +438,7 @@ public function testSwitchUserWithSeveralDefinedProvidersButNoFirewallRootProvid public function testAuthenticatorManagerEnabledEntryPoint(array $firewall, $entryPointId) { $container = $this->getRawContainer(); + $container->register(AppCustomAuthenticator::class); $container->loadFromExtension('security', [ 'enable_authenticator_manager' => true, 'providers' => [ @@ -458,9 +459,9 @@ public function testAuthenticatorManagerEnabledEntryPoint(array $firewall, $entr public function provideEntryPointFirewalls() { // only one entry point available - yield [['http_basic' => true], 'security.authentication.basic_entry_point.main']; + yield [['http_basic' => true], 'security.authenticator.http_basic.main']; // explicitly configured by authenticator key - yield [['form_login' => true, 'http_basic' => true, 'entry_point' => 'form_login'], 'security.authentication.form_entry_point.main']; + yield [['form_login' => true, 'http_basic' => true, 'entry_point' => 'form_login'], 'security.authenticator.form_login.main']; // explicitly configured another service yield [['form_login' => true, 'entry_point' => EntryPointStub::class], EntryPointStub::class]; // no entry point required @@ -469,14 +470,7 @@ public function provideEntryPointFirewalls() // only one guard authenticator entry point available yield [[ 'guard' => ['authenticators' => [AppCustomAuthenticator::class]], - ], AppCustomAuthenticator::class]; - // explicitly configured guard authenticator entry point - yield [[ - 'guard' => [ - 'authenticators' => [AppCustomAuthenticator::class, NullAuthenticator::class], - 'entry_point' => NullAuthenticator::class, - ], - ], NullAuthenticator::class]; + ], 'security.authenticator.guard.main.0']; } /** @@ -507,12 +501,7 @@ public function provideEntryPointRequiredData() // more than one entry point available and not explicitly set yield [ ['http_basic' => true, 'form_login' => true], - '/^Because you have multiple authenticators in firewall "main", you need to set the "entry_point" key to one of your authenticators/', - ]; - // more than one guard entry point available and not explicitly set - yield [ - ['guard' => ['authenticators' => [AppCustomAuthenticator::class, NullAuthenticator::class]]], - '/^Because you have multiple guard authenticators, you need to set the "entry_point" key to one of your authenticators/', + '/Because you have multiple authenticators in firewall "main", you need to set the "entry_point" key to one of your authenticators \("form_login", "http_basic"\) or a service ID implementing/', ]; } @@ -537,6 +526,7 @@ public function testAlwaysAuthenticateBeforeGrantingCannotBeTrueWithAuthenticato public function testConfigureCustomAuthenticator(array $firewall, array $expectedAuthenticators) { $container = $this->getRawContainer(); + $container->register(TestAuthenticator::class); $container->loadFromExtension('security', [ 'enable_authenticator_manager' => true, 'providers' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index ba7d29010d62e..852643c7881e7 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -25,7 +25,7 @@ "symfony/polyfill-php80": "^1.15", "symfony/security-core": "^5.2", "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-guard": "^5.1", + "symfony/security-guard": "^5.2", "symfony/security-http": "^5.2" }, "require-dev": { diff --git a/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php b/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php index 6d51428c99e33..1d7cec6c6ecfa 100644 --- a/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php @@ -29,6 +29,7 @@ use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; +use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** * This authenticator is used to bridge Guard authenticators with @@ -38,7 +39,7 @@ * * @internal */ -class GuardBridgeAuthenticator implements InteractiveAuthenticatorInterface +class GuardBridgeAuthenticator implements InteractiveAuthenticatorInterface, AuthenticationEntryPointInterface { private $guard; private $userProvider; @@ -49,6 +50,11 @@ public function __construct(GuardAuthenticatorInterface $guard, UserProviderInte $this->userProvider = $userProvider; } + public function start(Request $request, AuthenticationException $authException = null) + { + return $this->guard->start($request, $authException); + } + public function supports(Request $request): ?bool { return $this->guard->supports($request); diff --git a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php index 6e22839497151..246e4894b1abc 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -49,6 +50,7 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator private $successHandler; private $failureHandler; private $options; + private $httpKernel; public function __construct(HttpUtils $httpUtils, UserProviderInterface $userProvider, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options) { @@ -146,4 +148,24 @@ private function getCredentials(Request $request): array return $credentials; } + + public function setHttpKernel(HttpKernelInterface $httpKernel): void + { + $this->httpKernel = $httpKernel; + } + + public function start(Request $request, AuthenticationException $authException = null): Response + { + if (!$this->options['use_forward']) { + return parent::start($request, $authException); + } + + $subRequest = $this->httpUtils->createRequest($request, $this->options['login_path']); + $response = $this->httpKernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST); + if (200 === $response->getStatusCode()) { + $response->setStatusCode(401); + } + + return $response; + } } From 4857be89d37ef628ce8b7e8b7e42a7fa1cd64aed Mon Sep 17 00:00:00 2001 From: Guilherme Augusto Henschel Date: Fri, 27 Nov 2020 09:41:21 -0300 Subject: [PATCH 27/46] fix: resolving pt translation issues --- .../Resources/translations/validators.pt.xlf | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf index f140e1a45c00e..0244ee4f398ba 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.pt.xlf @@ -128,7 +128,7 @@ This value should be a valid number. - Este valor deveria de ser um número válido. + Este valor deveria ser um número válido. This file is not a valid image. @@ -176,15 +176,15 @@ This value should be the user's current password. - Este valor deveria de ser a senha atual do usuário. + Este valor deveria ser a senha atual do usuário. This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters. - Este valor deve possuir exatamente {{ limit }} carateres. + Este valor deve possuir exatamente {{ limit }} caracteres. The file was only partially uploaded. - Só foi enviado uma parte do arquivo. + Só foi enviada uma parte do arquivo. No file was uploaded. @@ -192,7 +192,7 @@ No temporary folder was configured in php.ini. - Não existe nenhuma pasta temporária configurada no arquivo do php.ini. + Não existe uma pasta temporária configurada no arquivo php.ini. Cannot write temporary file to disk. @@ -292,11 +292,11 @@ The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed. - A imagem está em orientação de paisagem com ({{ width }}x{{ height }}px). Imagens orientadas em paisagem não são permitidas. + A imagem está em orientação de paisagem ({{ width }}x{{ height }}px). Imagens orientadas em paisagem não são permitidas. The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed. - A imagem está em orientação de retrato com ({{ width }}x{{ height }}px). Imagens orientadas em retrato não são permitidas. + A imagem está em orientação de retrato ({{ width }}x{{ height }}px). Imagens orientadas em retrato não são permitidas. An empty file is not allowed. From ce046fd120dc40efacfad5845b7f9c769667c0a8 Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Fri, 27 Nov 2020 08:48:41 -0500 Subject: [PATCH 28/46] adjust KernelBrowser::getProfile() typehint --- src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 6e44657cf8181..40381e34aa310 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -65,7 +65,7 @@ public function getKernel() /** * Gets the profile associated with the current Response. * - * @return HttpProfile|false A Profile instance + * @return HttpProfile|false|null A Profile instance */ public function getProfile() { From 5f2bb905e7730cc3bc765a7cd3254e943294f25d Mon Sep 17 00:00:00 2001 From: Kevin Bond Date: Fri, 27 Nov 2020 09:11:20 -0500 Subject: [PATCH 29/46] adjust Client::getProfile() typehint --- src/Symfony/Bundle/FrameworkBundle/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Client.php b/src/Symfony/Bundle/FrameworkBundle/Client.php index 6450a4ec0022a..ee95a9ae7b793 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Client.php +++ b/src/Symfony/Bundle/FrameworkBundle/Client.php @@ -62,7 +62,7 @@ public function getKernel() /** * Gets the profile associated with the current Response. * - * @return HttpProfile|false A Profile instance + * @return HttpProfile|false|null A Profile instance */ public function getProfile() { From c3d00dbeba186fb21624166e66d3bfd27de2ee4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Jusi=C4=99ga?= Date: Fri, 27 Nov 2020 15:15:14 +0100 Subject: [PATCH 30/46] Allow symfony/semaphore on PHP8 --- src/Symfony/Component/Semaphore/composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Semaphore/composer.json b/src/Symfony/Component/Semaphore/composer.json index 5d38d07acaa9b..e540ee0807af2 100644 --- a/src/Symfony/Component/Semaphore/composer.json +++ b/src/Symfony/Component/Semaphore/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "psr/log": "~1.0" }, "require-dev": { From 54af139a4ea2eb5ac0792fc0a355e5ecc9047642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Fri, 20 Nov 2020 19:00:05 +0100 Subject: [PATCH 31/46] [DependencyInjection] Fix circular in DI with lazy + byContruct loop --- .../Compiler/AnalyzeServiceReferencesPass.php | 9 +- .../DependencyInjection/Dumper/PhpDumper.php | 2 +- .../Tests/ContainerBuilderTest.php | 6 + .../Tests/Dumper/PhpDumperTest.php | 6 + .../containers/container_almost_circular.php | 36 ++++++ .../Tests/Fixtures/includes/classes.php | 12 ++ .../php/services_almost_circular_private.php | 77 ++++++++++- .../php/services_almost_circular_public.php | 120 ++++++++++++++++-- 8 files changed, 252 insertions(+), 16 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php index 1cd5ab93eebd2..92e4acacfba2f 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AnalyzeServiceReferencesPass.php @@ -12,6 +12,7 @@ namespace Symfony\Component\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; @@ -35,6 +36,7 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe private $hasProxyDumper; private $lazy; private $byConstructor; + private $byFactory; private $definitions; private $aliases; @@ -66,6 +68,7 @@ public function process(ContainerBuilder $container) $this->graph->clear(); $this->lazy = false; $this->byConstructor = false; + $this->byFactory = false; $this->definitions = $container->getDefinitions(); $this->aliases = $container->getAliases(); @@ -87,7 +90,7 @@ protected function processValue($value, $isRoot = false) $inExpression = $this->inExpression(); if ($value instanceof ArgumentInterface) { - $this->lazy = true; + $this->lazy = !$this->byFactory || !$value instanceof IteratorArgument; parent::processValue($value->getValues()); $this->lazy = $lazy; @@ -137,7 +140,11 @@ protected function processValue($value, $isRoot = false) $byConstructor = $this->byConstructor; $this->byConstructor = $isRoot || $byConstructor; + + $byFactory = $this->byFactory; + $this->byFactory = true; $this->processValue($value->getFactory()); + $this->byFactory = $byFactory; $this->processValue($value->getArguments()); $properties = $value->getProperties(); diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index a9c46edd66efe..a57ce41c59744 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -420,7 +420,7 @@ private function collectCircularReferences(string $sourceId, array $edges, array foreach ($edges as $edge) { $node = $edge->getDestNode(); $id = $node->getId(); - if (!($definition = $node->getValue()) instanceof Definition || $sourceId === $id || ($edge->isLazy() && ($this->proxyDumper ?? $this->getProxyDumper())->isProxyCandidate($definition)) || $edge->isWeak()) { + if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isLazy() || $edge->isWeak()) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index dc462a0ee5853..b69e875a7e452 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1374,12 +1374,18 @@ public function testAlmostCircular($visibility) $container = include __DIR__.'/Fixtures/containers/container_almost_circular.php'; $container->compile(); + $entityManager = $container->get('doctrine.entity_manager'); + $this->assertEquals(new \stdClass(), $entityManager); + $pA = $container->get('pA'); $this->assertEquals(new \stdClass(), $pA); $logger = $container->get('monolog.logger'); $this->assertEquals(new \stdClass(), $logger->handler); + $logger_inline = $container->get('monolog_inline.logger'); + $this->assertEquals(new \stdClass(), $logger_inline->handler); + $foo = $container->get('foo'); $this->assertSame($foo, $foo->bar->foobar->foo); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index 0e5e0f5a716b8..9f17269846cf8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -1054,12 +1054,18 @@ public function testAlmostCircular($visibility) $container = new $container(); + $entityManager = $container->get('doctrine.entity_manager'); + $this->assertEquals(new \stdClass(), $entityManager); + $pA = $container->get('pA'); $this->assertEquals(new \stdClass(), $pA); $logger = $container->get('monolog.logger'); $this->assertEquals(new \stdClass(), $logger->handler); + $logger_inline = $container->get('monolog_inline.logger'); + $this->assertEquals(new \stdClass(), $logger_inline->handler); + $foo = $container->get('foo'); $this->assertSame($foo, $foo->bar->foobar->foo); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php index 6a0b2da766cdd..8dd05316969f2 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_almost_circular.php @@ -1,5 +1,6 @@ register('doctrine.config', 'stdClass')->setPublic(false) + ->setProperty('resolver', new Reference('doctrine.entity_listener_resolver')) + ->setProperty('flag', 'ok'); + +$container->register('doctrine.entity_manager', 'stdClass')->setPublic(true) + ->setFactory([FactoryChecker::class, 'create']) + ->addArgument(new Reference('doctrine.config')); +$container->register('doctrine.entity_listener_resolver', 'stdClass')->setPublic($public) + ->addArgument(new IteratorArgument([new Reference('doctrine.listener')])); +$container->register('doctrine.listener', 'stdClass')->setPublic($public) + ->addArgument(new Reference('doctrine.entity_manager')); + // multiple path detection $container->register('pA', 'stdClass')->setPublic(true) @@ -42,6 +57,27 @@ $container->register('monolog.logger_2', 'stdClass')->setPublic($public) ->setProperty('handler', new Reference('mailer.transport')); +// monolog-like + handler that require monolog with inlined factory + +$container->register('monolog_inline.logger', 'stdClass')->setPublic(true) + ->setProperty('handler', new Reference('mailer_inline.mailer')); + +$container->register('mailer_inline.mailer', 'stdClass')->setPublic(false) + ->addArgument( + (new Definition('stdClass')) + ->setFactory([new Reference('mailer_inline.transport_factory'), 'create']) + ); + +$container->register('mailer_inline.transport_factory', FactoryCircular::class)->setPublic($public) + ->addArgument(new TaggedIteratorArgument('mailer_inline.transport')); + +$container->register('mailer_inline.transport_factory.amazon', 'stdClass')->setPublic($public) + ->addArgument(new Reference('monolog_inline.logger_2')) + ->addTag('mailer.transport'); + +$container->register('monolog_inline.logger_2', 'stdClass')->setPublic($public) + ->setProperty('handler', new Reference('mailer_inline.mailer')); + // same visibility for deps $container->register('foo', FooCircular::class)->setPublic(true) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php index 46efa450acfff..dcac28effc4ee 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php @@ -128,6 +128,18 @@ public function create() } } +class FactoryChecker +{ + public static function create($config) + { + if (!isset($config->flag)) { + throw new \LogicException('The injected config must contain a "flag" property.'); + } + + return new stdClass(); + } +} + class FoobarCircular { public function __construct(FooCircular $foo) diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 0ef3627e69eaf..40c86fb88fb6a 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -28,6 +28,7 @@ public function __construct() 'baz6' => 'getBaz6Service', 'connection' => 'getConnectionService', 'connection2' => 'getConnection2Service', + 'doctrine.entity_manager' => 'getDoctrine_EntityManagerService', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', 'foo5' => 'getFoo5Service', @@ -40,6 +41,7 @@ public function __construct() 'manager2' => 'getManager2Service', 'manager3' => 'getManager3Service', 'monolog.logger' => 'getMonolog_LoggerService', + 'monolog_inline.logger' => 'getMonologInline_LoggerService', 'pA' => 'getPAService', 'root' => 'getRootService', 'subscriber' => 'getSubscriberService', @@ -72,6 +74,9 @@ public function getRemovedIds(): array 'connection4' => true, 'dispatcher' => true, 'dispatcher2' => true, + 'doctrine.config' => true, + 'doctrine.entity_listener_resolver' => true, + 'doctrine.listener' => true, 'foo4' => true, 'foobar' => true, 'foobar2' => true, @@ -85,8 +90,12 @@ public function getRemovedIds(): array 'mailer.transport' => true, 'mailer.transport_factory' => true, 'mailer.transport_factory.amazon' => true, + 'mailer_inline.mailer' => true, + 'mailer_inline.transport_factory' => true, + 'mailer_inline.transport_factory.amazon' => true, 'manager4' => true, 'monolog.logger_2' => true, + 'monolog_inline.logger_2' => true, 'multiuse1' => true, 'pB' => true, 'pC' => true, @@ -185,6 +194,22 @@ protected function getConnection2Service() return $instance; } + /** + * Gets the public 'doctrine.entity_manager' shared service. + * + * @return \stdClass + */ + protected function getDoctrine_EntityManagerService() + { + $a = new \stdClass(); + $a->resolver = new \stdClass(new RewindableGenerator(function () { + yield 0 => ($this->privates['doctrine.listener'] ?? $this->getDoctrine_ListenerService()); + }, 1)); + $a->flag = 'ok'; + + return $this->services['doctrine.entity_manager'] = \FactoryChecker::create($a); + } + /** * Gets the public 'foo' shared service. * @@ -378,6 +403,20 @@ protected function getMonolog_LoggerService() return $instance; } + /** + * Gets the public 'monolog_inline.logger' shared service. + * + * @return \stdClass + */ + protected function getMonologInline_LoggerService() + { + $this->services['monolog_inline.logger'] = $instance = new \stdClass(); + + $instance->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + + return $instance; + } + /** * Gets the public 'pA' shared service. * @@ -448,6 +487,16 @@ protected function getBar6Service() return $this->privates['bar6'] = new \stdClass($a); } + /** + * Gets the private 'doctrine.listener' shared service. + * + * @return \stdClass + */ + protected function getDoctrine_ListenerService() + { + return $this->privates['doctrine.listener'] = new \stdClass(($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService())); + } + /** * Gets the private 'level5' shared service. * @@ -473,7 +522,8 @@ protected function getMailer_TransportService() { return $this->privates['mailer.transport'] = (new \FactoryCircular(new RewindableGenerator(function () { yield 0 => ($this->privates['mailer.transport_factory.amazon'] ?? $this->getMailer_TransportFactory_AmazonService()); - }, 1)))->create(); + yield 1 => $this->getMailerInline_TransportFactory_AmazonService(); + }, 2)))->create(); } /** @@ -492,6 +542,31 @@ protected function getMailer_TransportFactory_AmazonService() return $instance; } + /** + * Gets the private 'mailer_inline.mailer' shared service. + * + * @return \stdClass + */ + protected function getMailerInline_MailerService() + { + return $this->privates['mailer_inline.mailer'] = new \stdClass((new \FactoryCircular(new RewindableGenerator(function () { + return new \EmptyIterator(); + }, 0)))->create()); + } + + /** + * Gets the private 'mailer_inline.transport_factory.amazon' shared service. + * + * @return \stdClass + */ + protected function getMailerInline_TransportFactory_AmazonService() + { + $a = new \stdClass(); + $a->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + + return new \stdClass($a); + } + /** * Gets the private 'manager4' shared service. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index ddc1f59a269b9..86efcab216516 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -33,6 +33,9 @@ public function __construct() 'connection4' => 'getConnection4Service', 'dispatcher' => 'getDispatcherService', 'dispatcher2' => 'getDispatcher2Service', + 'doctrine.entity_listener_resolver' => 'getDoctrine_EntityListenerResolverService', + 'doctrine.entity_manager' => 'getDoctrine_EntityManagerService', + 'doctrine.listener' => 'getDoctrine_ListenerService', 'foo' => 'getFooService', 'foo2' => 'getFoo2Service', 'foo4' => 'getFoo4Service', @@ -48,11 +51,15 @@ public function __construct() 'mailer.transport' => 'getMailer_TransportService', 'mailer.transport_factory' => 'getMailer_TransportFactoryService', 'mailer.transport_factory.amazon' => 'getMailer_TransportFactory_AmazonService', + 'mailer_inline.transport_factory' => 'getMailerInline_TransportFactoryService', + 'mailer_inline.transport_factory.amazon' => 'getMailerInline_TransportFactory_AmazonService', 'manager' => 'getManagerService', 'manager2' => 'getManager2Service', 'manager3' => 'getManager3Service', 'monolog.logger' => 'getMonolog_LoggerService', 'monolog.logger_2' => 'getMonolog_Logger2Service', + 'monolog_inline.logger' => 'getMonologInline_LoggerService', + 'monolog_inline.logger_2' => 'getMonologInline_Logger2Service', 'pA' => 'getPAService', 'pB' => 'getPBService', 'pC' => 'getPCService', @@ -83,12 +90,14 @@ public function getRemovedIds(): array 'bar6' => true, 'config' => true, 'config2' => true, + 'doctrine.config' => true, 'level2' => true, 'level3' => true, 'level4' => true, 'level5' => true, 'level6' => true, 'logger2' => true, + 'mailer_inline.mailer' => true, 'manager4' => true, 'multiuse1' => true, 'subscriber2' => true, @@ -260,6 +269,42 @@ protected function getDispatcher2Service($lazyLoad = true) return $instance; } + /** + * Gets the public 'doctrine.entity_listener_resolver' shared service. + * + * @return \stdClass + */ + protected function getDoctrine_EntityListenerResolverService() + { + return $this->services['doctrine.entity_listener_resolver'] = new \stdClass(new RewindableGenerator(function () { + yield 0 => ($this->services['doctrine.listener'] ?? $this->getDoctrine_ListenerService()); + }, 1)); + } + + /** + * Gets the public 'doctrine.entity_manager' shared service. + * + * @return \stdClass + */ + protected function getDoctrine_EntityManagerService() + { + $a = new \stdClass(); + $a->resolver = ($this->services['doctrine.entity_listener_resolver'] ?? $this->getDoctrine_EntityListenerResolverService()); + $a->flag = 'ok'; + + return $this->services['doctrine.entity_manager'] = \FactoryChecker::create($a); + } + + /** + * Gets the public 'doctrine.listener' shared service. + * + * @return \stdClass + */ + protected function getDoctrine_ListenerService() + { + return $this->services['doctrine.listener'] = new \stdClass(($this->services['doctrine.entity_manager'] ?? $this->getDoctrine_EntityManagerService())); + } + /** * Gets the public 'foo' shared service. * @@ -449,13 +494,7 @@ protected function getLoggerService() */ protected function getMailer_TransportService() { - $a = ($this->services['mailer.transport_factory'] ?? $this->getMailer_TransportFactoryService()); - - if (isset($this->services['mailer.transport'])) { - return $this->services['mailer.transport']; - } - - return $this->services['mailer.transport'] = $a->create(); + return $this->services['mailer.transport'] = ($this->services['mailer.transport_factory'] ?? $this->getMailer_TransportFactoryService())->create(); } /** @@ -467,7 +506,8 @@ protected function getMailer_TransportFactoryService() { return $this->services['mailer.transport_factory'] = new \FactoryCircular(new RewindableGenerator(function () { yield 0 => ($this->services['mailer.transport_factory.amazon'] ?? $this->getMailer_TransportFactory_AmazonService()); - }, 1)); + yield 1 => ($this->services['mailer_inline.transport_factory.amazon'] ?? $this->getMailerInline_TransportFactory_AmazonService()); + }, 2)); } /** @@ -477,13 +517,29 @@ protected function getMailer_TransportFactoryService() */ protected function getMailer_TransportFactory_AmazonService() { - $a = ($this->services['monolog.logger_2'] ?? $this->getMonolog_Logger2Service()); + return $this->services['mailer.transport_factory.amazon'] = new \stdClass(($this->services['monolog.logger_2'] ?? $this->getMonolog_Logger2Service())); + } - if (isset($this->services['mailer.transport_factory.amazon'])) { - return $this->services['mailer.transport_factory.amazon']; - } + /** + * Gets the public 'mailer_inline.transport_factory' shared service. + * + * @return \FactoryCircular + */ + protected function getMailerInline_TransportFactoryService() + { + return $this->services['mailer_inline.transport_factory'] = new \FactoryCircular(new RewindableGenerator(function () { + return new \EmptyIterator(); + }, 0)); + } - return $this->services['mailer.transport_factory.amazon'] = new \stdClass($a); + /** + * Gets the public 'mailer_inline.transport_factory.amazon' shared service. + * + * @return \stdClass + */ + protected function getMailerInline_TransportFactory_AmazonService() + { + return $this->services['mailer_inline.transport_factory.amazon'] = new \stdClass(($this->services['monolog_inline.logger_2'] ?? $this->getMonologInline_Logger2Service())); } /** @@ -562,6 +618,34 @@ protected function getMonolog_Logger2Service() return $instance; } + /** + * Gets the public 'monolog_inline.logger' shared service. + * + * @return \stdClass + */ + protected function getMonologInline_LoggerService() + { + $this->services['monolog_inline.logger'] = $instance = new \stdClass(); + + $instance->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + + return $instance; + } + + /** + * Gets the public 'monolog_inline.logger_2' shared service. + * + * @return \stdClass + */ + protected function getMonologInline_Logger2Service() + { + $this->services['monolog_inline.logger_2'] = $instance = new \stdClass(); + + $instance->handler = ($this->privates['mailer_inline.mailer'] ?? $this->getMailerInline_MailerService()); + + return $instance; + } + /** * Gets the public 'pA' shared service. * @@ -691,6 +775,16 @@ protected function getLevel5Service() return $instance; } + /** + * Gets the private 'mailer_inline.mailer' shared service. + * + * @return \stdClass + */ + protected function getMailerInline_MailerService() + { + return $this->privates['mailer_inline.mailer'] = new \stdClass(($this->services['mailer_inline.transport_factory'] ?? $this->getMailerInline_TransportFactoryService())->create()); + } + /** * Gets the private 'manager4' shared service. * From 726f3616a816e0b36cb96cdd8045cbe6b08314c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 28 Nov 2020 11:15:42 +0100 Subject: [PATCH 32/46] Fix parameter order --- src/Symfony/Component/Console/Output/TrimmedBufferOutput.php | 4 ++-- src/Symfony/Component/Console/Style/SymfonyStyle.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php index c014d43633cf8..a03aa835f0086 100644 --- a/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php +++ b/src/Symfony/Component/Console/Output/TrimmedBufferOutput.php @@ -25,10 +25,10 @@ class TrimmedBufferOutput extends Output private $buffer = ''; public function __construct( + int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, - OutputFormatterInterface $formatter = null, - int $maxLength + OutputFormatterInterface $formatter = null ) { if ($maxLength <= 0) { throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength)); diff --git a/src/Symfony/Component/Console/Style/SymfonyStyle.php b/src/Symfony/Component/Console/Style/SymfonyStyle.php index a5edac3c4a1f1..0400a26cf2f0d 100644 --- a/src/Symfony/Component/Console/Style/SymfonyStyle.php +++ b/src/Symfony/Component/Console/Style/SymfonyStyle.php @@ -46,7 +46,7 @@ class SymfonyStyle extends OutputStyle public function __construct(InputInterface $input, OutputInterface $output) { $this->input = $input; - $this->bufferedOutput = new TrimmedBufferOutput($output->getVerbosity(), false, clone $output->getFormatter(), \DIRECTORY_SEPARATOR === '\\' ? 4 : 2); + $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter()); // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH; $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); From faa1fd32f9bb1bdd64e2e7172f28477c8d617654 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 28 Nov 2020 14:14:15 +0100 Subject: [PATCH 33/46] [HttpClient] fix binding to network interfaces --- src/Symfony/Component/HttpClient/CurlHttpClient.php | 2 +- .../Component/HttpClient/NativeHttpClient.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/HttpClient/CurlHttpClient.php b/src/Symfony/Component/HttpClient/CurlHttpClient.php index bbcaf69e3bda1..e69761b392ffa 100644 --- a/src/Symfony/Component/HttpClient/CurlHttpClient.php +++ b/src/Symfony/Component/HttpClient/CurlHttpClient.php @@ -269,7 +269,7 @@ public function request(string $method, string $url, array $options = []): Respo if ($options['bindto']) { if (file_exists($options['bindto'])) { $curlopts[\CURLOPT_UNIX_SOCKET_PATH] = $options['bindto']; - } elseif (preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) { + } elseif (0 !== strpos($options['bindto'], 'if!') && preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) { $curlopts[\CURLOPT_INTERFACE] = $matches[1]; $curlopts[\CURLOPT_LOCALPORT] = $matches[2]; } else { diff --git a/src/Symfony/Component/HttpClient/NativeHttpClient.php b/src/Symfony/Component/HttpClient/NativeHttpClient.php index 0710b7a72bba2..18bf8f569bbbf 100644 --- a/src/Symfony/Component/HttpClient/NativeHttpClient.php +++ b/src/Symfony/Component/HttpClient/NativeHttpClient.php @@ -67,8 +67,16 @@ public function request(string $method, string $url, array $options = []): Respo { [$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions); - if ($options['bindto'] && file_exists($options['bindto'])) { - throw new TransportException(__CLASS__.' cannot bind to local Unix sockets, use e.g. CurlHttpClient instead.'); + if ($options['bindto']) { + if (file_exists($options['bindto'])) { + throw new TransportException(__CLASS__.' cannot bind to local Unix sockets, use e.g. CurlHttpClient instead.'); + } + if (0 === strpos($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (0 === strpos($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } } $options['body'] = self::getBodyAsString($options['body']); From 02d16324a5df65aa4c045815ebf1cb3c1bf58851 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 28 Nov 2020 14:29:03 +0100 Subject: [PATCH 34/46] [HttpClient] fix binding to network interfaces --- src/Symfony/Component/HttpClient/AmpHttpClient.php | 11 ++++++++++- .../Component/HttpClient/Internal/AmpClientState.php | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpClient/AmpHttpClient.php b/src/Symfony/Component/HttpClient/AmpHttpClient.php index 0877b8883bef7..b53d636b9cf2f 100644 --- a/src/Symfony/Component/HttpClient/AmpHttpClient.php +++ b/src/Symfony/Component/HttpClient/AmpHttpClient.php @@ -82,6 +82,15 @@ public function request(string $method, string $url, array $options = []): Respo throw new \LogicException('You cannot use the "proxy" option as the "amphp/http-tunnel" package is not installed. Try running "composer require amphp/http-tunnel".'); } + if ($options['bindto']) { + if (0 === strpos($options['bindto'], 'if!')) { + throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.'); + } + if (0 === strpos($options['bindto'], 'host!')) { + $options['bindto'] = substr($options['bindto'], 5); + } + } + if ('' !== $options['body'] && 'POST' === $method && !isset($options['normalized_headers']['content-type'])) { $options['headers'][] = 'Content-Type: application/x-www-form-urlencoded'; } @@ -141,7 +150,7 @@ public function stream($responses, float $timeout = null): ResponseStreamInterfa if ($responses instanceof AmpResponse) { $responses = [$responses]; } elseif (!is_iterable($responses)) { - throw new \TypeError(sprintf('%s() expects parameter 1 to be an iterable of AmpResponse objects, %s given.', __METHOD__, get_debug_type($responses))); + throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AmpResponse objects, "%s" given.', __METHOD__, get_debug_type($responses))); } return new ResponseStream(AmpResponse::stream($responses, $timeout)); diff --git a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php index 61a0c004acfb9..71cb6e9cc3393 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php @@ -119,7 +119,7 @@ public function request(array $options, Request $request, CancellationToken $can private function getClient(array $options): array { $options = [ - 'bindto' => $options['bindto'] ?: '0', + 'bindto' => $options['bindto'] ?: '0:0', 'verify_peer' => $options['verify_peer'], 'capath' => $options['capath'], 'cafile' => $options['cafile'], From 9e83bb7634b07ec58f6328b0d2578042df8c5204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 28 Nov 2020 12:39:59 +0100 Subject: [PATCH 35/46] Fix form EntotyType with uid --- .../Form/ChoiceList/ORMQueryBuilderLoader.php | 15 ++++- .../Doctrine/Tests/Fixtures/UlidIdEntity.php | 28 ++++++++ .../ChoiceList/ORMQueryBuilderLoaderTest.php | 65 +++++++++++++++++++ .../Doctrine/Tests/Types/UlidTypeTest.php | 6 +- .../Doctrine/Tests/Types/UuidTypeTest.php | 6 +- 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php index 0d6a214c92b68..efefb28f85bba 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/ORMQueryBuilderLoader.php @@ -12,6 +12,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type; use Doctrine\ORM\QueryBuilder; /** @@ -74,7 +75,7 @@ public function getEntitiesByIds(string $identifier, array $values) // Guess type $entity = current($qb->getRootEntities()); $metadata = $qb->getEntityManager()->getClassMetadata($entity); - if (\in_array($metadata->getTypeOfField($identifier), ['integer', 'bigint', 'smallint'])) { + if (\in_array($type = $metadata->getTypeOfField($identifier), ['integer', 'bigint', 'smallint'])) { $parameterType = Connection::PARAM_INT_ARRAY; // Filter out non-integer values (e.g. ""). If we don't, some @@ -82,13 +83,23 @@ public function getEntitiesByIds(string $identifier, array $values) $values = array_values(array_filter($values, function ($v) { return (string) $v === (string) (int) $v || ctype_digit($v); })); - } elseif (\in_array($metadata->getTypeOfField($identifier), ['uuid', 'guid'])) { + } elseif (\in_array($type, ['ulid', 'uuid', 'guid'])) { $parameterType = Connection::PARAM_STR_ARRAY; // Like above, but we just filter out empty strings. $values = array_values(array_filter($values, function ($v) { return '' !== (string) $v; })); + + // Convert values into right type + if (Type::hasType($type)) { + $doctrineType = Type::getType($type); + $platform = $qb->getEntityManager()->getConnection()->getDatabasePlatform(); + foreach ($values as &$value) { + $value = $doctrineType->convertToDatabaseValue($value, $platform); + } + unset($value); + } } else { $parameterType = Connection::PARAM_STR_ARRAY; } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php new file mode 100644 index 0000000000000..3ee909fe4bfc5 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UlidIdEntity.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +use Doctrine\ORM\Mapping\Column; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\Id; + +/** @Entity */ +class UlidIdEntity +{ + /** @Id @Column(type="ulid") */ + protected $id; + + public function __construct($id) + { + $this->id = $id; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php index d846df62c8da9..1702532aa699e 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Form/ChoiceList/ORMQueryBuilderLoaderTest.php @@ -12,13 +12,25 @@ namespace Symfony\Bridge\Doctrine\Tests\Form\ChoiceList; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\GuidType; +use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Version; use PHPUnit\Framework\TestCase; use Symfony\Bridge\Doctrine\Form\ChoiceList\ORMQueryBuilderLoader; use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper; +use Symfony\Bridge\Doctrine\Types\UlidType; +use Symfony\Bridge\Doctrine\Types\UuidType; +use Symfony\Component\Uid\Uuid; class ORMQueryBuilderLoaderTest extends TestCase { + protected function tearDown(): void + { + if (Type::hasType('uuid')) { + Type::overrideType('uuid', GuidType::class); + } + } + public function testIdentifierTypeIsStringArray() { $this->checkIdentifierType('Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity', Connection::PARAM_STR_ARRAY); @@ -131,6 +143,51 @@ public function testFilterEmptyUuids($entityClass) $loader->getEntitiesByIds('id', ['71c5fd46-3f16-4abb-bad7-90ac1e654a2d', '', 'b98e8e11-2897-44df-ad24-d2627eb7f499']); } + /** + * @dataProvider provideUidEntityClasses + */ + public function testFilterUid($entityClass) + { + if (Type::hasType('uuid')) { + Type::overrideType('uuid', UuidType::class); + } else { + Type::addType('uuid', UuidType::class); + } + if (!Type::hasType('ulid')) { + Type::addType('ulid', UlidType::class); + } + + $em = DoctrineTestHelper::createTestEntityManager(); + + $query = $this->getMockBuilder('QueryMock') + ->setMethods(['setParameter', 'getResult', 'getSql', '_doExecute']) + ->getMock(); + + $query + ->method('getResult') + ->willReturn([]); + + $query->expects($this->once()) + ->method('setParameter') + ->with('ORMQueryBuilderLoader_getEntitiesByIds_id', [Uuid::fromString('71c5fd46-3f16-4abb-bad7-90ac1e654a2d')->toBinary(), Uuid::fromString('b98e8e11-2897-44df-ad24-d2627eb7f499')->toBinary()], Connection::PARAM_STR_ARRAY) + ->willReturn($query); + + $qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder') + ->setConstructorArgs([$em]) + ->setMethods(['getQuery']) + ->getMock(); + + $qb->expects($this->once()) + ->method('getQuery') + ->willReturn($query); + + $qb->select('e') + ->from($entityClass, 'e'); + + $loader = new ORMQueryBuilderLoader($qb); + $loader->getEntitiesByIds('id', ['71c5fd46-3f16-4abb-bad7-90ac1e654a2d', '', 'b98e8e11-2897-44df-ad24-d2627eb7f499']); + } + public function testEmbeddedIdentifierName() { if (Version::compare('2.5.0') > 0) { @@ -176,4 +233,12 @@ public function provideGuidEntityClasses() ['Symfony\Bridge\Doctrine\Tests\Fixtures\UuidIdEntity'], ]; } + + public function provideUidEntityClasses() + { + return [ + ['Symfony\Bridge\Doctrine\Tests\Fixtures\UuidIdEntity'], + ['Symfony\Bridge\Doctrine\Tests\Fixtures\UlidIdEntity'], + ]; + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php index 36aace3ed843d..fde2341bc9ebe 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UlidTypeTest.php @@ -31,7 +31,11 @@ final class UlidTypeTest extends TestCase public static function setUpBeforeClass(): void { - Type::addType('ulid', UlidType::class); + if (Type::hasType('ulid')) { + Type::overrideType('ulid', UlidType::class); + } else { + Type::addType('ulid', UlidType::class); + } } protected function setUp(): void diff --git a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php index 45e2ee0d5dc71..d6bf714627a1d 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Types/UuidTypeTest.php @@ -31,7 +31,11 @@ final class UuidTypeTest extends TestCase public static function setUpBeforeClass(): void { - Type::addType('uuid', UuidType::class); + if (Type::hasType('uuid')) { + Type::overrideType('uuid', UuidType::class); + } else { + Type::addType('uuid', UuidType::class); + } } protected function setUp(): void From 8d512d98196d3cba001313b3dff7746e02c5dcc9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 28 Nov 2020 14:45:11 +0100 Subject: [PATCH 36/46] [HttpClient] partial revert of previous commit --- src/Symfony/Component/HttpClient/Internal/AmpClientState.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php index 71cb6e9cc3393..61a0c004acfb9 100644 --- a/src/Symfony/Component/HttpClient/Internal/AmpClientState.php +++ b/src/Symfony/Component/HttpClient/Internal/AmpClientState.php @@ -119,7 +119,7 @@ public function request(array $options, Request $request, CancellationToken $can private function getClient(array $options): array { $options = [ - 'bindto' => $options['bindto'] ?: '0:0', + 'bindto' => $options['bindto'] ?: '0', 'verify_peer' => $options['verify_peer'], 'capath' => $options['capath'], 'cafile' => $options['cafile'], From 37be09499201804c28a799ad2206b913f857559a Mon Sep 17 00:00:00 2001 From: bill moll Date: Tue, 24 Nov 2020 14:13:56 -0600 Subject: [PATCH 37/46] [Messenger] Fix mssql compatibility for doctrine transport. Add logic for locking row for update when the doctrine dbal connection is sqlsrv. This is a quick and dirty solution, but it prevents the need to rewrite the logic due to doctrine dbal limitations. See issue https://github.com/symfony/symfony/issues/39117 --- .../Messenger/Transport/Doctrine/Connection.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php index 637c7a57ac194..ef41dc5f02fe5 100644 --- a/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php +++ b/src/Symfony/Component/Messenger/Transport/Doctrine/Connection.php @@ -16,6 +16,7 @@ use Doctrine\DBAL\Driver\Result as DriverResult; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Exception\TableNotFoundException; +use Doctrine\DBAL\LockMode; use Doctrine\DBAL\Query\QueryBuilder; use Doctrine\DBAL\Result; use Doctrine\DBAL\Schema\Comparator; @@ -161,9 +162,23 @@ public function get(): ?array ->orderBy('available_at', 'ASC') ->setMaxResults(1); + // Append pessimistic write lock to FROM clause if db platform supports it + $sql = $query->getSQL(); + if (($fromPart = $query->getQueryPart('from')) && + ($table = $fromPart[0]['table'] ?? null) && + ($alias = $fromPart[0]['alias'] ?? null) + ) { + $fromClause = sprintf('%s %s', $table, $alias); + $sql = str_replace( + sprintf('FROM %s WHERE', $fromClause), + sprintf('FROM %s WHERE', $this->driverConnection->getDatabasePlatform()->appendLockHint($fromClause, LockMode::PESSIMISTIC_WRITE)), + $sql + ); + } + // use SELECT ... FOR UPDATE to lock table $stmt = $this->executeQuery( - $query->getSQL().' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(), + $sql.' '.$this->driverConnection->getDatabasePlatform()->getWriteLockSQL(), $query->getParameters(), $query->getParameterTypes() ); From 1694621f21e98d13802963546208c99440feb5e2 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 29 Nov 2020 10:22:51 +0100 Subject: [PATCH 38/46] Update CHANGELOG for 4.4.17 --- CHANGELOG-4.4.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/CHANGELOG-4.4.md b/CHANGELOG-4.4.md index eb669f2baca7b..f110770e17fc7 100644 --- a/CHANGELOG-4.4.md +++ b/CHANGELOG-4.4.md @@ -7,6 +7,48 @@ in 4.4 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.4.0...v4.4.1 +* 4.4.17 (2020-11-29) + + * bug #39166 [Messenger] Fix mssql compatibility for doctrine transport. (bill moll) + * bug #39211 [HttpClient] fix binding to network interfaces (nicolas-grekas) + * bug #39129 [DependencyInjection] Fix circular in DI with lazy + byContruct loop (jderusse) + * bug #39068 [DependencyInjection][Translator] Silent deprecation triggered by libxml_disable_entity_loader (jderusse) + * bug #39119 [Form] prevent duplicated error message for file upload limits (xabbuh) + * bug #39099 [Form] ignore the pattern attribute for textareas (xabbuh) + * bug #39154 [Yaml] fix lexing strings containing escaped quotation characters (xabbuh) + * bug #38597 [PhpUnitBridge] Fix qualification of deprecations triggered by the debug class loader (fancyweb) + * bug #39160 [Console] Use a partial buffer in SymfonyStyle (jderusse) + * bug #39168 [Console] Fix console closing tag (jderusse) + * bug #39155 [VarDumper] fix casting resources turned into objects on PHP 8 (nicolas-grekas) + * bug #39115 [HttpClient] don't fallback to HTTP/1.1 when HTTP/2 streams break (nicolas-grekas) + * bug #33763 [Yaml] fix lexing nested sequences/mappings (xabbuh) + * bug #39083 [Dotenv] Check if method inheritEnvironmentVariables exists (Chi-teck) + * bug #39094 [Ldap] Fix undefined variable $con (derrabus) + * bug #39091 [Config] Recheck glob brace support after GlobResource was serialized (wouterj) + * bug #39092 Fix critical extension when reseting paged control (jderusse) + * bug #38614 [HttpFoundation] Fix for virtualhosts based on URL path (mvorisek) + * bug #38387 [Validator] prevent hash collisions caused by reused object hashes (fancyweb, xabbuh) + * bug #38999 [DependencyInjection] autoconfigure behavior describing tags on decorators (xabbuh) + * bug #39058 [DependencyInjection] Fix circular detection with multiple paths (jderusse) + * bug #39059 [Filesystem] fix cleaning up tmp files when dumpFile() fails (nicolas-grekas) + * bug #38628 [DoctrineBridge] indexBy could reference to association columns (juanmiguelbesada) + * bug #39021 [DependencyInjection] Optimize circular collection by removing flattening (jderusse) + * bug #39031 [Ldap] Fix pagination (jderusse) + * bug #39038 [DoctrineBridge] also reset id readers (xabbuh) + * bug #39025 [DoctrineBridge] Fix DBAL deprecations in middlewares (derrabus) + * bug #38991 [Console] Fix ANSI when stdErr is not a tty (jderusse) + * bug #38980 [DependencyInjection] Fix circular reference with Factory + Lazy Iterrator (jderusse) + * bug #38971 [PhpUnitBridge] fix replaying skipped tests (nicolas-grekas) + * bug #38910 [HttpKernel] Fix session initialized several times (jderusse) + * bug #38882 [DependencyInjection] Improve performances in CircualReference detection (jderusse) + * bug #38950 [Process] Dont test TTY if there is no TTY support (Nyholm) + * bug #38921 [PHPUnitBridge] Fixed crash on Windows with PHP 8 (villfa) + * bug #38869 [SecurityBundle] inject only compatible token storage implementations for usage tracking (xabbuh) + * bug #38894 [HttpKernel] Remove Symfony 3 compatibility code (derrabus) + * bug #38895 [PhpUnitBridge] Fix wrong check for exporter in ConstraintTrait (alcaeus) + * bug #38879 [Cache] Fixed expiry could be int in ChainAdapter due to race conditions (phamviet) + * bug #38856 [Cache] Add missing use statement (fabpot) + * 4.4.16 (2020-10-28) * bug #38713 [DI] Fix Preloader exception when preloading a class with an unknown parent/interface (rgeraads) From 09df32693acd4c8ec053fb80d78f4d664b334936 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 29 Nov 2020 10:23:08 +0100 Subject: [PATCH 39/46] Update VERSION for 4.4.17 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 746eec8869ce8..88b77741c5181 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '4.4.17-DEV'; + const VERSION = '4.4.17'; const VERSION_ID = 40417; const MAJOR_VERSION = 4; const MINOR_VERSION = 4; const RELEASE_VERSION = 17; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '11/2022'; const END_OF_LIFE = '11/2023'; From aa5ec20a0c81cbb63293395bedb3e9952060ad9d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 29 Nov 2020 10:27:10 +0100 Subject: [PATCH 40/46] Bump Symfony version to 4.4.18 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 88b77741c5181..cda7223b9a6f9 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -76,12 +76,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '4.4.17'; - const VERSION_ID = 40417; + const VERSION = '4.4.18-DEV'; + const VERSION_ID = 40418; const MAJOR_VERSION = 4; const MINOR_VERSION = 4; - const RELEASE_VERSION = 17; - const EXTRA_VERSION = ''; + const RELEASE_VERSION = 18; + const EXTRA_VERSION = 'DEV'; const END_OF_MAINTENANCE = '11/2022'; const END_OF_LIFE = '11/2023'; From 915e787d0f1c02a2cfd57358b1f1987e8c718680 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 29 Nov 2020 10:27:43 +0100 Subject: [PATCH 41/46] Update CHANGELOG for 5.1.9 --- CHANGELOG-5.1.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/CHANGELOG-5.1.md b/CHANGELOG-5.1.md index 0f8e3a5c324dd..3d530f59a41d1 100644 --- a/CHANGELOG-5.1.md +++ b/CHANGELOG-5.1.md @@ -7,6 +7,54 @@ in 5.1 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.1.0...v5.1.1 +* 5.1.9 (2020-11-29) + + * bug #39166 [Messenger] Fix mssql compatibility for doctrine transport. (bill moll) + * bug #39211 [HttpClient] fix binding to network interfaces (nicolas-grekas) + * bug #39129 [DependencyInjection] Fix circular in DI with lazy + byContruct loop (jderusse) + * bug #39068 [DependencyInjection][Translator] Silent deprecation triggered by libxml_disable_entity_loader (jderusse) + * bug #39119 [Form] prevent duplicated error message for file upload limits (xabbuh) + * bug #39099 [Form] ignore the pattern attribute for textareas (xabbuh) + * bug #39154 [Yaml] fix lexing strings containing escaped quotation characters (xabbuh) + * bug #39180 [Serializer] Fix denormalizing scalar with UnwrappingDenormalizer (camilledejoye) + * bug #38597 [PhpUnitBridge] Fix qualification of deprecations triggered by the debug class loader (fancyweb) + * bug #39160 [Console] Use a partial buffer in SymfonyStyle (jderusse) + * bug #39168 [Console] Fix console closing tag (jderusse) + * bug #39155 [VarDumper] fix casting resources turned into objects on PHP 8 (nicolas-grekas) + * bug #39131 [Cache] Fix CI because of Couchbase version (jderusse) + * bug #39115 [HttpClient] don't fallback to HTTP/1.1 when HTTP/2 streams break (nicolas-grekas) + * bug #33763 [Yaml] fix lexing nested sequences/mappings (xabbuh) + * bug #39083 [Dotenv] Check if method inheritEnvironmentVariables exists (Chi-teck) + * bug #39094 [Ldap] Fix undefined variable $con (derrabus) + * bug #39091 [Config] Recheck glob brace support after GlobResource was serialized (wouterj) + * bug #39092 Fix critical extension when reseting paged control (jderusse) + * bug #38614 [HttpFoundation] Fix for virtualhosts based on URL path (mvorisek) + * bug #39072 [FrameworkBundle] [Notifier] fix firebase transport factory DI tag type (xabbuh) + * bug #38387 [Validator] prevent hash collisions caused by reused object hashes (fancyweb, xabbuh) + * bug #38999 [DependencyInjection] autoconfigure behavior describing tags on decorators (xabbuh) + * bug #39058 [DependencyInjection] Fix circular detection with multiple paths (jderusse) + * bug #39059 [Filesystem] fix cleaning up tmp files when dumpFile() fails (nicolas-grekas) + * bug #38628 [DoctrineBridge] indexBy could reference to association columns (juanmiguelbesada) + * bug #39021 [DependencyInjection] Optimize circular collection by removing flattening (jderusse) + * bug #39031 [Ldap] Fix pagination (jderusse) + * bug #39038 [DoctrineBridge] also reset id readers (xabbuh) + * bug #39026 [Messenger] Fix DBAL deprecations in PostgreSqlConnection (chalasr) + * bug #39025 [DoctrineBridge] Fix DBAL deprecations in middlewares (derrabus) + * bug #38991 [Console] Fix ANSI when stdErr is not a tty (jderusse) + * bug #38980 [DependencyInjection] Fix circular reference with Factory + Lazy Iterrator (jderusse) + * bug #38977 [HttpClient] Check status code before decoding content in TraceableResponse (chalasr) + * bug #38971 [PhpUnitBridge] fix replaying skipped tests (nicolas-grekas) + * bug #38910 [HttpKernel] Fix session initialized several times (jderusse) + * bug #38882 [DependencyInjection] Improve performances in CircualReference detection (jderusse) + * bug #38950 [Process] Dont test TTY if there is no TTY support (Nyholm) + * bug #38921 [PHPUnitBridge] Fixed crash on Windows with PHP 8 (villfa) + * bug #38869 [SecurityBundle] inject only compatible token storage implementations for usage tracking (xabbuh) + * bug #38894 [HttpKernel] Remove Symfony 3 compatibility code (derrabus) + * bug #38895 [PhpUnitBridge] Fix wrong check for exporter in ConstraintTrait (alcaeus) + * bug #38879 [Cache] Fixed expiry could be int in ChainAdapter due to race conditions (phamviet) + * bug #38867 [FrameworkBundle] Fixing TranslationUpdateCommand failure when using "--no-backup" (liarco) + * bug #38856 [Cache] Add missing use statement (fabpot) + * 5.1.8 (2020-10-28) * bug #38713 [DI] Fix Preloader exception when preloading a class with an unknown parent/interface (rgeraads) From a2efa61d2db36c852ca259b285778388f08161eb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 29 Nov 2020 10:27:52 +0100 Subject: [PATCH 42/46] Update VERSION for 5.1.9 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 55e50173badc2..4134ff870e331 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.9-DEV'; + const VERSION = '5.1.9'; const VERSION_ID = 50109; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; const RELEASE_VERSION = 9; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '01/2021'; const END_OF_LIFE = '01/2021'; From d4ccae9b31f0c3c505507804990d835d74fce7d6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 29 Nov 2020 10:31:15 +0100 Subject: [PATCH 43/46] Bump Symfony version to 5.1.10 --- src/Symfony/Component/HttpKernel/Kernel.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 4134ff870e331..76a07f8f08366 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -73,12 +73,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.1.9'; - const VERSION_ID = 50109; + const VERSION = '5.1.10-DEV'; + const VERSION_ID = 50110; const MAJOR_VERSION = 5; const MINOR_VERSION = 1; - const RELEASE_VERSION = 9; - const EXTRA_VERSION = ''; + const RELEASE_VERSION = 10; + const EXTRA_VERSION = 'DEV'; const END_OF_MAINTENANCE = '01/2021'; const END_OF_LIFE = '01/2021'; From 27450c0bb4827ea7ab354307f05d79b3f1365197 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 28 Nov 2020 16:34:29 +0100 Subject: [PATCH 44/46] [Security] [DX] Automatically add PasswordUpgradeBadge + default support() impl in AbstractFormLoginAuthenticator --- .../AbstractLoginFormAuthenticator.php | 14 ++++++ .../CheckCredentialsListener.php | 5 ++ .../CheckCredentialsListenerTest.php | 49 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php index 24c7405ea91a8..aeaf1d17cd193 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractLoginFormAuthenticator.php @@ -32,6 +32,20 @@ abstract class AbstractLoginFormAuthenticator extends AbstractAuthenticator impl */ abstract protected function getLoginUrl(Request $request): string; + /** + * {@inheritdoc} + * + * Override to change the request conditions that have to be + * matched in order to handle the login form submit. + * + * This default implementation handles all POST requests to the + * login path (@see getLoginUrl()). + */ + public function supports(Request $request): bool + { + return $request->isMethod('POST') && $this->getLoginUrl($request) === $request->getPathInfo(); + } + /** * Override to change what happens after a bad username/password is submitted. */ diff --git a/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php b/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php index e2d3f86b8f5b3..c1f649b089ce0 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CheckCredentialsListener.php @@ -14,6 +14,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface; @@ -65,6 +66,10 @@ public function checkPassport(CheckPassportEvent $event): void $badge->markResolved(); + if (!$passport->hasBadge(PasswordUpgradeBadge::class)) { + $passport->addBadge(new PasswordUpgradeBadge($presentedPassword)); + } + return; } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php index 5dc411bef092e..e903dcd22cbf6 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/CheckCredentialsListenerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\PasswordUpgradeBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CustomCredentials; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; @@ -113,6 +114,54 @@ public function testNoCredentialsBadgeProvided() $this->listener->checkPassport($event); } + public function testAddsPasswordUpgradeBadge() + { + $encoder = $this->createMock(PasswordEncoderInterface::class); + $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', 'ThePa$$word')->willReturn(true); + + $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder); + + $passport = new Passport(new UserBadge('wouter', function () { return $this->user; }), new PasswordCredentials('ThePa$$word')); + $this->listener->checkPassport($this->createEvent($passport)); + + $this->assertTrue($passport->hasBadge(PasswordUpgradeBadge::class)); + $this->assertEquals('ThePa$$word', $passport->getBadge(PasswordUpgradeBadge::class)->getAndErasePlaintextPassword()); + } + + public function testAddsNoPasswordUpgradeBadgeIfItAlreadyExists() + { + $encoder = $this->createMock(PasswordEncoderInterface::class); + $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', 'ThePa$$word')->willReturn(true); + + $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder); + + $passport = $this->getMockBuilder(Passport::class) + ->setMethods(['addBadge']) + ->setConstructorArgs([new UserBadge('wouter', function () { return $this->user; }), new PasswordCredentials('ThePa$$word'), [new PasswordUpgradeBadge('ThePa$$word')]]) + ->getMock(); + + $passport->expects($this->never())->method('addBadge')->with($this->isInstanceOf(PasswordUpgradeBadge::class)); + + $this->listener->checkPassport($this->createEvent($passport)); + } + + public function testAddsNoPasswordUpgradeBadgeIfPasswordIsInvalid() + { + $encoder = $this->createMock(PasswordEncoderInterface::class); + $encoder->expects($this->any())->method('isPasswordValid')->with('encoded-password', 'ThePa$$word')->willReturn(false); + + $this->encoderFactory->expects($this->any())->method('getEncoder')->with($this->identicalTo($this->user))->willReturn($encoder); + + $passport = $this->getMockBuilder(Passport::class) + ->setMethods(['addBadge']) + ->setConstructorArgs([new UserBadge('wouter', function () { return $this->user; }), new PasswordCredentials('ThePa$$word'), [new PasswordUpgradeBadge('ThePa$$word')]]) + ->getMock(); + + $passport->expects($this->never())->method('addBadge')->with($this->isInstanceOf(PasswordUpgradeBadge::class)); + + $this->listener->checkPassport($this->createEvent($passport)); + } + private function createEvent($passport) { return new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport); From d5477fe2fabd31495d33418f8ad0f0bf097d04fb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 30 Nov 2020 06:53:11 +0100 Subject: [PATCH 45/46] Update CHANGELOG for 5.2.0 --- CHANGELOG-5.2.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG-5.2.md b/CHANGELOG-5.2.md index 2a83a9f8b5649..ff8c81c4481b7 100644 --- a/CHANGELOG-5.2.md +++ b/CHANGELOG-5.2.md @@ -7,6 +7,30 @@ in 5.2 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v5.2.0...v5.2.1 +* 5.2.0 (2020-11-30) + + * feature #39213 [Security] [DX] Automatically add PasswordUpgradeBadge + default support() impl in AbstractFormLoginAuthenticator (wouterj) + * bug #39166 [Messenger] Fix mssql compatibility for doctrine transport. (bill moll) + * bug #39210 [DoctrineBridge] Fix form EntityType with filter on UID (jderusse) + * bug #39211 [HttpClient] fix binding to network interfaces (nicolas-grekas) + * bug #39129 [DependencyInjection] Fix circular in DI with lazy + byContruct loop (jderusse) + * feature #39153 [Security] Automatically register custom authenticator as entry_point (if supported) (wouterj) + * bug #39068 [DependencyInjection][Translator] Silent deprecation triggered by libxml_disable_entity_loader (jderusse) + * bug #39119 [Form] prevent duplicated error message for file upload limits (xabbuh) + * bug #39099 [Form] ignore the pattern attribute for textareas (xabbuh) + * feature #39118 [DoctrineBridge] Require doctrine/persistence 2 (greg0ire) + * feature #39128 [HttpFoundation] Deprecate BinaryFileResponse::create() (derrabus) + * bug #39154 [Yaml] fix lexing strings containing escaped quotation characters (xabbuh) + * bug #39187 [Security] Support for SwitchUserToken instances serialized with 4.4/5.1 (derrabus) + * bug #39180 [Serializer] Fix denormalizing scalar with UnwrappingDenormalizer (camilledejoye) + * bug #38597 [PhpUnitBridge] Fix qualification of deprecations triggered by the debug class loader (fancyweb) + * bug #39160 [Console] Use a partial buffer in SymfonyStyle (jderusse) + * bug #39168 [Console] Fix console closing tag (jderusse) + * bug #39155 [VarDumper] fix casting resources turned into objects on PHP 8 (nicolas-grekas) + * bug #39131 [Cache] Fix CI because of Couchbase version (jderusse) + * bug #39115 [HttpClient] don't fallback to HTTP/1.1 when HTTP/2 streams break (nicolas-grekas) + * bug #33763 [Yaml] fix lexing nested sequences/mappings (xabbuh) + * 5.2.0-RC2 (2020-11-21) * bug #39113 [DoctrineBridge] drop binary variants of UID types (nicolas-grekas) From 25acc0b5dfab28e51a40ba9d57033993a42c5f08 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 30 Nov 2020 06:54:18 +0100 Subject: [PATCH 46/46] Update VERSION for 5.2.0 --- src/Symfony/Component/HttpKernel/Kernel.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 3522aaa770665..285507a21ec6c 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -74,12 +74,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl private static $freshCache = []; - const VERSION = '5.2.0-DEV'; + const VERSION = '5.2.0'; const VERSION_ID = 50200; const MAJOR_VERSION = 5; const MINOR_VERSION = 2; const RELEASE_VERSION = 0; - const EXTRA_VERSION = 'DEV'; + const EXTRA_VERSION = ''; const END_OF_MAINTENANCE = '07/2021'; const END_OF_LIFE = '07/2021';