diff --git a/.travis.yml b/.travis.yml index 73eec7a9ebd53..24155a5c0c36f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -151,6 +151,7 @@ before_install: INI=~/.phpenv/versions/$PHP/etc/conf.d/travis.ini echo date.timezone = Europe/Paris >> $INI echo memory_limit = -1 >> $INI + echo default_socket_timeout = 10 >> $INI echo session.gc_probability = 0 >> $INI echo opcache.enable_cli = 1 >> $INI echo apc.enable_cli = 1 >> $INI diff --git a/CHANGELOG-5.1.md b/CHANGELOG-5.1.md index 5f1269de038e9..7d1409a1ec728 100644 --- a/CHANGELOG-5.1.md +++ b/CHANGELOG-5.1.md @@ -7,6 +7,23 @@ 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.0 (2020-05-31) + + * bug #37009 [Validator] use "allowedVariables" to configure the ExpressionLanguageSyntax constraint (xabbuh) + * bug #37008 [Security] Fixed AbstractToken::hasUserChanged() (wouterj) + * bug #36894 [Validator] never directly validate Existence (Required/Optional) constraints (xabbuh) + * bug #37007 [Console] Fix QuestionHelper::disableStty() (chalasr) + * bug #36865 [Form] validate subforms in all validation groups (xabbuh) + * bug #36907 Fixes sprintf(): Too few arguments in form transformer (pedrocasado) + * bug #36868 [Validator] Use Mime component to determine mime type for file validator (pierredup) + * bug #37000 Add meaningful message when using ProcessHelper and Process is not installed (l-vo) + * bug #36990 [Messenger] Change the default notify timeout value for PostgreSQL (fabpot) + * bug #36995 [TwigBridge] fix fallback html-to-txt body converter (nicolas-grekas) + * bug #36993 [ErrorHandler] fix setting $trace to null in FatalError (nicolas-grekas) + * bug #36987 Handle fetch mode deprecation of DBAL 2.11. (derrabus) + * bug #36984 [SecurityBundle] Fixed version constraint on security-core and security-guard (wouterj) + * bug #36974 [Security] Fixed handling of CSRF logout error (wouterj) + * 5.1.0-RC2 (2020-05-26) * bug #36966 Fix extra SQL support in Doctrine migrations (fabpot) diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index e58803b397c83..eda901d0541e0 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -63,7 +63,7 @@ public function loadTokenBySeries(string $series) $paramValues = ['series' => $series]; $paramTypes = ['series' => \PDO::PARAM_STR]; $stmt = $this->conn->executeQuery($sql, $paramValues, $paramTypes); - $row = $stmt->fetch(\PDO::FETCH_ASSOC); + $row = method_exists($stmt, 'fetchAssociative') ? $stmt->fetchAssociative() : $stmt->fetch(\PDO::FETCH_ASSOC); if ($row) { return new PersistentToken($row['class'], $row['username'], $series, $row['value'], new \DateTime($row['last_used'])); diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php new file mode 100644 index 0000000000000..df696ff7ff292 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/RememberMe/DoctrineTokenProviderTest.php @@ -0,0 +1,88 @@ += 80000) { + self::markTestSkipped('Doctrine DBAL 2.x is incompatible with PHP 8.'); + } + } + + public function testCreateNewToken() + { + $provider = $this->bootstrapProvider(); + + $token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51')); + $provider->createNewToken($token); + + $this->assertEquals($provider->loadTokenBySeries('someSeries'), $token); + } + + public function testLoadTokenBySeriesThrowsNotFoundException() + { + $provider = $this->bootstrapProvider(); + + $this->expectException(TokenNotFoundException::class); + $provider->loadTokenBySeries('someSeries'); + } + + public function testUpdateToken() + { + $provider = $this->bootstrapProvider(); + + $token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51')); + $provider->createNewToken($token); + $provider->updateToken('someSeries', 'newValue', $lastUsed = new \DateTime('2014-06-26T22:03:46')); + $token = $provider->loadTokenBySeries('someSeries'); + + $this->assertEquals('newValue', $token->getTokenValue()); + $this->assertEquals($token->getLastUsed(), $lastUsed); + } + + public function testDeleteToken() + { + $provider = $this->bootstrapProvider(); + $token = new PersistentToken('someClass', 'someUser', 'someSeries', 'tokenValue', new \DateTime('2013-01-26T18:23:51')); + $provider->createNewToken($token); + $provider->deleteTokenBySeries('someSeries'); + + $this->expectException(TokenNotFoundException::class); + + $provider->loadTokenBySeries('someSeries'); + } + + /** + * @return DoctrineTokenProvider + */ + private function bootstrapProvider() + { + $connection = DriverManager::getConnection([ + 'driver' => 'pdo_sqlite', + 'url' => 'sqlite:///:memory:', + ]); + $connection->executeUpdate(<<< 'SQL' + CREATE TABLE rememberme_token ( + series char(88) UNIQUE PRIMARY KEY NOT NULL, + value char(88) NOT NULL, + lastUsed datetime NOT NULL, + class varchar(100) NOT NULL, + username varchar(200) NOT NULL + ); +SQL + ); + + return new DoctrineTokenProvider($connection); + } +} diff --git a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php index eb1a4dc257677..f04db50c8ced7 100644 --- a/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php +++ b/src/Symfony/Bridge/Monolog/Command/ServerLogCommand.php @@ -102,7 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output) } if (!$socket = stream_socket_server($host, $errno, $errstr)) { - throw new RuntimeException(sprintf('Server start failed on "%s": '.$errstr.' '.$errno, $host)); + throw new RuntimeException(sprintf('Server start failed on "%s": ', $host).$errstr.' '.$errno); } foreach ($this->getLogs($socket) as $clientId => $message) { diff --git a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php index 14535f232a0a4..510cfcdd5b740 100644 --- a/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php +++ b/src/Symfony/Bridge/Twig/Mime/BodyRenderer.php @@ -74,6 +74,6 @@ private function convertHtmlToText(string $html): string return $this->converter->convert($html); } - return strip_tags($html); + return strip_tags(preg_replace('{<(head|style)\b.*?\1>}i', '', $html)); } } diff --git a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php index 6eeade3a737af..175a8e1978066 100644 --- a/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Mime/BodyRendererTest.php @@ -29,11 +29,12 @@ public function testRenderTextOnly(): void public function testRenderHtmlOnly(): void { - $email = $this->prepareEmail(null, 'HTML'); + $html = '
headHTML'; + $email = $this->prepareEmail(null, $html); $body = $email->getBody(); $this->assertInstanceOf(AlternativePart::class, $body); $this->assertEquals('HTML', $body->getParts()[0]->bodyToString()); - $this->assertEquals('HTML', $body->getParts()[1]->bodyToString()); + $this->assertEquals(str_replace('=', '=3D', $html), $body->getParts()[1]->bodyToString()); } public function testRenderHtmlOnlyWithTextSet(): void diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 3be54e720ac1a..c1603160dd0cd 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -23,9 +23,9 @@ "symfony/event-dispatcher": "^5.1", "symfony/http-kernel": "^5.0", "symfony/polyfill-php80": "^1.15", - "symfony/security-core": "^4.4|^5.0", + "symfony/security-core": "^5.1", "symfony/security-csrf": "^4.4|^5.0", - "symfony/security-guard": "^4.4|^5.0", + "symfony/security-guard": "^5.1", "symfony/security-http": "^5.1" }, "require-dev": { diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js index 6e693a9dc57c7..588a9d22ed350 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/time.js @@ -94,7 +94,7 @@ class TimelineEngine { createLabel(name, duration, memory, period) { const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label'); - const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} Mb`, 'timeline-sublabel'); + const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} MiB`, 'timeline-sublabel'); label.appendChild(sublabel); diff --git a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php index e48f6f22410a7..c4de580d0b514 100644 --- a/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php +++ b/src/Symfony/Component/Asset/VersionStrategy/JsonManifestVersionStrategy.php @@ -59,7 +59,7 @@ private function getManifestPath(string $path): ?string $this->manifestData = json_decode(file_get_contents($this->manifestPath), true); if (0 < json_last_error()) { - throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": '.json_last_error_msg(), $this->manifestPath)); + throw new \RuntimeException(sprintf('Error parsing JSON from asset manifest file "%s": ', $this->manifestPath).json_last_error_msg()); } } diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index f9cee34931a8b..a042ad8a2c1d2 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -219,7 +219,13 @@ protected function doFetch(array $ids) } $stmt->execute(); - while ($row = $stmt->fetch(\PDO::FETCH_NUM)) { + if (method_exists($stmt, 'iterateNumeric')) { + $stmt = $stmt->iterateNumeric(); + } else { + $stmt->setFetchMode(\PDO::FETCH_NUM); + } + + foreach ($stmt as $row) { if (null === $row[1]) { $expired[] = $row[0]; } else { @@ -251,7 +257,7 @@ protected function doHave(string $id) $stmt->bindValue(':time', time(), \PDO::PARAM_INT); $stmt->execute(); - return (bool) $stmt->fetchColumn(); + return (bool) (method_exists($stmt, 'fetchOne') ? $stmt->fetchOne() : $stmt->fetchColumn()); } /** diff --git a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php index d9c3b91568429..05a88e086c3cf 100644 --- a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php +++ b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php @@ -24,11 +24,11 @@ protected function isPruned($cache, string $name): bool $getPdoConn = $o->getMethod('getConnection'); $getPdoConn->setAccessible(true); - /** @var \Doctrine\DBAL\Statement $select */ + /** @var \Doctrine\DBAL\Statement|\PDOStatement $select */ $select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id'); $select->bindValue(':id', sprintf('%%%s', $name)); $select->execute(); - return 0 === \count($select->fetchAll(\PDO::FETCH_COLUMN)); + return 1 !== (int) (method_exists($select, 'fetchOne') ? $select->fetchOne() : $select->fetch(\PDO::FETCH_COLUMN)); } } diff --git a/src/Symfony/Component/Console/Helper/ProcessHelper.php b/src/Symfony/Component/Console/Helper/ProcessHelper.php index 01989681572aa..f82c16bae84a0 100644 --- a/src/Symfony/Component/Console/Helper/ProcessHelper.php +++ b/src/Symfony/Component/Console/Helper/ProcessHelper.php @@ -36,6 +36,10 @@ class ProcessHelper extends Helper */ public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process { + if (!class_exists(Process::class)) { + throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".'); + } + if ($output instanceof ConsoleOutputInterface) { $output = $output->getErrorOutput(); } diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index a4bd54a97e624..7dad15620632b 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -35,7 +35,7 @@ class QuestionHelper extends Helper { private $inputStream; private static $shell; - private static $stty; + private static $stty = true; /** * Asks a question to the user. @@ -109,7 +109,7 @@ private function doAsk(OutputInterface $output, Question $question) $inputStream = $this->inputStream ?: STDIN; $autocomplete = $question->getAutocompleterCallback(); - if (null === $autocomplete || !Terminal::hasSttyAvailable()) { + if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) { $ret = false; if ($question->isHidden()) { try { @@ -416,7 +416,7 @@ private function getHiddenResponse(OutputInterface $output, $inputStream, bool $ return $value; } - if (Terminal::hasSttyAvailable()) { + if (self::$stty && Terminal::hasSttyAvailable()) { $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 8164f743bc390..9afad24357d07 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Console\Tests\Helper; +use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\HelperSet; @@ -783,6 +784,35 @@ public function testTraversableAutocomplete() $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question)); } + public function testDisableSttby() + { + if (!Terminal::hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('invalid'); + + QuestionHelper::disableStty(); + $dialog = new QuestionHelper(); + $dialog->setHelperSet(new HelperSet([new FormatterHelper()])); + + $question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']); + $question->setMaxAttempts(1); + + //