diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php index f0e0a303ee905..1ea644df04263 100644 --- a/src/Symfony/Component/Console/Application.php +++ b/src/Symfony/Component/Console/Application.php @@ -186,6 +186,8 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu } } + $prevShellVerbosity = getenv('SHELL_VERBOSITY'); + try { $this->configureIO($input, $output); @@ -223,6 +225,22 @@ public function run(?InputInterface $input = null, ?OutputInterface $output = nu $phpHandler[0]->setExceptionHandler($finalHandler); } } + + // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it + // to its previous value to avoid one command verbosity to spread to other commands + if (false === $prevShellVerbosity) { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY'); + } + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } else { + if (\function_exists('putenv')) { + @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); + } + $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; + $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; + } } if ($this->autoExit) { diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php index 8e1591ec1b14a..09b65bbf9504f 100644 --- a/src/Symfony/Component/Console/Helper/QuestionHelper.php +++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php @@ -516,12 +516,16 @@ private function readInput($inputStream, Question $question): string|false $ret = ''; $cp = $this->setIOCodepage(); while (false !== ($char = fgetc($multiLineStreamReader))) { - if (\PHP_EOL === "{$ret}{$char}") { + if ("\x4" === $char || \PHP_EOL === "{$ret}{$char}") { break; } $ret .= $char; } + if (stream_get_meta_data($inputStream)['seekable']) { + fseek($inputStream, ftell($multiLineStreamReader)); + } + return $this->resetIOCodepage($cp, $ret); } diff --git a/src/Symfony/Component/Console/Tester/ApplicationTester.php b/src/Symfony/Component/Console/Tester/ApplicationTester.php index cebb6f8ebf34a..a6dc8e1ce5178 100644 --- a/src/Symfony/Component/Console/Tester/ApplicationTester.php +++ b/src/Symfony/Component/Console/Tester/ApplicationTester.php @@ -47,37 +47,17 @@ public function __construct( */ public function run(array $input, array $options = []): int { - $prevShellVerbosity = getenv('SHELL_VERBOSITY'); - - try { - $this->input = new ArrayInput($input); - if (isset($options['interactive'])) { - $this->input->setInteractive($options['interactive']); - } + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } - if ($this->inputs) { - $this->input->setStream(self::createStream($this->inputs)); - } + if ($this->inputs) { + $this->input->setStream(self::createStream($this->inputs)); + } - $this->initOutput($options); + $this->initOutput($options); - return $this->statusCode = $this->application->run($this->input, $this->output); - } finally { - // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it - // to its previous value to avoid one test's verbosity to spread to the following tests - if (false === $prevShellVerbosity) { - if (\function_exists('putenv')) { - @putenv('SHELL_VERBOSITY'); - } - unset($_ENV['SHELL_VERBOSITY']); - unset($_SERVER['SHELL_VERBOSITY']); - } else { - if (\function_exists('putenv')) { - @putenv('SHELL_VERBOSITY='.$prevShellVerbosity); - } - $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity; - $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity; - } - } + return $this->statusCode = $this->application->run($this->input, $this->output); } } diff --git a/src/Symfony/Component/Console/Tester/TesterTrait.php b/src/Symfony/Component/Console/Tester/TesterTrait.php index 1ab7a70aa22d9..127556d1db842 100644 --- a/src/Symfony/Component/Console/Tester/TesterTrait.php +++ b/src/Symfony/Component/Console/Tester/TesterTrait.php @@ -169,6 +169,10 @@ private static function createStream(array $inputs) foreach ($inputs as $input) { fwrite($stream, $input.\PHP_EOL); + + if (str_contains($input, \PHP_EOL)) { + fwrite($stream, "\x4"); + } } rewind($stream); diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 268f8ba501a9e..6390d4828fb79 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -37,6 +37,7 @@ use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Output\Output; @@ -831,7 +832,7 @@ public function testSetCatchErrors(bool $catchExceptions) try { $tester->run(['command' => 'boom']); - $this->fail('The exception is not catched.'); + $this->fail('The exception is not caught.'); } catch (\Throwable $e) { $this->assertInstanceOf(\Error::class, $e); $this->assertSame('This is an error.', $e->getMessage()); @@ -2463,6 +2464,102 @@ private function createSignalableApplication(Command $command, ?EventDispatcherI return $application; } + + public function testShellVerbosityIsRestoredAfterCommandExecutionWithInitialValue() + { + // Set initial SHELL_VERBOSITY + putenv('SHELL_VERBOSITY=-2'); + $_ENV['SHELL_VERBOSITY'] = '-2'; + $_SERVER['SHELL_VERBOSITY'] = '-2'; + + $application = new Application(); + $application->setAutoExit(false); + $application->register('foo') + ->setCode(function (InputInterface $input, OutputInterface $output): int { + $output->write('SHELL_VERBOSITY: '.$_SERVER['SHELL_VERBOSITY']); + + return 0; + }); + + $input = new ArrayInput(['command' => 'foo', '--verbose' => 3]); + $output = new BufferedOutput(); + + $application->run($input, $output); + + $this->assertSame('SHELL_VERBOSITY: 3', $output->fetch()); + $this->assertSame('-2', getenv('SHELL_VERBOSITY')); + $this->assertSame('-2', $_ENV['SHELL_VERBOSITY']); + $this->assertSame('-2', $_SERVER['SHELL_VERBOSITY']); + + // Clean up for other tests + putenv('SHELL_VERBOSITY'); + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + } + + public function testShellVerbosityIsRemovedAfterCommandExecutionWhenNotSetInitially() + { + // Ensure SHELL_VERBOSITY is not set initially + putenv('SHELL_VERBOSITY'); + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + + $application = new Application(); + $application->setAutoExit(false); + $application->register('foo') + ->setCode(function (InputInterface $input, OutputInterface $output): int { + $output->write('SHELL_VERBOSITY: '.$_SERVER['SHELL_VERBOSITY']); + + return 0; + }); + + $input = new ArrayInput(['command' => 'foo', '--verbose' => 3]); + $output = new BufferedOutput(); + + $application->run($input, $output); + + $this->assertSame('SHELL_VERBOSITY: 3', $output->fetch()); + $this->assertFalse(getenv('SHELL_VERBOSITY')); + $this->assertArrayNotHasKey('SHELL_VERBOSITY', $_ENV); + $this->assertArrayNotHasKey('SHELL_VERBOSITY', $_SERVER); + } + + public function testShellVerbosityDoesNotLeakBetweenCommandExecutions() + { + // Ensure no initial SHELL_VERBOSITY + putenv('SHELL_VERBOSITY'); + unset($_ENV['SHELL_VERBOSITY']); + unset($_SERVER['SHELL_VERBOSITY']); + + $application = new Application(); + $application->setAutoExit(false); + $application->register('verbose-cmd') + ->setCode(function (InputInterface $input, OutputInterface $output): int { + $output->write('SHELL_VERBOSITY: '.$_SERVER['SHELL_VERBOSITY']); + + return 0; + }); + $application->register('normal-cmd') + ->setCode(function (InputInterface $input, OutputInterface $output): int { + $output->write('SHELL_VERBOSITY: '.$_SERVER['SHELL_VERBOSITY']); + + return 0; + }); + + $output = new BufferedOutput(); + + $application->run(new ArrayInput(['command' => 'verbose-cmd', '--verbose' => true]), $output); + + $this->assertSame('SHELL_VERBOSITY: 1', $output->fetch(), 'SHELL_VERBOSITY should be set to 1 for verbose command'); + $this->assertFalse(getenv('SHELL_VERBOSITY'), 'SHELL_VERBOSITY should not be set after first command'); + + $application->run(new ArrayInput(['command' => 'normal-cmd']), $output); + + $this->assertSame('SHELL_VERBOSITY: 0', $output->fetch(), 'SHELL_VERBOSITY should not leak to second command'); + $this->assertFalse(getenv('SHELL_VERBOSITY'), 'SHELL_VERBOSITY should not leak to second command'); + $this->assertArrayNotHasKey('SHELL_VERBOSITY', $_ENV); + $this->assertArrayNotHasKey('SHELL_VERBOSITY', $_SERVER); + } } class CustomApplication extends Application diff --git a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php index 0e91dd85b199e..76e40cef0bf90 100644 --- a/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/QuestionHelperTest.php @@ -519,7 +519,7 @@ public function testAskMultilineResponseWithWithCursorInMiddleOfSeekableInputStr $question->setMultiline(true); $this->assertSame("some\ninput", $dialog->ask($this->createStreamableInputInterfaceMock($response), $this->createOutputInterface(), $question)); - $this->assertSame(8, ftell($response)); + $this->assertSame(18, ftell($response)); } /** diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php index e8716ff196ad9..c8b07fc0f5e0f 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Tests/Transport/ConnectionTest.php @@ -868,6 +868,12 @@ public function testConfigureSchemaOracleSequenceNameSuffixed() { $driverConnection = $this->createMock(DBALConnection::class); $driverConnection->method('getDatabasePlatform')->willReturn(new OraclePlatform()); + + // Mock the result returned by executeQuery to be an Oracle version 12.1.0 or higher. + $result = $this->createMock(Result::class); + $result->method('fetchOne')->willReturn('12.1.0'); + $driverConnection->method('executeQuery')->willReturn($result); + $schema = new Schema(); $connection = new Connection(['table_name' => 'messenger_messages'], $driverConnection); diff --git a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php index 99e9b0bccb022..daa93b2b0d1e8 100644 --- a/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php +++ b/src/Symfony/Component/Messenger/Bridge/Doctrine/Transport/Connection.php @@ -338,7 +338,9 @@ public function setup(): void throw new \TypeError(\sprintf('The table name must be an instance of "%s" or a string ("%s" given).', AbstractAsset::class, get_debug_type($tableName))); } - return $tableName === $this->configuration['table_name']; + // SchemaAssetsFilter needs to match the messenger table name and also the messenger sequence name to make $schemaDiff work correctly in updateSchema() + // This may also work for other databases if their sequence name is suffixed with '_seq', '_id_seq' or similar. + return str_starts_with($tableName, $this->configuration['table_name']); // MESSENGER_MESSAGES* }); $this->updateSchema(); $configuration->setSchemaAssetsFilter($assetFilter); @@ -569,9 +571,13 @@ private function addTableToSchema(Schema $schema): void // We need to create a sequence for Oracle and set the id column to get the correct nextval if ($this->driverConnection->getDatabasePlatform() instanceof OraclePlatform) { - $idColumn->setDefault($this->configuration['table_name'].self::ORACLE_SEQUENCES_SUFFIX.'.nextval'); + $serverVersion = $this->driverConnection->executeQuery("SELECT version FROM product_component_version WHERE product LIKE 'Oracle Database%'")->fetchOne(); + if (version_compare($serverVersion, '12.1.0', '>=')) { + $idColumn->setAutoincrement(false); // disable the creation of SEQUENCE and TRIGGER + $idColumn->setDefault($this->configuration['table_name'].self::ORACLE_SEQUENCES_SUFFIX.'.nextval'); - $schema->createSequence($this->configuration['table_name'].self::ORACLE_SEQUENCES_SUFFIX); + $schema->createSequence($this->configuration['table_name'].self::ORACLE_SEQUENCES_SUFFIX); + } } } diff --git a/src/Symfony/Component/Security/Core/Resources/translations/security.es.xlf b/src/Symfony/Component/Security/Core/Resources/translations/security.es.xlf index e8af87e5bb9c8..d1ebb1e343485 100644 --- a/src/Symfony/Component/Security/Core/Resources/translations/security.es.xlf +++ b/src/Symfony/Component/Security/Core/Resources/translations/security.es.xlf @@ -76,7 +76,7 @@ Too many failed login attempts, please try again in %minutes% minutes. - Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo en %minutes% minutos. + Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo en %minutes% minutos. diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 8c03e85681ae0..7024a45efe5de 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -111,7 +111,7 @@ public function authenticate(RequestEvent $event): void if (!$this->stateless) { $request->query->remove($this->usernameParameter); $request->server->set('QUERY_STRING', http_build_query($request->query->all(), '', '&')); - $response = new RedirectResponse($this->urlGenerator && $this->targetRoute ? $this->urlGenerator->generate($this->targetRoute) : $request->getUri(), 302); + $response = new RedirectResponse($this->urlGenerator && $this->targetRoute && self::EXIT_VALUE !== $username ? $this->urlGenerator->generate($this->targetRoute) : $request->getUri(), 302); $event->setResponse($response); } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php index 114d0db979e46..7c87c1a8fdd45 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -109,6 +110,20 @@ public function testExitUserUpdatesToken() $this->assertSame($originalToken, $this->tokenStorage->getToken()); } + public function testExitUserDoesNotRedirectToTargetRoute() + { + $originalToken = new UsernamePasswordToken(new InMemoryUser('username', '', []), 'key', []); + $this->tokenStorage->setToken(new SwitchUserToken(new InMemoryUser('username', '', ['ROLE_USER']), 'key', ['ROLE_USER'], $originalToken)); + + $this->request->query->set('_switch_user', SwitchUserListener::EXIT_VALUE); + + $listener = new SwitchUserListener($this->tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager, urlGenerator: $this->createMock(UrlGeneratorInterface::class), targetRoute: 'whatever'); + $listener($this->event); + + $this->assertInstanceOf(RedirectResponse::class, $this->event->getResponse()); + $this->assertSame($this->request->getUri(), $this->event->getResponse()->getTargetUrl()); + } + public function testExitUserDispatchesEventWithRefreshedUser() { $originalUser = new InMemoryUser('username', null); diff --git a/src/Symfony/Component/String/Inflector/EnglishInflector.php b/src/Symfony/Component/String/Inflector/EnglishInflector.php index 7224b4713e3ce..b9d74c004eb65 100644 --- a/src/Symfony/Component/String/Inflector/EnglishInflector.php +++ b/src/Symfony/Component/String/Inflector/EnglishInflector.php @@ -25,6 +25,9 @@ final class EnglishInflector implements InflectorInterface // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: singular suffix, normal + // nodes (node) + ['sedon', 5, true, true, 'node'], + // bacteria (bacterium) ['airetcab', 8, true, true, 'bacterium'], @@ -202,6 +205,9 @@ final class EnglishInflector implements InflectorInterface // Fourth entry: Whether the suffix may succeed a consonant // Fifth entry: plural suffix, normal + // nodes (node) + ['edon', 4, true, true, 'nodes'], + // axes (axis) ['sixa', 4, false, false, 'axes'], diff --git a/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php b/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php index 789a73d965e53..e3b35cbc8dfb5 100644 --- a/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php +++ b/src/Symfony/Component/String/Tests/Inflector/EnglishInflectorTest.php @@ -123,6 +123,7 @@ public static function singularizeProvider() ['nebulae', 'nebula'], ['neuroses', ['neuros', 'neurose', 'neurosis']], ['news', 'news'], + ['nodes', 'node'], ['oases', ['oas', 'oase', 'oasis']], ['objectives', 'objective'], ['outages', 'outage'], @@ -281,6 +282,7 @@ public static function pluralizeProvider() ['nebula', 'nebulae'], ['neurosis', 'neuroses'], ['news', 'news'], + ['node', 'nodes'], ['oasis', 'oases'], ['objective', 'objectives'], ['ox', 'oxen'], diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf index a9ad8a76b11e9..0d47977dec685 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.es.xlf @@ -468,7 +468,7 @@ This value is not a valid Twig template. - Este valor no es una plantilla Twig válida. + Este valor no es una plantilla Twig válida.