From 3057ea5cc64547c3ff859b9112e2dd43672b09ef Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 24 May 2024 12:26:22 +0200 Subject: [PATCH 01/34] use constructor property promotion --- Node/DumpNode.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Node/DumpNode.php b/Node/DumpNode.php index 5c3ac6ca..c96b4042 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -33,7 +33,6 @@ public function __construct( } parent::__construct($nodes, [], $lineno, $tag); - $this->varPrefix = $varPrefix; } public function compile(Compiler $compiler): void From bc841b3ce6898e55e419e9fb1435a48fcd201fce Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Jun 2024 17:52:34 +0200 Subject: [PATCH 02/34] Prefix all sprintf() calls --- Command/DebugCommand.php | 30 +++++++++---------- Command/LintCommand.php | 22 +++++++------- ErrorRenderer/TwigErrorRenderer.php | 2 +- Extension/EmojiExtension.php | 2 +- Extension/HttpKernelRuntime.php | 2 +- Extension/TranslationExtension.php | 8 ++--- Mime/BodyRenderer.php | 2 +- Mime/NotificationEmail.php | 6 ++-- Node/DumpNode.php | 10 +++---- .../TranslationDefaultDomainNodeVisitor.php | 2 +- Test/FormLayoutTestCase.php | 4 +-- Tests/Extension/HttpKernelExtensionTest.php | 2 +- Tests/Node/FormThemeTest.php | 10 +++---- Tests/Node/SearchAndRenderBlockNodeTest.php | 22 +++++++------- Tests/Node/TransNodeTest.php | 6 ++-- UndefinedCallableHandler.php | 4 +-- 16 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 21ede17a..42b456c2 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -57,7 +57,7 @@ protected function configure(): void ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), ]) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, @@ -90,13 +90,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int $filter = $input->getOption('filter'); if (null !== $name && [] === $this->getFilesystemLoaders()) { - throw new InvalidArgumentException(sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); + throw new InvalidArgumentException(\sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); } match ($input->getOption('format')) { 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), - default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + default => throw new InvalidArgumentException(\sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; return 0; @@ -121,7 +121,7 @@ private function displayPathsText(SymfonyStyle $io, string $name): void $io->section('Matched File'); if ($file->valid()) { if ($fileLink = $this->getFileLink($file->key())) { - $io->block($file->current(), 'OK', sprintf('fg=black;bg=green;href=%s', $fileLink), ' ', true); + $io->block($file->current(), 'OK', \sprintf('fg=black;bg=green;href=%s', $fileLink), ' ', true); } else { $io->success($file->current()); } @@ -131,9 +131,9 @@ private function displayPathsText(SymfonyStyle $io, string $name): void $io->section('Overridden Files'); do { if ($fileLink = $this->getFileLink($file->key())) { - $io->text(sprintf('* %s', $fileLink, $file->current())); + $io->text(\sprintf('* %s', $fileLink, $file->current())); } else { - $io->text(sprintf('* %s', $file->current())); + $io->text(\sprintf('* %s', $file->current())); } $file->next(); } while ($file->valid()); @@ -158,7 +158,7 @@ private function displayPathsText(SymfonyStyle $io, string $name): void } } - $this->error($io, sprintf('Template name "%s" not found', $name), $alternatives); + $this->error($io, \sprintf('Template name "%s" not found', $name), $alternatives); } $io->section('Configured Paths'); @@ -171,7 +171,7 @@ private function displayPathsText(SymfonyStyle $io, string $name): void if (FilesystemLoader::MAIN_NAMESPACE === $namespace) { $message = 'No template paths configured for your application'; } else { - $message = sprintf('No template paths configured for "@%s" namespace', $namespace); + $message = \sprintf('No template paths configured for "@%s" namespace', $namespace); foreach ($this->getFilesystemLoaders() as $loader) { $namespaces = $loader->getNamespaces(); foreach ($this->findAlternatives($namespace, $namespaces) as $namespace) { @@ -199,7 +199,7 @@ private function displayPathsJson(SymfonyStyle $io, string $name): void $data['overridden_files'] = $files; } } else { - $data['matched_file'] = sprintf('Template name "%s" not found', $name); + $data['matched_file'] = \sprintf('Template name "%s" not found', $name); } $data['loader_paths'] = $paths; @@ -364,7 +364,7 @@ private function getPrettyMetadata(string $type, mixed $entity, bool $decorated) return '(unknown?)'; } } catch (\UnexpectedValueException $e) { - return sprintf(' %s', $decorated ? OutputFormatter::escape($e->getMessage()) : $e->getMessage()); + return \sprintf(' %s', $decorated ? OutputFormatter::escape($e->getMessage()) : $e->getMessage()); } if ('globals' === $type) { @@ -374,7 +374,7 @@ private function getPrettyMetadata(string $type, mixed $entity, bool $decorated) $description = substr(@json_encode($meta), 0, 50); - return sprintf(' = %s', $decorated ? OutputFormatter::escape($description) : $description); + return \sprintf(' = %s', $decorated ? OutputFormatter::escape($description) : $description); } if ('functions' === $type) { @@ -421,14 +421,14 @@ private function buildWarningMessages(array $wrongBundles): array { $messages = []; foreach ($wrongBundles as $path => $alternatives) { - $message = sprintf('Path "%s" not matching any bundle found', $path); + $message = \sprintf('Path "%s" not matching any bundle found', $path); if ($alternatives) { if (1 === \count($alternatives)) { - $message .= sprintf(", did you mean \"%s\"?\n", $alternatives[0]); + $message .= \sprintf(", did you mean \"%s\"?\n", $alternatives[0]); } else { $message .= ", did you mean one of these:\n"; foreach ($alternatives as $bundle) { - $message .= sprintf(" - %s\n", $bundle); + $message .= \sprintf(" - %s\n", $bundle); } } } @@ -481,7 +481,7 @@ private function parseTemplateName(string $name, string $default = FilesystemLoa { if (isset($name[0]) && '@' === $name[0]) { if (false === ($pos = strpos($name, '/')) || $pos === \strlen($name) - 1) { - throw new InvalidArgumentException(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + throw new InvalidArgumentException(\sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); } $namespace = substr($name, 1, $pos - 1); diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 14c00ba1..456c1864 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -52,7 +52,7 @@ public function __construct( protected function configure(): void { $this - ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) + ->addOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions()))) ->addOption('show-deprecations', null, InputOption::VALUE_NONE, 'Show deprecations as errors') ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN') ->addOption('excludes', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Excluded directories', []) @@ -151,7 +151,7 @@ protected function findFiles(string $filename): iterable return Finder::create()->files()->in($filename)->name($this->namePatterns)->exclude($this->excludes); } - throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename)); + throw new RuntimeException(\sprintf('File or directory "%s" is not readable.', $filename)); } private function validate(string $template, string $file): array @@ -178,7 +178,7 @@ private function display(InputInterface $input, OutputInterface $output, Symfony 'txt' => $this->displayTxt($output, $io, $files), 'json' => $this->displayJson($output, $files), 'github' => $this->displayTxt($output, $io, $files, true), - default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), + default => throw new InvalidArgumentException(\sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; } @@ -189,7 +189,7 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi foreach ($filesInfo as $info) { if ($info['valid'] && $output->isVerbose()) { - $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + $io->comment('OK'.($info['file'] ? \sprintf(' in %s', $info['file']) : '')); } elseif (!$info['valid']) { ++$errors; $this->renderException($io, $info['template'], $info['exception'], $info['file'], $githubReporter); @@ -197,9 +197,9 @@ private function displayTxt(OutputInterface $output, SymfonyStyle $io, array $fi } if (0 === $errors) { - $io->success(sprintf('All %d Twig files contain valid syntax.', \count($filesInfo))); + $io->success(\sprintf('All %d Twig files contain valid syntax.', \count($filesInfo))); } else { - $io->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors)); + $io->warning(\sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors)); } return min($errors, 1); @@ -231,28 +231,28 @@ private function renderException(SymfonyStyle $output, string $template, Error $ $githubReporter?->error($exception->getRawMessage(), $file, $line <= 0 ? null : $line); if ($file) { - $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); + $output->text(\sprintf(' ERROR in %s (line %s)', $file, $line)); } else { - $output->text(sprintf(' ERROR (line %s)', $line)); + $output->text(\sprintf(' ERROR (line %s)', $line)); } // If the line is not known (this might happen for deprecations if we fail at detecting the line for instance), // we render the message without context, to ensure the message is displayed. if ($line <= 0) { - $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + $output->text(\sprintf(' >> %s ', $exception->getRawMessage())); return; } foreach ($this->getContext($template, $line) as $lineNumber => $code) { - $output->text(sprintf( + $output->text(\sprintf( '%s %-6s %s', $lineNumber === $line ? ' >> ' : ' ', $lineNumber, $code )); if ($lineNumber === $line) { - $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + $output->text(\sprintf(' >> %s ', $exception->getRawMessage())); } } } diff --git a/ErrorRenderer/TwigErrorRenderer.php b/ErrorRenderer/TwigErrorRenderer.php index 0ea9b9aa..f624720b 100644 --- a/ErrorRenderer/TwigErrorRenderer.php +++ b/ErrorRenderer/TwigErrorRenderer.php @@ -69,7 +69,7 @@ public static function isDebug(RequestStack $requestStack, bool $debug): \Closur private function findTemplate(int $statusCode): ?string { - $template = sprintf('@Twig/Exception/error%s.html.twig', $statusCode); + $template = \sprintf('@Twig/Exception/error%s.html.twig', $statusCode); if ($this->twig->getLoader()->exists($template)) { return $template; } diff --git a/Extension/EmojiExtension.php b/Extension/EmojiExtension.php index b98798da..dcc5a5a0 100644 --- a/Extension/EmojiExtension.php +++ b/Extension/EmojiExtension.php @@ -47,7 +47,7 @@ public function emojify(string $string, ?string $catalog = null): string try { $tr = self::$transliterators[$catalog] ??= EmojiTransliterator::create($catalog, EmojiTransliterator::REVERSE); } catch (\IntlException $e) { - throw new \LogicException(sprintf('The emoji catalog "%s" is not available.', $catalog), previous: $e); + throw new \LogicException(\sprintf('The emoji catalog "%s" is not available.', $catalog), previous: $e); } return (string) $tr->transliterate($string); diff --git a/Extension/HttpKernelRuntime.php b/Extension/HttpKernelRuntime.php index 0aefed8f..6c488ef7 100644 --- a/Extension/HttpKernelRuntime.php +++ b/Extension/HttpKernelRuntime.php @@ -54,7 +54,7 @@ public function renderFragmentStrategy(string $strategy, string|ControllerRefere public function generateFragmentUri(ControllerReference $controller, bool $absolute = false, bool $strict = true, bool $sign = true): string { if (null === $this->fragmentUriGenerator) { - throw new \LogicException(sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__)); + throw new \LogicException(\sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__)); } return $this->fragmentUriGenerator->generate($controller, null, $absolute, $strict, $sign); diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index bf8b81bd..1958aebe 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -44,7 +44,7 @@ public function getTranslator(): TranslatorInterface { if (null === $this->translator) { if (!interface_exists(TranslatorInterface::class)) { - throw new \LogicException(sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); + throw new \LogicException(\sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); } $this->translator = new class() implements TranslatorInterface { @@ -97,7 +97,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar { if ($message instanceof TranslatableInterface) { if ([] !== $arguments && !\is_string($arguments)) { - throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be a locale passed as a string when the message is a "%s", "%s" given.', __METHOD__, TranslatableInterface::class, get_debug_type($arguments))); + throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be a locale passed as a string when the message is a "%s", "%s" given.', __METHOD__, TranslatableInterface::class, get_debug_type($arguments))); } if ($message instanceof TranslatableMessage && '' === $message->getMessage()) { @@ -108,7 +108,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar } if (!\is_array($arguments)) { - throw new \TypeError(sprintf('Unless the message is a "%s", argument 2 passed to "%s()" must be an array of parameters, "%s" given.', TranslatableInterface::class, __METHOD__, get_debug_type($arguments))); + throw new \TypeError(\sprintf('Unless the message is a "%s", argument 2 passed to "%s()" must be an array of parameters, "%s" given.', TranslatableInterface::class, __METHOD__, get_debug_type($arguments))); } if ('' === $message = (string) $message) { @@ -125,7 +125,7 @@ public function trans(string|\Stringable|TranslatableInterface|null $message, ar public function createTranslatable(string $message, array $parameters = [], ?string $domain = null): TranslatableMessage { if (!class_exists(TranslatableMessage::class)) { - throw new \LogicException(sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__)); + throw new \LogicException(\sprintf('You cannot use the "%s" as the Translation Component is not installed. Try running "composer require symfony/translation".', __CLASS__)); } return new TranslatableMessage($message, $parameters, $domain); diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index 25d87353..162f8c5f 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -52,7 +52,7 @@ public function render(Message $message): void $messageContext = $message->getContext(); if (isset($messageContext['email'])) { - throw new InvalidArgumentException(sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); + throw new InvalidArgumentException(\sprintf('A "%s" context cannot have an "email" entry as this is a reserved variable.', get_debug_type($message))); } $vars = array_merge($this->context, $messageContext, [ diff --git a/Mime/NotificationEmail.php b/Mime/NotificationEmail.php index 6e33d33d..4b4e1b26 100644 --- a/Mime/NotificationEmail.php +++ b/Mime/NotificationEmail.php @@ -54,7 +54,7 @@ public function __construct(?Headers $headers = null, ?AbstractPart $body = null } if ($missingPackages) { - throw new \LogicException(sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); + throw new \LogicException(\sprintf('You cannot use "%s" if the "%s" Twig extension%s not available. Try running "%s".', static::class, implode('" and "', $missingPackages), \count($missingPackages) > 1 ? 's are' : ' is', 'composer require '.implode(' ', array_keys($missingPackages)))); } parent::__construct($headers, $body); @@ -88,7 +88,7 @@ public function markAsPublic(): static public function markdown(string $content): static { if (!class_exists(MarkdownExtension::class)) { - throw new \LogicException(sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__)); + throw new \LogicException(\sprintf('You cannot use "%s" if the Markdown Twig extension is not available. Try running "composer require twig/markdown-extra".', __METHOD__)); } $this->context['markdown'] = true; @@ -218,7 +218,7 @@ public function getPreparedHeaders(): Headers $importance = $this->context['importance'] ?? self::IMPORTANCE_LOW; $this->priority($this->determinePriority($importance)); if ($this->context['importance']) { - $headers->setHeaderBody('Text', 'Subject', sprintf('[%s] %s', strtoupper($importance), $this->getSubject())); + $headers->setHeaderBody('Text', 'Subject', \sprintf('[%s] %s', strtoupper($importance), $this->getSubject())); } return $headers; diff --git a/Node/DumpNode.php b/Node/DumpNode.php index c96b4042..7c9c4a4a 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -44,18 +44,18 @@ public function compile(Compiler $compiler): void if (!$this->hasNode('values')) { // remove embedded templates (macros) from the context $compiler - ->write(sprintf('$%svars = [];'."\n", $this->varPrefix)) - ->write(sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) + ->write(\sprintf('$%svars = [];'."\n", $this->varPrefix)) + ->write(\sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) ->indent() - ->write(sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix)) + ->write(\sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix)) ->indent() - ->write(sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) + ->write(\sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) ->outdent() ->write("}\n") ->outdent() ->write("}\n") ->addDebugInfo($this) - ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); + ->write(\sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); } elseif (($values = $this->getNode('values')) && 1 === $values->count()) { $compiler ->addDebugInfo($this) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index b071677d..a9266066 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -114,6 +114,6 @@ private function isNamedArguments(Node $arguments): bool private function getVarName(): string { - return sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); + return \sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); } } diff --git a/Test/FormLayoutTestCase.php b/Test/FormLayoutTestCase.php index 1fdd83c9..0c719efc 100644 --- a/Test/FormLayoutTestCase.php +++ b/Test/FormLayoutTestCase.php @@ -57,7 +57,7 @@ protected function assertMatchesXpath($html, $expression, $count = 1): void // the top level $dom->loadXML(''.$html.''); } catch (\Exception $e) { - $this->fail(sprintf( + $this->fail(\sprintf( "Failed loading HTML:\n\n%s\n\nError: %s", $html, $e->getMessage() @@ -68,7 +68,7 @@ protected function assertMatchesXpath($html, $expression, $count = 1): void if ($nodeList->length != $count) { $dom->formatOutput = true; - $this->fail(sprintf( + $this->fail(\sprintf( "Failed asserting that \n\n%s\n\nmatches exactly %s. Matches %s in \n\n%s", $expression, 1 == $count ? 'once' : $count.' times', diff --git a/Tests/Extension/HttpKernelExtensionTest.php b/Tests/Extension/HttpKernelExtensionTest.php index fdf3c4f4..6d4ea74d 100644 --- a/Tests/Extension/HttpKernelExtensionTest.php +++ b/Tests/Extension/HttpKernelExtensionTest.php @@ -67,7 +67,7 @@ public function testGenerateFragmentUri() $kernelRuntime = new HttpKernelRuntime($fragmentHandler, $fragmentUriGenerator); $loader = new ArrayLoader([ - 'index' => sprintf(<< \sprintf(<<assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [1 => "tpl1", 0 => "tpl2"], true);', $this->getVariableGetter('form') ), @@ -71,7 +71,7 @@ public function testCompile() $node = new FormThemeNode($form, $resources, 0, null, true); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, [1 => "tpl1", 0 => "tpl2"], false);', $this->getVariableGetter('form') ), @@ -83,7 +83,7 @@ public function testCompile() $node = new FormThemeNode($form, $resources, 0); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, "tpl1", true);', $this->getVariableGetter('form') ), @@ -93,7 +93,7 @@ public function testCompile() $node = new FormThemeNode($form, $resources, 0, null, true); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime("Symfony\\\\Component\\\\Form\\\\FormRenderer")->setTheme(%s, "tpl1", false);', $this->getVariableGetter('form') ), @@ -103,6 +103,6 @@ public function testCompile() protected function getVariableGetter($name) { - return sprintf('($context["%s"] ?? null)', $name); + return \sprintf('($context["%s"] ?? null)', $name); } } diff --git a/Tests/Node/SearchAndRenderBlockNodeTest.php b/Tests/Node/SearchAndRenderBlockNodeTest.php index b259990e..c8cff99c 100644 --- a/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -36,7 +36,7 @@ public function testCompileWidget() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'widget\')', $this->getVariableGetter('form') ), @@ -59,7 +59,7 @@ public function testCompileWidgetWithVariables() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'widget\', ["foo" => "bar"])', $this->getVariableGetter('form') ), @@ -79,7 +79,7 @@ public function testCompileLabelWithLabel() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["label" => "my label"])', $this->getVariableGetter('form') ), @@ -101,7 +101,7 @@ public function testCompileLabelWithNullLabel() // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\')', $this->getVariableGetter('form') ), @@ -123,7 +123,7 @@ public function testCompileLabelWithEmptyStringLabel() // "label" => null must not be included in the output! // Otherwise the default label is overwritten with null. $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\')', $this->getVariableGetter('form') ), @@ -142,7 +142,7 @@ public function testCompileLabelWithDefaultLabel() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\')', $this->getVariableGetter('form') ), @@ -169,7 +169,7 @@ public function testCompileLabelWithAttributes() // Otherwise the default label is overwritten with null. // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar"])', $this->getVariableGetter('form') ), @@ -195,7 +195,7 @@ public function testCompileLabelWithLabelAndAttributes() $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar", "label" => "value in argument"])', $this->getVariableGetter('form') ), @@ -226,7 +226,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() // Otherwise the default label is overwritten with null. // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', (%s($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', $this->getVariableGetter('form'), method_exists(CoreExtension::class, 'testEmpty') ? 'CoreExtension::testEmpty' : 'twig_test_empty' @@ -264,7 +264,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() // Otherwise the default label is overwritten with null. // https://github.com/symfony/symfony/issues/5029 $this->assertEquals( - sprintf( + \sprintf( '$this->env->getRuntime(\'Symfony\Component\Form\FormRenderer\')->searchAndRenderBlock(%s, \'label\', ["foo" => "bar", "label" => "value in attributes"] + (%s($_label_ = ((true) ? (null) : (null))) ? [] : ["label" => $_label_]))', $this->getVariableGetter('form'), method_exists(CoreExtension::class, 'testEmpty') ? 'CoreExtension::testEmpty' : 'twig_test_empty' @@ -275,6 +275,6 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() protected function getVariableGetter($name) { - return sprintf('($context["%s"] ?? null)', $name); + return \sprintf('($context["%s"] ?? null)', $name); } } diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index d1f1114f..0b055cae 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -34,7 +34,7 @@ public function testCompileStrict() $compiler = new Compiler($env); $this->assertEquals( - sprintf( + \sprintf( 'yield $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->trans("trans %%var%%", array_merge(["%%var%%" => %s], %s), "messages");', $this->getVariableGetterWithoutStrictCheck('var'), $this->getVariableGetterWithStrictCheck('foo') @@ -45,11 +45,11 @@ public function testCompileStrict() protected function getVariableGetterWithoutStrictCheck($name) { - return sprintf('($context["%s"] ?? null)', $name); + return \sprintf('($context["%s"] ?? null)', $name); } protected function getVariableGetterWithStrictCheck($name) { - return sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); + return \sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); } } diff --git a/UndefinedCallableHandler.php b/UndefinedCallableHandler.php index c9f502f6..4e63c283 100644 --- a/UndefinedCallableHandler.php +++ b/UndefinedCallableHandler.php @@ -115,7 +115,7 @@ public static function onUndefinedFunction(string $name): TwigFunction|false private static function onUndefined(string $name, string $type, string $component): string { if (class_exists(FullStack::class) && isset(self::FULL_STACK_ENABLE[$component])) { - return sprintf('Did you forget to %s? Unknown %s "%s".', self::FULL_STACK_ENABLE[$component], $type, $name); + return \sprintf('Did you forget to %s? Unknown %s "%s".', self::FULL_STACK_ENABLE[$component], $type, $name); } $missingPackage = 'symfony/'.$component; @@ -124,6 +124,6 @@ private static function onUndefined(string $name, string $type, string $componen $missingPackage = 'symfony/twig-bundle'; } - return sprintf('Did you forget to run "composer require %s"? Unknown %s "%s".', $missingPackage, $type, $name); + return \sprintf('Did you forget to run "composer require %s"? Unknown %s "%s".', $missingPackage, $type, $name); } } From a971fb90d6a700a449d81120486c6c686cff356a Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 16 Jun 2024 17:17:26 +0200 Subject: [PATCH 03/34] chore: CS fixes --- Extension/EmojiExtension.php | 2 +- .../AbstractBootstrap3HorizontalLayoutTestCase.php | 6 +++--- .../AbstractBootstrap4HorizontalLayoutTestCase.php | 6 +++--- Tests/Extension/AbstractDivLayoutTestCase.php | 6 +++--- Tests/Extension/AbstractLayoutTestCase.php | 14 +++++++------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Extension/EmojiExtension.php b/Extension/EmojiExtension.php index dcc5a5a0..c98a3aac 100644 --- a/Extension/EmojiExtension.php +++ b/Extension/EmojiExtension.php @@ -38,7 +38,7 @@ public function getFilters(): array } /** - * Converts emoji short code (:wave:) to real emoji (👋) + * Converts emoji short code (:wave:) to real emoji (👋). */ public function emojify(string $string, ?string $catalog = null): string { diff --git a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php index 3a4104bb..db0789db 100644 --- a/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3HorizontalLayoutTestCase.php @@ -163,9 +163,9 @@ public function testStartTagWithOverriddenVars() public function testStartTagForMultipartForm() { $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'method' => 'get', - 'action' => 'http://example.com/directory', - ]) + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]) ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') ->getForm(); diff --git a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php index 723559ee..9b202e92 100644 --- a/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap4HorizontalLayoutTestCase.php @@ -214,9 +214,9 @@ public function testStartTagWithOverriddenVars() public function testStartTagForMultipartForm() { $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'method' => 'get', - 'action' => 'http://example.com/directory', - ]) + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]) ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') ->getForm(); diff --git a/Tests/Extension/AbstractDivLayoutTestCase.php b/Tests/Extension/AbstractDivLayoutTestCase.php index a02fca4b..ede4d695 100644 --- a/Tests/Extension/AbstractDivLayoutTestCase.php +++ b/Tests/Extension/AbstractDivLayoutTestCase.php @@ -691,9 +691,9 @@ public function testCollectionRowWithCustomBlock() public function testChoiceRowWithCustomBlock() { $form = $this->factory->createNamedBuilder('name_c', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', 'a', [ - 'choices' => ['ChoiceA' => 'a', 'ChoiceB' => 'b'], - 'expanded' => true, - ]) + 'choices' => ['ChoiceA' => 'a', 'ChoiceB' => 'b'], + 'expanded' => true, + ]) ->getForm(); $this->assertWidgetMatchesXpath($form->createView(), [], diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index f340b066..f61fefb3 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -313,8 +313,8 @@ public function testLabelFormatOverriddenOption() public function testLabelWithoutTranslationOnButton() { $form = $this->factory->createNamedBuilder('myform', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'translation_domain' => false, - ]) + 'translation_domain' => false, + ]) ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType') ->getForm(); $view = $form->get('mybutton')->createView(); @@ -2412,9 +2412,9 @@ public function testStartTagWithOverriddenVars() public function testStartTagForMultipartForm() { $form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'method' => 'get', - 'action' => 'http://example.com/directory', - ]) + 'method' => 'get', + 'action' => 'http://example.com/directory', + ]) ->add('file', 'Symfony\Component\Form\Extension\Core\Type\FileType') ->getForm(); @@ -2559,8 +2559,8 @@ public function testTranslatedAttributes() public function testAttributesNotTranslatedWhenTranslationDomainIsFalse() { $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType', null, [ - 'translation_domain' => false, - ]) + 'translation_domain' => false, + ]) ->add('firstName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['title' => 'Foo']]) ->add('lastName', 'Symfony\Component\Form\Extension\Core\Type\TextType', ['attr' => ['placeholder' => 'Bar']]) ->getForm() From 9d8c235ca99ea15cb5d5018e02751a133fbacedc Mon Sep 17 00:00:00 2001 From: Christophe Coevoet Date: Tue, 25 Jun 2024 14:58:00 +0200 Subject: [PATCH 04/34] Add more precise types in reusable test cases --- Test/FormLayoutTestCase.php | 13 +++++++++++++ Tests/Extension/AbstractLayoutTestCase.php | 4 ++++ Tests/Extension/FormExtensionFieldHelpersTest.php | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Test/FormLayoutTestCase.php b/Test/FormLayoutTestCase.php index 0c719efc..bd8123a3 100644 --- a/Test/FormLayoutTestCase.php +++ b/Test/FormLayoutTestCase.php @@ -19,6 +19,7 @@ use Symfony\Component\Form\Test\FormIntegrationTestCase; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Twig\Environment; +use Twig\Extension\ExtensionInterface; use Twig\Loader\FilesystemLoader; /** @@ -81,15 +82,27 @@ protected function assertMatchesXpath($html, $expression, $count = 1): void } } + /** + * @return string[] + */ abstract protected function getTemplatePaths(): array; + /** + * @return ExtensionInterface[] + */ abstract protected function getTwigExtensions(): array; + /** + * @return array + */ protected function getTwigGlobals(): array { return []; } + /** + * @return string[] + */ abstract protected function getThemes(): array; protected function renderForm(FormView $view, array $vars = []): string diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index f61fefb3..0053f7ca 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -17,6 +17,7 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormExtensionInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Tests\VersionAwareTest; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -47,6 +48,9 @@ protected function setUp(): void parent::setUp(); } + /** + * @return FormExtensionInterface[] + */ protected function getExtensions() { return [ diff --git a/Tests/Extension/FormExtensionFieldHelpersTest.php b/Tests/Extension/FormExtensionFieldHelpersTest.php index 8e2c0298..c463eaac 100644 --- a/Tests/Extension/FormExtensionFieldHelpersTest.php +++ b/Tests/Extension/FormExtensionFieldHelpersTest.php @@ -26,7 +26,7 @@ class FormExtensionFieldHelpersTest extends FormIntegrationTestCase private FormExtension $translatorExtension; private FormView $view; - protected function getTypes() + protected function getTypes(): array { return [new TextType(), new ChoiceType()]; } From 0f0769a91c7237419739b19f387e6a114742b342 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 25 Jun 2024 08:08:32 +0200 Subject: [PATCH 05/34] Unify how --format is handle by commands --- Command/DebugCommand.php | 15 +++++++++++---- Command/LintCommand.php | 5 ++++- Tests/Command/DebugCommandTest.php | 2 +- composer.json | 1 + 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 42b456c2..0e92e3e5 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -57,7 +57,7 @@ protected function configure(): void ->setDefinition([ new InputArgument('name', InputArgument::OPTIONAL, 'The template name'), new InputOption('filter', null, InputOption::VALUE_REQUIRED, 'Show details for all entries matching this filter'), - new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'text'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, \sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())), 'txt'), ]) ->setHelp(<<<'EOF' The %command.name% command outputs a list of twig functions, @@ -93,8 +93,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new InvalidArgumentException(\sprintf('Argument "name" not supported, it requires the Twig loader "%s".', FilesystemLoader::class)); } - match ($input->getOption('format')) { - 'text' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), + $format = $input->getOption('format'); + if ('text' === $format) { + trigger_deprecation('symfony/twig-bridge', '7.2', 'The "text" format is deprecated, use "txt" instead.'); + + $format = 'txt'; + } + match ($format) { + 'txt' => $name ? $this->displayPathsText($io, $name) : $this->displayGeneralText($io, $filter), 'json' => $name ? $this->displayPathsJson($io, $name) : $this->displayGeneralJson($io, $filter), default => throw new InvalidArgumentException(\sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))), }; @@ -582,8 +588,9 @@ private function getFileLink(string $absolutePath): string return (string) $this->fileLinkFormatter?->format($absolutePath, 1); } + /** @return string[] */ private function getAvailableFormatOptions(): array { - return ['text', 'json']; + return ['txt', 'json']; } } diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 456c1864..96b37c77 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -71,8 +71,10 @@ protected function configure(): void Or of a whole directory: php %command.full_name% dirname - php %command.full_name% dirname --format=json +The --format option specifies the format of the command output: + + php %command.full_name% dirname --format=json EOF ) ; @@ -280,6 +282,7 @@ public function complete(CompletionInput $input, CompletionSuggestions $suggesti } } + /** @return string[] */ private function getAvailableFormatOptions(): array { return ['txt', 'json', 'github']; diff --git a/Tests/Command/DebugCommandTest.php b/Tests/Command/DebugCommandTest.php index 8a67932f..7ba828c6 100644 --- a/Tests/Command/DebugCommandTest.php +++ b/Tests/Command/DebugCommandTest.php @@ -314,7 +314,7 @@ public function testComplete(array $input, array $expectedSuggestions) public static function provideCompletionSuggestions(): iterable { yield 'name' => [['email'], []]; - yield 'option --format' => [['--format', ''], ['text', 'json']]; + yield 'option --format' => [['--format', ''], ['txt', 'json']]; } private function createCommandTester(array $paths = [], array $bundleMetadata = [], ?string $defaultPath = null, bool $useChainLoader = false, array $globals = []): CommandTester diff --git a/composer.json b/composer.json index f7f8d32d..b707dab2 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^3.9" }, From 9a8b5f195826e92219416e1966b47682c2ec9d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 1 Jul 2024 00:59:34 +0200 Subject: [PATCH 06/34] Remove random file name for lint:twig output in case of error Before: ERROR in sf_6681e2dd64a2e2.02015003 (line -1) After: ERROR in Standard Input (line -1) --- Command/LintCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Command/LintCommand.php b/Command/LintCommand.php index 96b37c77..54720952 100644 --- a/Command/LintCommand.php +++ b/Command/LintCommand.php @@ -89,7 +89,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt'); if (['-'] === $filenames) { - return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), uniqid('sf_', true))]); + return $this->display($input, $output, $io, [$this->validate(file_get_contents('php://stdin'), 'Standard Input')]); } if (!$filenames) { From 4b6c870e0705743e0877d4b3f1f9a6ce6449ff6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 1 Jul 2024 01:28:28 +0200 Subject: [PATCH 07/34] Use constant var name to cache trans_default_domain expression result --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index a9266066..3faff33c 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -30,6 +30,8 @@ */ final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface { + private const INTERNAL_VAR_NAME = '__internal_trans_default_domain'; + private Scope $scope; public function __construct() @@ -49,9 +51,8 @@ public function enterNode(Node $node, Environment $env): Node return $node; } else { - $var = $this->getVarName(); - $name = new AssignNameExpression($var, $node->getTemplateLine()); - $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); + $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } @@ -111,9 +112,4 @@ private function isNamedArguments(Node $arguments): bool return false; } - - private function getVarName(): string - { - return \sprintf('__internal_%s', hash('xxh128', uniqid(mt_rand(), true))); - } } From d09d344d9400a5f94e344d6fe2c4637b4b31035b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 6 Jul 2024 09:57:16 +0200 Subject: [PATCH 08/34] Update .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 84c7add0..14c3c359 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore From 04f930f07662f6e8889685f706e1427d684bbbc6 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Fri, 26 Jul 2024 13:12:13 +0200 Subject: [PATCH 09/34] [Form] Deprecate VersionAwareTest trait --- .../AbstractBootstrap3LayoutTestCase.php | 8 ---- .../AbstractBootstrap5LayoutTestCase.php | 2 - Tests/Extension/AbstractLayoutTestCase.php | 37 ------------------- 3 files changed, 47 deletions(-) diff --git a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php index 08a026fe..a25bd542 100644 --- a/Tests/Extension/AbstractBootstrap3LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap3LayoutTestCase.php @@ -2869,8 +2869,6 @@ public function testColor() public function testWeekSingleText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2889,8 +2887,6 @@ public function testWeekSingleText() public function testWeekSingleTextNoHtml5() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2910,8 +2906,6 @@ public function testWeekSingleTextNoHtml5() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ @@ -2938,8 +2932,6 @@ public function testWeekChoices() public function testWeekText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ 'input' => 'string', 'widget' => 'text', diff --git a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php index 576f2b18..4b3aa7fa 100644 --- a/Tests/Extension/AbstractBootstrap5LayoutTestCase.php +++ b/Tests/Extension/AbstractBootstrap5LayoutTestCase.php @@ -1842,8 +1842,6 @@ public function testTimezoneWithPlaceholder() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', WeekType::class, $data, [ diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index 0053f7ca..859566a7 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -19,7 +19,6 @@ use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormExtensionInterface; use Symfony\Component\Form\FormView; -use Symfony\Component\Form\Tests\VersionAwareTest; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatableMessage; use Symfony\Contracts\Translation\TranslatableInterface; @@ -27,8 +26,6 @@ abstract class AbstractLayoutTestCase extends FormLayoutTestCase { - use VersionAwareTest; - protected MockObject&CsrfTokenManagerInterface $csrfTokenManager; protected array $testableFeatures = []; @@ -676,8 +673,6 @@ public function testSingleExpandedChoiceAttributesWithMainAttributes() public function testSingleChoiceWithPreferred() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -702,8 +697,6 @@ public function testSingleChoiceWithPreferred() public function testSingleChoiceWithPreferredAndNoSeparator() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -727,8 +720,6 @@ public function testSingleChoiceWithPreferredAndNoSeparator() public function testSingleChoiceWithPreferredAndBlankSeparator() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&b'], @@ -753,8 +744,6 @@ public function testSingleChoiceWithPreferredAndBlankSeparator() public function testChoiceWithOnlyPreferred() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&a', [ 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b'], 'preferred_choices' => ['&a', '&b'], @@ -1818,8 +1807,6 @@ public function testNumber() public function testRenderNumberWithHtml5NumberType() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ 'html5' => true, ]); @@ -1836,8 +1823,6 @@ public function testRenderNumberWithHtml5NumberType() public function testRenderNumberWithHtml5NumberTypeAndStepAttribute() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\NumberType', 1234.56, [ 'html5' => true, 'attr' => ['step' => '0.1'], @@ -1912,8 +1897,6 @@ public function testPercent() public function testPercentNoSymbol() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => false, 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); $this->assertWidgetMatchesXpath($form->createView(), [], '/input @@ -1927,8 +1910,6 @@ public function testPercentNoSymbol() public function testPercentCustomSymbol() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', PercentType::class, 0.1, ['symbol' => '‱', 'rounding_mode' => \NumberFormatter::ROUND_CEILING]); $this->assertWidgetMatchesXpath($form->createView(), [], '/input @@ -2606,8 +2587,6 @@ public function testColor() public function testLabelWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType'); $html = $this->renderLabel($form->createView(), 'Address is %address%', [ 'label_translation_parameters' => [ @@ -2625,8 +2604,6 @@ public function testLabelWithTranslationParameters() public function testHelpWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ 'help' => 'for company %company%', 'help_translation_parameters' => [ @@ -2697,8 +2674,6 @@ public function trans(TranslatorInterface $translator, ?string $locale = null): public function testAttributesWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, [ 'attr' => [ 'title' => 'Message to %company%', @@ -2720,8 +2695,6 @@ public function testAttributesWithTranslationParameters() public function testButtonWithTranslationParameters() { - $this->requiresFeatureSet(403); - $form = $this->factory->createNamedBuilder('myform') ->add('mybutton', 'Symfony\Component\Form\Extension\Core\Type\ButtonType', [ 'label' => 'Submit to %company%', @@ -2745,8 +2718,6 @@ public function testButtonWithTranslationParameters() */ public function testSubmitFormNoValidate(bool $validate) { - $this->requiresFeatureSet(404); - $form = $this->factory->create(SubmitType::class, null, [ 'validate' => $validate, ]); @@ -2776,8 +2747,6 @@ public static function submitFormNoValidateProvider() public function testWeekSingleText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2795,8 +2764,6 @@ public function testWeekSingleText() public function testWeekSingleTextNoHtml5() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('holidays', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '1970-W01', [ 'input' => 'string', 'widget' => 'single_text', @@ -2815,8 +2782,6 @@ public function testWeekSingleTextNoHtml5() public function testWeekChoices() { - $this->requiresFeatureSet(404); - $data = ['year' => (int) date('Y'), 'week' => 1]; $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', $data, [ @@ -2841,8 +2806,6 @@ public function testWeekChoices() public function testWeekText() { - $this->requiresFeatureSet(404); - $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\WeekType', '2000-W01', [ 'input' => 'string', 'widget' => 'text', From 38030470f1e02aaf1000851e62564bd6f29ab57a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 31 Jul 2024 16:13:26 +0200 Subject: [PATCH 10/34] Remove unused code and unnecessary `else` branches --- Command/DebugCommand.php | 4 +--- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 10 +++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 0e92e3e5..d0aded57 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -344,15 +344,13 @@ private function getMetadata(string $type, mixed $entity): mixed } // format args - $args = array_map(function (\ReflectionParameter $param) { + return array_map(function (\ReflectionParameter $param) { if ($param->isDefaultValueAvailable()) { return $param->getName().' = '.json_encode($param->getDefaultValue()); } return $param->getName(); }, $args); - - return $args; } return null; diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 3faff33c..c76b7808 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -50,12 +50,12 @@ public function enterNode(Node $node, Environment $env): Node $this->scope->set('domain', $node->getNode('expr')); return $node; - } else { - $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); - $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); - - return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } + + $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); + + return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } if (!$this->scope->has('domain')) { From ac122b6d5423cb2463162a716eaa912993345fbd Mon Sep 17 00:00:00 2001 From: Roy de Vos Burchart Date: Thu, 1 Aug 2024 17:21:17 +0200 Subject: [PATCH 11/34] Code style change in `@PER-CS2.0` affecting `@Symfony` (parentheses for anonymous classes) --- Extension/TranslationExtension.php | 2 +- Tests/Extension/AbstractLayoutTestCase.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Extension/TranslationExtension.php b/Extension/TranslationExtension.php index 1958aebe..73c9ec85 100644 --- a/Extension/TranslationExtension.php +++ b/Extension/TranslationExtension.php @@ -47,7 +47,7 @@ public function getTranslator(): TranslatorInterface throw new \LogicException(\sprintf('You cannot use the "%s" if the Translation Contracts are not available. Try running "composer require symfony/translation".', __CLASS__)); } - $this->translator = new class() implements TranslatorInterface { + $this->translator = new class implements TranslatorInterface { use TranslatorTrait; }; } diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index 859566a7..a0c9ed40 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -2652,7 +2652,7 @@ public function testHelpWithTranslatableMessage() public function testHelpWithTranslatableInterface() { - $message = new class() implements TranslatableInterface { + $message = new class implements TranslatableInterface { public function trans(TranslatorInterface $translator, ?string $locale = null): string { return $translator->trans('foo'); From a0d160765e16aa6810c347866e48e3cde3247895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sat, 17 Aug 2024 09:41:53 +0200 Subject: [PATCH 12/34] [TwigBridge] Render a `block` via the `#[Template]` attribute --- Attribute/Template.php | 2 ++ EventListener/TemplateAttributeListener.php | 12 ++++++-- .../TemplateAttributeListenerTest.php | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Attribute/Template.php b/Attribute/Template.php index e265e239..ef2f193b 100644 --- a/Attribute/Template.php +++ b/Attribute/Template.php @@ -21,11 +21,13 @@ class Template * @param string $template The name of the template to render * @param string[]|null $vars The controller method arguments to pass to the template * @param bool $stream Enables streaming the template + * @param string|null $block The name of the block to use in the template */ public function __construct( public string $template, public ?array $vars = null, public bool $stream = false, + public ?string $block = null, ) { } } diff --git a/EventListener/TemplateAttributeListener.php b/EventListener/TemplateAttributeListener.php index f5962deb..7220f4c4 100644 --- a/EventListener/TemplateAttributeListener.php +++ b/EventListener/TemplateAttributeListener.php @@ -55,8 +55,16 @@ public function onKernelView(ViewEvent $event): void } $event->setResponse($attribute->stream - ? new StreamedResponse(fn () => $this->twig->display($attribute->template, $parameters), $status) - : new Response($this->twig->render($attribute->template, $parameters), $status) + ? new StreamedResponse( + null !== $attribute->block + ? fn () => $this->twig->load($attribute->template)->displayBlock($attribute->block, $parameters) + : fn () => $this->twig->display($attribute->template, $parameters), + $status) + : new Response( + null !== $attribute->block + ? $this->twig->load($attribute->template)->renderBlock($attribute->block, $parameters) + : $this->twig->render($attribute->template, $parameters), + $status) ); } diff --git a/Tests/EventListener/TemplateAttributeListenerTest.php b/Tests/EventListener/TemplateAttributeListenerTest.php index e1fb7f95..478f285e 100644 --- a/Tests/EventListener/TemplateAttributeListenerTest.php +++ b/Tests/EventListener/TemplateAttributeListenerTest.php @@ -17,10 +17,12 @@ use Symfony\Bridge\Twig\Tests\Fixtures\TemplateAttributeController; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ViewEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Twig\Environment; +use Twig\Loader\ArrayLoader; class TemplateAttributeListenerTest extends TestCase { @@ -65,6 +67,33 @@ public function testAttribute() $this->assertSame('Bar', $event->getResponse()->getContent()); } + public function testAttributeWithBlock() + { + $twig = new Environment(new ArrayLoader([ + 'foo.html.twig' => 'ERROR {% block bar %}FOOBAR{% endblock %}', + ])); + + $request = new Request(); + $kernel = $this->createMock(HttpKernelInterface::class); + $controllerArgumentsEvent = new ControllerArgumentsEvent($kernel, [new TemplateAttributeController(), 'foo'], ['Bar'], $request, null); + $listener = new TemplateAttributeListener($twig); + + $request->attributes->set('_template', new Template('foo.html.twig', [], false, 'bar')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertSame('FOOBAR', $event->getResponse()->getContent()); + + $request->attributes->set('_template', new Template('foo.html.twig', [], true, 'bar')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $listener->onKernelView($event); + $this->assertInstanceOf(StreamedResponse::class, $event->getResponse()); + + $request->attributes->set('_template', new Template('foo.html.twig', [], false, 'not_a_block')); + $event = new ViewEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, ['foo' => 'bar'], $controllerArgumentsEvent); + $this->expectExceptionMessage('Block "not_a_block" on template "foo.html.twig" does not exist in "foo.html.twig".'); + $listener->onKernelView($event); + } + public function testForm() { $request = new Request(); From 0f55794a3bf48087cd11837b3d8918c655d8ee78 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Thu, 29 Aug 2024 10:25:47 +0200 Subject: [PATCH 13/34] bump requirement for Twig to 3.12+ --- CHANGELOG.md | 5 ++ Node/DumpNode.php | 8 +-- Node/FormThemeNode.php | 17 ++++-- Node/StopwatchNode.php | 9 +-- Node/TransDefaultDomainNode.php | 9 +-- Node/TransNode.php | 9 +-- Tests/Node/FormThemeTest.php | 4 +- Tests/Node/SearchAndRenderBlockNodeTest.php | 61 +++---------------- .../TranslationNodeVisitorTest.php | 32 +++------- Tests/NodeVisitor/TwigNodeProvider.php | 10 --- .../TokenParser/FormThemeTokenParserTest.php | 22 ++----- TokenParser/FormThemeTokenParser.php | 2 +- composer.json | 2 +- 13 files changed, 52 insertions(+), 138 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df8f28f0..b18e2745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.2 +--- + + * Deprecate passing a tag to the constructor of `FormThemeNode` + 7.1 --- diff --git a/Node/DumpNode.php b/Node/DumpNode.php index 1367655c..23ebf361 100644 --- a/Node/DumpNode.php +++ b/Node/DumpNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Node; @@ -26,18 +25,13 @@ public function __construct( private string $varPrefix, ?Node $values, int $lineno, - ?string $tag = null, ) { $nodes = []; if (null !== $values) { $nodes['values'] = $values; } - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct($nodes, [], $lineno); - } else { - parent::__construct($nodes, [], $lineno, $tag); - } + parent::__construct($nodes, [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/FormThemeNode.php b/Node/FormThemeNode.php index 1d077097..b9ca29a5 100644 --- a/Node/FormThemeNode.php +++ b/Node/FormThemeNode.php @@ -12,7 +12,6 @@ namespace Symfony\Bridge\Twig\Node; use Symfony\Component\Form\FormRenderer; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Node; @@ -23,13 +22,19 @@ #[YieldReady] final class FormThemeNode extends Node { - public function __construct(Node $form, Node $resources, int $lineno, ?string $tag = null, bool $only = false) + /** + * @param bool $only + */ + public function __construct(Node $form, Node $resources, int $lineno, $only = false) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno); - } else { - parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno, $tag); + if (null === $only || \is_string($only)) { + trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); + $only = \func_num_args() > 4 ? func_get_arg(4) : true; + } elseif (!\is_bool($only)) { + throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($only))); } + + parent::__construct(['form' => $form, 'resources' => $resources], ['only' => $only], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/StopwatchNode.php b/Node/StopwatchNode.php index 239d1ca6..55085f2f 100644 --- a/Node/StopwatchNode.php +++ b/Node/StopwatchNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AssignNameExpression; @@ -25,13 +24,9 @@ #[YieldReady] final class StopwatchNode extends Node { - public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0, ?string $tag = null) + public function __construct(Node $name, Node $body, AssignNameExpression $var, int $lineno = 0) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); - } else { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); - } + parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/TransDefaultDomainNode.php b/Node/TransDefaultDomainNode.php index 28cb6f1b..04349839 100644 --- a/Node/TransDefaultDomainNode.php +++ b/Node/TransDefaultDomainNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -23,13 +22,9 @@ #[YieldReady] final class TransDefaultDomainNode extends Node { - public function __construct(AbstractExpression $expr, int $lineno = 0, ?string $tag = null) + public function __construct(AbstractExpression $expr, int $lineno = 0) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['expr' => $expr], [], $lineno); - } else { - parent::__construct(['expr' => $expr], [], $lineno, $tag); - } + parent::__construct(['expr' => $expr], [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Node/TransNode.php b/Node/TransNode.php index 42126399..525f2373 100644 --- a/Node/TransNode.php +++ b/Node/TransNode.php @@ -11,7 +11,6 @@ namespace Symfony\Bridge\Twig\Node; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Attribute\YieldReady; use Twig\Compiler; use Twig\Node\Expression\AbstractExpression; @@ -27,7 +26,7 @@ #[YieldReady] final class TransNode extends Node { - public function __construct(Node $body, ?Node $domain = null, ?AbstractExpression $count = null, ?AbstractExpression $vars = null, ?AbstractExpression $locale = null, int $lineno = 0, ?string $tag = null) + public function __construct(Node $body, ?Node $domain = null, ?AbstractExpression $count = null, ?AbstractExpression $vars = null, ?AbstractExpression $locale = null, int $lineno = 0) { $nodes = ['body' => $body]; if (null !== $domain) { @@ -43,11 +42,7 @@ public function __construct(Node $body, ?Node $domain = null, ?AbstractExpressio $nodes['locale'] = $locale; } - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct($nodes, [], $lineno); - } else { - parent::__construct($nodes, [], $lineno, $tag); - } + parent::__construct($nodes, [], $lineno); } public function compile(Compiler $compiler): void diff --git a/Tests/Node/FormThemeTest.php b/Tests/Node/FormThemeTest.php index ec95e352..ff1dad47 100644 --- a/Tests/Node/FormThemeTest.php +++ b/Tests/Node/FormThemeTest.php @@ -68,7 +68,7 @@ public function testCompile() trim($compiler->compile($node)->getSource()) ); - $node = new FormThemeNode($form, $resources, 0, null, true); + $node = new FormThemeNode($form, $resources, 0, true); $this->assertEquals( \sprintf( @@ -90,7 +90,7 @@ public function testCompile() trim($compiler->compile($node)->getSource()) ); - $node = new FormThemeNode($form, $resources, 0, null, true); + $node = new FormThemeNode($form, $resources, 0, true); $this->assertEquals( \sprintf( diff --git a/Tests/Node/SearchAndRenderBlockNodeTest.php b/Tests/Node/SearchAndRenderBlockNodeTest.php index a0da4317..cf960c09 100644 --- a/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Compiler; use Twig\Environment; use Twig\Extension\CoreExtension; @@ -33,11 +32,7 @@ public function testCompileWidget() new NameExpression('form', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_widget', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -60,11 +55,7 @@ public function testCompileWidgetWithVariables() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_widget', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_widget'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -84,11 +75,7 @@ public function testCompileLabelWithLabel() new ConstantExpression('my label', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -108,11 +95,7 @@ public function testCompileLabelWithNullLabel() new ConstantExpression(null, 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -134,11 +117,7 @@ public function testCompileLabelWithEmptyStringLabel() new ConstantExpression('', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -159,11 +138,7 @@ public function testCompileLabelWithDefaultLabel() new NameExpression('form', 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -187,11 +162,7 @@ public function testCompileLabelWithAttributes() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -220,11 +191,7 @@ public function testCompileLabelWithLabelAndAttributes() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -252,11 +219,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() ), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); @@ -294,11 +257,7 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() ], 0), ]); - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); - } else { - $node = new SearchAndRenderBlockNode('form_label', $arguments, 0); - } + $node = new SearchAndRenderBlockNode(new TwigFunction('form_label'), $arguments, 0); $compiler = new Compiler(new Environment($this->createMock(LoaderInterface::class))); diff --git a/Tests/NodeVisitor/TranslationNodeVisitorTest.php b/Tests/NodeVisitor/TranslationNodeVisitorTest.php index be26c9b4..873e7f36 100644 --- a/Tests/NodeVisitor/TranslationNodeVisitorTest.php +++ b/Tests/NodeVisitor/TranslationNodeVisitorTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Environment; use Twig\Loader\LoaderInterface; use Twig\Node\Expression\ArrayExpression; @@ -22,7 +21,6 @@ use Twig\Node\Expression\NameExpression; use Twig\Node\Node; use Twig\TwigFilter; -use Twig\TwigFunction; class TranslationNodeVisitorTest extends TestCase { @@ -41,27 +39,15 @@ public function testMessageExtractionWithInvalidDomainNode() { $message = 'new key'; - if (class_exists(FirstClassTwigCallableReady::class)) { - $node = new FilterExpression( - new ConstantExpression($message, 0), - new TwigFilter('trans'), - new Node([ - new ArrayExpression([], 0), - new NameExpression('variable', 0), - ]), - 0 - ); - } else { - $node = new FilterExpression( - new ConstantExpression($message, 0), - new ConstantExpression('trans', 0), - new Node([ - new ArrayExpression([], 0), - new NameExpression('variable', 0), - ]), - 0 - ); - } + $node = new FilterExpression( + new ConstantExpression($message, 0), + new TwigFilter('trans'), + new Node([ + new ArrayExpression([], 0), + new NameExpression('variable', 0), + ]), + 0 + ); $this->testMessagesExtraction($node, [[$message, TranslationNodeVisitor::UNDEFINED_DOMAIN]]); } diff --git a/Tests/NodeVisitor/TwigNodeProvider.php b/Tests/NodeVisitor/TwigNodeProvider.php index 7a79c341..d8e12988 100644 --- a/Tests/NodeVisitor/TwigNodeProvider.php +++ b/Tests/NodeVisitor/TwigNodeProvider.php @@ -13,7 +13,6 @@ use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; use Symfony\Bridge\Twig\Node\TransNode; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Node\BodyNode; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; @@ -47,15 +46,6 @@ public static function getTransFilter($message, $domain = null, $arguments = nul ] : []; } - if (!class_exists(FirstClassTwigCallableReady::class)) { - return new FilterExpression( - new ConstantExpression($message, 0), - new ConstantExpression('trans', 0), - new Node($arguments), - 0 - ); - } - return new FilterExpression( new ConstantExpression($message, 0), new TwigFilter('trans'), diff --git a/Tests/TokenParser/FormThemeTokenParserTest.php b/Tests/TokenParser/FormThemeTokenParserTest.php index c9c0ce80..35ac52ae 100644 --- a/Tests/TokenParser/FormThemeTokenParserTest.php +++ b/Tests/TokenParser/FormThemeTokenParserTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Node\FormThemeNode; use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; -use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Environment; use Twig\Loader\LoaderInterface; use Twig\Node\Expression\ArrayExpression; @@ -36,10 +35,7 @@ public function testCompile($source, $expected) $stream = $env->tokenize($source); $parser = new Parser($env); - if (class_exists(FirstClassTwigCallableReady::class)) { - $expected->setNodeTag('form_theme'); - } - + $expected->setNodeTag('form_theme'); $expected->setSourceContext($source); $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)); @@ -56,8 +52,7 @@ public static function getTestsForFormTheme() new ConstantExpression(0, 1), new ConstantExpression('tpl1', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -70,8 +65,7 @@ public static function getTestsForFormTheme() new ConstantExpression(1, 1), new ConstantExpression('tpl2', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -79,8 +73,7 @@ public static function getTestsForFormTheme() new FormThemeNode( new NameExpression('form', 1), new ConstantExpression('tpl1', 1), - 1, - 'form_theme' + 1 ), ], [ @@ -91,8 +84,7 @@ public static function getTestsForFormTheme() new ConstantExpression(0, 1), new ConstantExpression('tpl1', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -105,8 +97,7 @@ public static function getTestsForFormTheme() new ConstantExpression(1, 1), new ConstantExpression('tpl2', 1), ], 1), - 1, - 'form_theme' + 1 ), ], [ @@ -120,7 +111,6 @@ public static function getTestsForFormTheme() new ConstantExpression('tpl2', 1), ], 1), 1, - 'form_theme', true ), ], diff --git a/TokenParser/FormThemeTokenParser.php b/TokenParser/FormThemeTokenParser.php index b95a2a05..413a8f51 100644 --- a/TokenParser/FormThemeTokenParser.php +++ b/TokenParser/FormThemeTokenParser.php @@ -48,7 +48,7 @@ public function parse(Token $token): Node $stream->expect(Token::BLOCK_END_TYPE); - return new FormThemeNode($form, $resources, $lineno, $this->getTag(), $only); + return new FormThemeNode($form, $resources, $lineno, $only); } public function getTag(): string diff --git a/composer.json b/composer.json index b707dab2..3af8ccbb 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.9" + "twig/twig": "^3.12" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From 36e7758e536e00e0594929277755811bffe9faec Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 2 Sep 2024 14:20:02 +0200 Subject: [PATCH 14/34] fix package name in deprecation --- Node/FormThemeNode.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Node/FormThemeNode.php b/Node/FormThemeNode.php index b9ca29a5..9d9bce1e 100644 --- a/Node/FormThemeNode.php +++ b/Node/FormThemeNode.php @@ -28,7 +28,7 @@ final class FormThemeNode extends Node public function __construct(Node $form, Node $resources, int $lineno, $only = false) { if (null === $only || \is_string($only)) { - trigger_deprecation('twig/twig', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); + trigger_deprecation('symfony/twig-bridge', '3.12', 'Passing a tag to %s() is deprecated.', __METHOD__); $only = \func_num_args() > 4 ? func_get_arg(4) : true; } elseif (!\is_bool($only)) { throw new \TypeError(\sprintf('Argument 4 passed to "%s()" must be a boolean, "%s" given.', __METHOD__, get_debug_type($only))); From b29f131ce7a8d7f9103ec35a57435daf69d4b791 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 30 Aug 2024 09:35:32 +0200 Subject: [PATCH 15/34] allow Twig 4 --- Tests/Node/TransNodeTest.php | 6 ++++++ composer.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index 0b055cae..35288174 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -18,6 +18,7 @@ use Twig\Loader\LoaderInterface; use Twig\Node\Expression\NameExpression; use Twig\Node\TextNode; +use Twig\Runtime\LoopIterator; /** * @author Asmir Mustafic @@ -50,6 +51,11 @@ protected function getVariableGetterWithoutStrictCheck($name) protected function getVariableGetterWithStrictCheck($name) { + if (class_exists(LoopIterator::class)) { + return \sprintf('(array_key_exists("%1$s", $context) ? $context["%1$s"] : throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source))', $name); + } + + // for Twig 3 and older, can be removed when support for Twig 3 is dropped return \sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); } } diff --git a/composer.json b/composer.json index 3af8ccbb..9d94e1c1 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From 3e12e7fe583d9ec53f8f5608d78d453d30fc7a3e Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 18 Sep 2024 13:33:46 +0200 Subject: [PATCH 16/34] Miscellaneous tests improvements --- Tests/Extension/AbstractDivLayoutTestCase.php | 14 ++++++++++++++ Tests/Extension/FormExtensionDivLayoutTest.php | 14 -------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Tests/Extension/AbstractDivLayoutTestCase.php b/Tests/Extension/AbstractDivLayoutTestCase.php index ede4d695..cfa2c5c6 100644 --- a/Tests/Extension/AbstractDivLayoutTestCase.php +++ b/Tests/Extension/AbstractDivLayoutTestCase.php @@ -619,6 +619,13 @@ public function testThemeBlockInheritance($theme) ); } + public static function themeBlockInheritanceProvider(): array + { + return [ + [['theme.html.twig']], + ]; + } + /** * @dataProvider themeInheritanceProvider */ @@ -663,6 +670,13 @@ public function testThemeInheritance($parentTheme, $childTheme) ); } + public static function themeInheritanceProvider(): array + { + return [ + [['parent_label.html.twig'], ['child_label.html.twig']], + ]; + } + /** * The block "_name_child_label" should be overridden in the theme of the * implemented driver. diff --git a/Tests/Extension/FormExtensionDivLayoutTest.php b/Tests/Extension/FormExtensionDivLayoutTest.php index ad2627a2..d0e90b1f 100644 --- a/Tests/Extension/FormExtensionDivLayoutTest.php +++ b/Tests/Extension/FormExtensionDivLayoutTest.php @@ -349,20 +349,6 @@ protected function setTheme(FormView $view, array $themes, $useDefaultThemes = t $this->renderer->setTheme($view, $themes, $useDefaultThemes); } - public static function themeBlockInheritanceProvider(): array - { - return [ - [['theme.html.twig']], - ]; - } - - public static function themeInheritanceProvider(): array - { - return [ - [['parent_label.html.twig'], ['child_label.html.twig']], - ]; - } - protected function getTemplatePaths(): array { return [ From 1b1fd833b3322227714f4d5d76a3727d2efdb889 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 24 Sep 2024 13:28:07 +0200 Subject: [PATCH 17/34] Remove useless parent method calls in tests --- Tests/Extension/AbstractLayoutTestCase.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index a0c9ed40..fa7fcc95 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -58,8 +58,6 @@ protected function getExtensions() protected function tearDown(): void { \Locale::setDefault($this->defaultLocale); - - parent::tearDown(); } protected function assertWidgetMatchesXpath(FormView $view, array $vars, $xpath) From 1b8dac6ac9e2a2b0dd08e48fa91d4f7ecea78d05 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 3 Oct 2024 14:15:19 +0200 Subject: [PATCH 18/34] Various CS fix for consistency --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index df5bdd9e..5788f539 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -59,9 +59,9 @@ public function enterNode(Node $node, Environment $env): Node if (class_exists(Nodes::class)) { return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); - } else { - return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); - } + } + + return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } if (!$this->scope->has('domain')) { From d072d64871a7eedc9604cebd52682762776d575f Mon Sep 17 00:00:00 2001 From: PHAS Developer <110562019+phasdev@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:23:13 +0000 Subject: [PATCH 19/34] [TwigBridge] Update main.css email stylesheet to latest foundation-emails release --- Resources/views/Email/zurb_2/main.css | 641 +++++++++++++++----------- 1 file changed, 380 insertions(+), 261 deletions(-) diff --git a/Resources/views/Email/zurb_2/main.css b/Resources/views/Email/zurb_2/main.css index dab0df58..7828ce78 100644 --- a/Resources/views/Email/zurb_2/main.css +++ b/Resources/views/Email/zurb_2/main.css @@ -1,7 +1,7 @@ /* * Copyright (c) 2017 ZURB, inc. -- MIT License * - * https://github.com/foundation/foundation-emails/blob/v2.2.1/dist/foundation-emails.css + * https://github.com/foundation/foundation-emails/blob/v2.4.0/dist/foundation-emails.css */ .wrapper { @@ -34,6 +34,7 @@ body { .ExternalClass span, .ExternalClass font, .ExternalClass td, +.ExternalClass th, .ExternalClass div { line-height: 100%; } @@ -58,34 +59,33 @@ img { center { width: 100%; - min-width: 580px; } a img { border: none; } -p { - margin: 0 0 0 10px; - Margin: 0 0 0 10px; -} - table { border-spacing: 0; border-collapse: collapse; } -td { +td, +th { word-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; } table, tr, -td { +td, +th { padding: 0; vertical-align: top; text-align: left; @@ -140,27 +140,38 @@ th.column { padding-bottom: 16px; } -td.columns .column, -td.columns .columns, -td.column .column, -td.column .columns, -th.columns .column, -th.columns .columns, -th.column .column, -th.column .columns { +td.columns .column.first, +td.columns .columns.first, +td.column .column.first, +td.column .columns.first, +th.columns .column.first, +th.columns .columns.first, +th.column .column.first, +th.column .columns.first { padding-left: 0 !important; +} + +td.columns .column.last, +td.columns .columns.last, +td.column .column.last, +td.column .columns.last, +th.columns .column.last, +th.columns .columns.last, +th.column .column.last, +th.column .columns.last { padding-right: 0 !important; } -td.columns .column center, -td.columns .columns center, -td.column .column center, -td.column .columns center, -th.columns .column center, -th.columns .columns center, -th.column .column center, -th.column .columns center { - min-width: none !important; +td.columns .column:not([class*=large-offset]), +td.columns .columns:not([class*=large-offset]), +td.column .column:not([class*=large-offset]), +td.column .columns:not([class*=large-offset]), +th.columns .column:not([class*=large-offset]), +th.columns .columns:not([class*=large-offset]), +th.column .column:not([class*=large-offset]), +th.column .columns:not([class*=large-offset]) { + padding-left: 0 !important; + padding-right: 0 !important; } td.columns.last, @@ -170,16 +181,34 @@ th.column.last { padding-right: 16px; } -td.columns table:not(.button), -td.column table:not(.button), -th.columns table:not(.button), -th.column table:not(.button) { +td.columns table, +td.column table, +th.columns table, +th.column table { + width: 100%; +} + +td.columns table.button, +td.column table.button, +th.columns table.button, +th.column table.button { + width: auto; +} + +td.columns table.button.expand, +td.columns table.button.expanded, +td.column table.button.expand, +td.column table.button.expanded, +th.columns table.button.expand, +th.columns table.button.expanded, +th.column table.button.expand, +th.column table.button.expanded { width: 100%; } td.large-1, th.large-1 { - width: 32.33333px; + width: 32.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -194,35 +223,30 @@ th.large-1.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-1, -.collapse>tbody>tr>th.large-1 { +.collapse>tbody>tr>td.large-1:not([class*=large-offset]), +.collapse>tbody>tr>th.large-1:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 48.33333px; -} - -.collapse td.large-1.first, -.collapse th.large-1.first, -.collapse td.large-1.last, -.collapse th.large-1.last { - width: 56.33333px; + width: 48.3333333333px; } -td.large-1 center, -th.large-1 center { - min-width: 0.33333px; +.collapse>tbody>tr td.large-1.first, +.collapse>tbody>tr th.large-1.first, +.collapse>tbody>tr td.large-1.last, +.collapse>tbody>tr th.large-1.last { + width: 56.3333333333px; } .body .columns td.large-1, .body .column td.large-1, .body .columns th.large-1, .body .column th.large-1 { - width: 8.33333%; + width: 8.333333%; } td.large-2, th.large-2 { - width: 80.66667px; + width: 80.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -237,30 +261,25 @@ th.large-2.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-2, -.collapse>tbody>tr>th.large-2 { +.collapse>tbody>tr>td.large-2:not([class*=large-offset]), +.collapse>tbody>tr>th.large-2:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 96.66667px; + width: 96.6666666667px; } -.collapse td.large-2.first, -.collapse th.large-2.first, -.collapse td.large-2.last, -.collapse th.large-2.last { - width: 104.66667px; -} - -td.large-2 center, -th.large-2 center { - min-width: 48.66667px; +.collapse>tbody>tr td.large-2.first, +.collapse>tbody>tr th.large-2.first, +.collapse>tbody>tr td.large-2.last, +.collapse>tbody>tr th.large-2.last { + width: 104.6666666667px; } .body .columns td.large-2, .body .column td.large-2, .body .columns th.large-2, .body .column th.large-2 { - width: 16.66667%; + width: 16.666666%; } td.large-3, @@ -280,25 +299,20 @@ th.large-3.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-3, -.collapse>tbody>tr>th.large-3 { +.collapse>tbody>tr>td.large-3:not([class*=large-offset]), +.collapse>tbody>tr>th.large-3:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 145px; } -.collapse td.large-3.first, -.collapse th.large-3.first, -.collapse td.large-3.last, -.collapse th.large-3.last { +.collapse>tbody>tr td.large-3.first, +.collapse>tbody>tr th.large-3.first, +.collapse>tbody>tr td.large-3.last, +.collapse>tbody>tr th.large-3.last { width: 153px; } -td.large-3 center, -th.large-3 center { - min-width: 97px; -} - .body .columns td.large-3, .body .column td.large-3, .body .columns th.large-3, @@ -308,7 +322,7 @@ th.large-3 center { td.large-4, th.large-4 { - width: 177.33333px; + width: 177.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -323,35 +337,30 @@ th.large-4.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-4, -.collapse>tbody>tr>th.large-4 { +.collapse>tbody>tr>td.large-4:not([class*=large-offset]), +.collapse>tbody>tr>th.large-4:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 193.33333px; -} - -.collapse td.large-4.first, -.collapse th.large-4.first, -.collapse td.large-4.last, -.collapse th.large-4.last { - width: 201.33333px; + width: 193.3333333333px; } -td.large-4 center, -th.large-4 center { - min-width: 145.33333px; +.collapse>tbody>tr td.large-4.first, +.collapse>tbody>tr th.large-4.first, +.collapse>tbody>tr td.large-4.last, +.collapse>tbody>tr th.large-4.last { + width: 201.3333333333px; } .body .columns td.large-4, .body .column td.large-4, .body .columns th.large-4, .body .column th.large-4 { - width: 33.33333%; + width: 33.333333%; } td.large-5, th.large-5 { - width: 225.66667px; + width: 225.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -366,30 +375,25 @@ th.large-5.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-5, -.collapse>tbody>tr>th.large-5 { +.collapse>tbody>tr>td.large-5:not([class*=large-offset]), +.collapse>tbody>tr>th.large-5:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 241.66667px; + width: 241.6666666667px; } -.collapse td.large-5.first, -.collapse th.large-5.first, -.collapse td.large-5.last, -.collapse th.large-5.last { - width: 249.66667px; -} - -td.large-5 center, -th.large-5 center { - min-width: 193.66667px; +.collapse>tbody>tr td.large-5.first, +.collapse>tbody>tr th.large-5.first, +.collapse>tbody>tr td.large-5.last, +.collapse>tbody>tr th.large-5.last { + width: 249.6666666667px; } .body .columns td.large-5, .body .column td.large-5, .body .columns th.large-5, .body .column th.large-5 { - width: 41.66667%; + width: 41.666666%; } td.large-6, @@ -409,25 +413,20 @@ th.large-6.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-6, -.collapse>tbody>tr>th.large-6 { +.collapse>tbody>tr>td.large-6:not([class*=large-offset]), +.collapse>tbody>tr>th.large-6:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 290px; } -.collapse td.large-6.first, -.collapse th.large-6.first, -.collapse td.large-6.last, -.collapse th.large-6.last { +.collapse>tbody>tr td.large-6.first, +.collapse>tbody>tr th.large-6.first, +.collapse>tbody>tr td.large-6.last, +.collapse>tbody>tr th.large-6.last { width: 298px; } -td.large-6 center, -th.large-6 center { - min-width: 242px; -} - .body .columns td.large-6, .body .column td.large-6, .body .columns th.large-6, @@ -437,7 +436,7 @@ th.large-6 center { td.large-7, th.large-7 { - width: 322.33333px; + width: 322.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -452,35 +451,30 @@ th.large-7.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-7, -.collapse>tbody>tr>th.large-7 { +.collapse>tbody>tr>td.large-7:not([class*=large-offset]), +.collapse>tbody>tr>th.large-7:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 338.33333px; -} - -.collapse td.large-7.first, -.collapse th.large-7.first, -.collapse td.large-7.last, -.collapse th.large-7.last { - width: 346.33333px; + width: 338.3333333333px; } -td.large-7 center, -th.large-7 center { - min-width: 290.33333px; +.collapse>tbody>tr td.large-7.first, +.collapse>tbody>tr th.large-7.first, +.collapse>tbody>tr td.large-7.last, +.collapse>tbody>tr th.large-7.last { + width: 346.3333333333px; } .body .columns td.large-7, .body .column td.large-7, .body .columns th.large-7, .body .column th.large-7 { - width: 58.33333%; + width: 58.333333%; } td.large-8, th.large-8 { - width: 370.66667px; + width: 370.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -495,30 +489,25 @@ th.large-8.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-8, -.collapse>tbody>tr>th.large-8 { +.collapse>tbody>tr>td.large-8:not([class*=large-offset]), +.collapse>tbody>tr>th.large-8:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 386.66667px; -} - -.collapse td.large-8.first, -.collapse th.large-8.first, -.collapse td.large-8.last, -.collapse th.large-8.last { - width: 394.66667px; + width: 386.6666666667px; } -td.large-8 center, -th.large-8 center { - min-width: 338.66667px; +.collapse>tbody>tr td.large-8.first, +.collapse>tbody>tr th.large-8.first, +.collapse>tbody>tr td.large-8.last, +.collapse>tbody>tr th.large-8.last { + width: 394.6666666667px; } .body .columns td.large-8, .body .column td.large-8, .body .columns th.large-8, .body .column th.large-8 { - width: 66.66667%; + width: 66.666666%; } td.large-9, @@ -538,25 +527,20 @@ th.large-9.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-9, -.collapse>tbody>tr>th.large-9 { +.collapse>tbody>tr>td.large-9:not([class*=large-offset]), +.collapse>tbody>tr>th.large-9:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 435px; } -.collapse td.large-9.first, -.collapse th.large-9.first, -.collapse td.large-9.last, -.collapse th.large-9.last { +.collapse>tbody>tr td.large-9.first, +.collapse>tbody>tr th.large-9.first, +.collapse>tbody>tr td.large-9.last, +.collapse>tbody>tr th.large-9.last { width: 443px; } -td.large-9 center, -th.large-9 center { - min-width: 387px; -} - .body .columns td.large-9, .body .column td.large-9, .body .columns th.large-9, @@ -566,7 +550,7 @@ th.large-9 center { td.large-10, th.large-10 { - width: 467.33333px; + width: 467.3333333333px; padding-left: 8px; padding-right: 8px; } @@ -581,35 +565,30 @@ th.large-10.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-10, -.collapse>tbody>tr>th.large-10 { +.collapse>tbody>tr>td.large-10:not([class*=large-offset]), +.collapse>tbody>tr>th.large-10:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 483.33333px; + width: 483.3333333333px; } -.collapse td.large-10.first, -.collapse th.large-10.first, -.collapse td.large-10.last, -.collapse th.large-10.last { - width: 491.33333px; -} - -td.large-10 center, -th.large-10 center { - min-width: 435.33333px; +.collapse>tbody>tr td.large-10.first, +.collapse>tbody>tr th.large-10.first, +.collapse>tbody>tr td.large-10.last, +.collapse>tbody>tr th.large-10.last { + width: 491.3333333333px; } .body .columns td.large-10, .body .column td.large-10, .body .columns th.large-10, .body .column th.large-10 { - width: 83.33333%; + width: 83.333333%; } td.large-11, th.large-11 { - width: 515.66667px; + width: 515.6666666667px; padding-left: 8px; padding-right: 8px; } @@ -624,30 +603,25 @@ th.large-11.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-11, -.collapse>tbody>tr>th.large-11 { +.collapse>tbody>tr>td.large-11:not([class*=large-offset]), +.collapse>tbody>tr>th.large-11:not([class*=large-offset]) { padding-right: 0; padding-left: 0; - width: 531.66667px; -} - -.collapse td.large-11.first, -.collapse th.large-11.first, -.collapse td.large-11.last, -.collapse th.large-11.last { - width: 539.66667px; + width: 531.6666666667px; } -td.large-11 center, -th.large-11 center { - min-width: 483.66667px; +.collapse>tbody>tr td.large-11.first, +.collapse>tbody>tr th.large-11.first, +.collapse>tbody>tr td.large-11.last, +.collapse>tbody>tr th.large-11.last { + width: 539.6666666667px; } .body .columns td.large-11, .body .column td.large-11, .body .columns th.large-11, .body .column th.large-11 { - width: 91.66667%; + width: 91.666666%; } td.large-12, @@ -667,25 +641,20 @@ th.large-12.last { padding-right: 16px; } -.collapse>tbody>tr>td.large-12, -.collapse>tbody>tr>th.large-12 { +.collapse>tbody>tr>td.large-12:not([class*=large-offset]), +.collapse>tbody>tr>th.large-12:not([class*=large-offset]) { padding-right: 0; padding-left: 0; width: 580px; } -.collapse td.large-12.first, -.collapse th.large-12.first, -.collapse td.large-12.last, -.collapse th.large-12.last { +.collapse>tbody>tr td.large-12.first, +.collapse>tbody>tr th.large-12.first, +.collapse>tbody>tr td.large-12.last, +.collapse>tbody>tr th.large-12.last { width: 588px; } -td.large-12 center, -th.large-12 center { - min-width: 532px; -} - .body .columns td.large-12, .body .column td.large-12, .body .columns th.large-12, @@ -699,7 +668,7 @@ td.large-offset-1.last, th.large-offset-1, th.large-offset-1.first, th.large-offset-1.last { - padding-left: 64.33333px; + padding-left: 64.3333333333px; } td.large-offset-2, @@ -708,7 +677,7 @@ td.large-offset-2.last, th.large-offset-2, th.large-offset-2.first, th.large-offset-2.last { - padding-left: 112.66667px; + padding-left: 112.6666666667px; } td.large-offset-3, @@ -726,7 +695,7 @@ td.large-offset-4.last, th.large-offset-4, th.large-offset-4.first, th.large-offset-4.last { - padding-left: 209.33333px; + padding-left: 209.3333333333px; } td.large-offset-5, @@ -735,7 +704,7 @@ td.large-offset-5.last, th.large-offset-5, th.large-offset-5.first, th.large-offset-5.last { - padding-left: 257.66667px; + padding-left: 257.6666666667px; } td.large-offset-6, @@ -753,7 +722,7 @@ td.large-offset-7.last, th.large-offset-7, th.large-offset-7.first, th.large-offset-7.last { - padding-left: 354.33333px; + padding-left: 354.3333333333px; } td.large-offset-8, @@ -762,7 +731,7 @@ td.large-offset-8.last, th.large-offset-8, th.large-offset-8.first, th.large-offset-8.last { - padding-left: 402.66667px; + padding-left: 402.6666666667px; } td.large-offset-9, @@ -780,7 +749,7 @@ td.large-offset-10.last, th.large-offset-10, th.large-offset-10.first, th.large-offset-10.last { - padding-left: 499.33333px; + padding-left: 499.3333333333px; } td.large-offset-11, @@ -789,7 +758,7 @@ td.large-offset-11.last, th.large-offset-11, th.large-offset-11.first, th.large-offset-11.last { - padding-left: 547.66667px; + padding-left: 547.6666666667px; } td.expander, @@ -896,12 +865,15 @@ span.text-center { float: none !important; text-align: center !important; } + .small-text-center { text-align: center !important; } + .small-text-left { text-align: left !important; } + .small-text-right { text-align: right !important; } @@ -934,8 +906,22 @@ th.float-center { text-align: center; } +td.columns[valign=bottom], +td.column[valign=bottom], +th.columns[valign=bottom], +th.column[valign=bottom] { + vertical-align: bottom; +} + +td.columns[valign=middle], +td.column[valign=middle], +th.columns[valign=middle], +th.column[valign=middle] { + vertical-align: middle; +} + .hide-for-large { - display: none !important; + display: none; mso-hide: all; overflow: hidden; max-height: 0; @@ -960,6 +946,7 @@ table.body table.container .hide-for-large * { } @media only screen and (max-width: 596px) { + table.body table.container .hide-for-large, table.body table.container .row.hide-for-large { display: table !important; @@ -993,8 +980,7 @@ h5, h6, p, td, -th, -a { +th { color: #0a0a0a; font-family: Helvetica, Arial, sans-serif; font-weight: normal; @@ -1002,7 +988,7 @@ a { margin: 0; Margin: 0; text-align: left; - line-height: 1.3; + line-height: 130%; } h1, @@ -1036,7 +1022,7 @@ h4 { } h5 { - font-size: 20px; + font-size: 19px; } h6 { @@ -1049,7 +1035,7 @@ p, td, th { font-size: 16px; - line-height: 1.3; + line-height: 130%; } p { @@ -1059,7 +1045,7 @@ p { p.lead { font-size: 20px; - line-height: 1.6; + line-height: 160%; } p.subheader { @@ -1072,7 +1058,33 @@ p.subheader { color: #8a8a8a; } -small { +p a { + margin: default; + Margin: default; +} + +.text-xs { + font-size: 11.1111111111px; +} + +.text-sm { + font-size: 13.3333333333px; +} + +.text-lg { + font-size: 19.2px; +} + +.text-xl { + font-size: 23.04px; +} + +.text-xxl { + font-size: 27.648px; +} + +small, +.small { font-size: 80%; color: #cacaca; } @@ -1080,6 +1092,11 @@ small { a { color: #2199e8; text-decoration: none; + font-family: Helvetica, Arial, sans-serif; + font-weight: normal; + padding: 0; + text-align: left; + line-height: 130%; } a:hover { @@ -1129,20 +1146,42 @@ pre code span.callout-strong { font-weight: bold; } -table.hr { - width: 100%; +td.columns table.hr table, +td.column table.hr table, +th.columns table.hr table, +th.column table.hr table, +td.columns table.h-line table, +td.column table.h-line table, +th.columns table.h-line table, +th.column table.h-line table { + width: auto; +} + +table.hr th, +table.h-line th { + padding-bottom: 20px; + text-align: center; } -table.hr th { +table.hr table, +table.h-line table { + display: inline-block; + margin: 0; + Margin: 0; +} + +table.hr th, +table.h-line th { + width: 580px; height: 0; - max-width: 580px; + padding-top: 20px; + clear: both; border-top: 0; border-right: 0; border-bottom: 1px solid #0a0a0a; border-left: 0; - margin: 20px auto; - Margin: 20px auto; - clear: both; + font-size: 0; + line-height: 0; } .stat { @@ -1168,6 +1207,17 @@ span.preheader { overflow: hidden; } +@media only screen { + a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + } +} + table.button { width: auto; margin: 0 0 16px 0; @@ -1187,6 +1237,7 @@ table.button table td a { font-weight: bold; color: #fefefe; text-decoration: none; + text-align: left; display: inline-block; padding: 8px 16px 8px 16px; border: 0 solid #2199e8; @@ -1203,6 +1254,10 @@ table.button.rounded table td { border: none; } +table.button:not(.expand):not(.expanded) table { + width: auto; +} + table.button:hover table tr td a, table.button:active table tr td a, table.button table tr td a:visited, @@ -1241,7 +1296,7 @@ table.button.large table a { table.button.expand, table.button.expanded { - width: 100% !important; + width: 100%; } table.button.expand table, @@ -1372,7 +1427,7 @@ th.callout-inner { th.callout-inner.primary { background: #def0fc; - border: 1px solid #444444; + border: 1px solid #0f5f94; color: #0a0a0a; } @@ -1385,19 +1440,19 @@ th.callout-inner.secondary { th.callout-inner.success { background: #e1faea; border: 1px solid #1b9448; - color: #fefefe; + color: #0a0a0a; } th.callout-inner.warning { background: #fff3d9; border: 1px solid #996800; - color: #fefefe; + color: #0a0a0a; } th.callout-inner.alert { background: #fce6e2; border: 1px solid #b42912; - color: #fefefe; + color: #0a0a0a; } .thumbnail { @@ -1422,8 +1477,10 @@ table.menu { table.menu td.menu-item, table.menu th.menu-item { - padding: 10px; + padding-top: 10px; padding-right: 10px; + padding-bottom: 10px; + padding-left: 10px; } table.menu td.menu-item a, @@ -1433,8 +1490,10 @@ table.menu th.menu-item a { table.menu.vertical td.menu-item, table.menu.vertical th.menu-item { - padding: 10px; + padding-top: 10px; padding-right: 0; + padding-bottom: 10px; + padding-left: 10px; display: block; } @@ -1454,8 +1513,32 @@ table.menu.text-center a { text-align: center; } -.menu[align="center"] { - width: auto !important; +.menu[align=center] { + width: auto; +} + +.menu[align=center] tr { + text-align: center; +} + +.menu:not(.float-center) .menu-item:first-child { + padding-left: 0 !important; +} + +.menu:not(.float-center) .menu-item:last-child { + padding-right: 0 !important; +} + +.menu.vertical .menu-item { + padding-left: 0 !important; + padding-right: 0 !important; +} + +@media only screen and (max-width: 596px) { + .menu.small-vertical .menu-item { + padding-left: 0 !important; + padding-right: 0 !important; + } } body.outlook p { @@ -1467,12 +1550,15 @@ body.outlook p { width: auto; height: auto; } + table.body center { min-width: 0 !important; } + table.body .container { width: 95% !important; } + table.body .columns, table.body .column { height: auto !important; @@ -1482,78 +1568,85 @@ body.outlook p { padding-left: 16px !important; padding-right: 16px !important; } - table.body .columns .column, - table.body .columns .columns, - table.body .column .column, - table.body .column .columns { - padding-left: 0 !important; - padding-right: 0 !important; - } - table.body .collapse .columns, - table.body .collapse .column { + + table.body .collapse>tbody>tr>.columns, + table.body .collapse>tbody>tr>.column { padding-left: 0 !important; padding-right: 0 !important; } + td.small-1, th.small-1 { display: inline-block !important; - width: 8.33333% !important; + width: 8.333333% !important; } + td.small-2, th.small-2 { display: inline-block !important; - width: 16.66667% !important; + width: 16.666666% !important; } + td.small-3, th.small-3 { display: inline-block !important; width: 25% !important; } + td.small-4, th.small-4 { display: inline-block !important; - width: 33.33333% !important; + width: 33.333333% !important; } + td.small-5, th.small-5 { display: inline-block !important; - width: 41.66667% !important; + width: 41.666666% !important; } + td.small-6, th.small-6 { display: inline-block !important; width: 50% !important; } + td.small-7, th.small-7 { display: inline-block !important; - width: 58.33333% !important; + width: 58.333333% !important; } + td.small-8, th.small-8 { display: inline-block !important; - width: 66.66667% !important; + width: 66.666666% !important; } + td.small-9, th.small-9 { display: inline-block !important; width: 75% !important; } + td.small-10, th.small-10 { display: inline-block !important; - width: 83.33333% !important; + width: 83.333333% !important; } + td.small-11, th.small-11 { display: inline-block !important; - width: 91.66667% !important; + width: 91.666666% !important; } + td.small-12, th.small-12 { display: inline-block !important; width: 100% !important; } + .columns td.small-12, .column td.small-12, .columns th.small-12, @@ -1561,98 +1654,119 @@ body.outlook p { display: block !important; width: 100% !important; } + table.body td.small-offset-1, table.body th.small-offset-1 { - margin-left: 8.33333% !important; - Margin-left: 8.33333% !important; + margin-left: 8.333333% !important; + Margin-left: 8.333333% !important; } + table.body td.small-offset-2, table.body th.small-offset-2 { - margin-left: 16.66667% !important; - Margin-left: 16.66667% !important; + margin-left: 16.666666% !important; + Margin-left: 16.666666% !important; } + table.body td.small-offset-3, table.body th.small-offset-3 { margin-left: 25% !important; Margin-left: 25% !important; } + table.body td.small-offset-4, table.body th.small-offset-4 { - margin-left: 33.33333% !important; - Margin-left: 33.33333% !important; + margin-left: 33.333333% !important; + Margin-left: 33.333333% !important; } + table.body td.small-offset-5, table.body th.small-offset-5 { - margin-left: 41.66667% !important; - Margin-left: 41.66667% !important; + margin-left: 41.666666% !important; + Margin-left: 41.666666% !important; } + table.body td.small-offset-6, table.body th.small-offset-6 { margin-left: 50% !important; Margin-left: 50% !important; } + table.body td.small-offset-7, table.body th.small-offset-7 { - margin-left: 58.33333% !important; - Margin-left: 58.33333% !important; + margin-left: 58.333333% !important; + Margin-left: 58.333333% !important; } + table.body td.small-offset-8, table.body th.small-offset-8 { - margin-left: 66.66667% !important; - Margin-left: 66.66667% !important; + margin-left: 66.666666% !important; + Margin-left: 66.666666% !important; } + table.body td.small-offset-9, table.body th.small-offset-9 { margin-left: 75% !important; Margin-left: 75% !important; } + table.body td.small-offset-10, table.body th.small-offset-10 { - margin-left: 83.33333% !important; - Margin-left: 83.33333% !important; + margin-left: 83.333333% !important; + Margin-left: 83.333333% !important; } + table.body td.small-offset-11, table.body th.small-offset-11 { - margin-left: 91.66667% !important; - Margin-left: 91.66667% !important; + margin-left: 91.666666% !important; + Margin-left: 91.666666% !important; } + table.body table.columns td.expander, table.body table.columns th.expander { display: none !important; } + table.body .right-text-pad, table.body .text-pad-right { padding-left: 10px !important; } + table.body .left-text-pad, table.body .text-pad-left { padding-right: 10px !important; } + table.menu { width: 100% !important; } + table.menu td, table.menu th { width: auto !important; display: inline-block !important; } + table.menu.vertical td, table.menu.vertical th, table.menu.small-vertical td, table.menu.small-vertical th { display: block !important; } - table.menu[align="center"] { + + table.menu[align=center] { width: auto !important; } + table.button.small-expand, table.button.small-expanded { width: 100% !important; } + table.button.small-expand table, table.button.small-expanded table { width: 100%; } + table.button.small-expand table a, table.button.small-expanded table a { text-align: center !important; @@ -1660,8 +1774,13 @@ body.outlook p { padding-left: 0 !important; padding-right: 0 !important; } + table.button.small-expand center, table.button.small-expanded center { min-width: 0; } -} + + th.callout-inner { + padding: 10px !important; + } +} \ No newline at end of file From f9c4f6527425da3413aad7c6dcae7e05aad7201c Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 6 Oct 2024 18:45:39 +0200 Subject: [PATCH 20/34] CS: clean some whitespaces/indentation --- .../TranslationDefaultDomainNodeVisitor.php | 3 +- Tests/Node/SearchAndRenderBlockNodeTest.php | 66 +++++++++---------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 5788f539..122842b8 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -56,10 +56,9 @@ public function enterNode(Node $node, Environment $env): Node $name = new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); $this->scope->set('domain', new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); - if (class_exists(Nodes::class)) { return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); - } + } return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } diff --git a/Tests/Node/SearchAndRenderBlockNodeTest.php b/Tests/Node/SearchAndRenderBlockNodeTest.php index 2e790195..f587b3c9 100644 --- a/Tests/Node/SearchAndRenderBlockNodeTest.php +++ b/Tests/Node/SearchAndRenderBlockNodeTest.php @@ -57,9 +57,9 @@ public function testCompileWidgetWithVariables() if (class_exists(Nodes::class)) { $arguments = new Nodes([ new NameExpression('form', 0), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), ], 0), ]); } else { @@ -201,9 +201,9 @@ public function testCompileLabelWithAttributes() $arguments = new Nodes([ new NameExpression('form', 0), new ConstantExpression(null, 0), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), ], 0), ]); } else { @@ -239,11 +239,11 @@ public function testCompileLabelWithLabelAndAttributes() $arguments = new Nodes([ new NameExpression('form', 0), new ConstantExpression('value in argument', 0), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), - new ConstantExpression('label', 0), - new ConstantExpression('value in attributes', 0), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), + new ConstantExpression('label', 0), + new ConstantExpression('value in attributes', 0), ], 0), ]); } else { @@ -277,14 +277,14 @@ public function testCompileLabelWithLabelThatEvaluatesToNull() if (class_exists(Nodes::class)) { $arguments = new Nodes([ new NameExpression('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 + new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 ), ]); } else { @@ -324,20 +324,20 @@ public function testCompileLabelWithLabelThatEvaluatesToNullAndAttributes() if (class_exists(Nodes::class)) { $arguments = new Nodes([ new NameExpression('form', 0), - new ConditionalExpression( - // if - new ConstantExpression(true, 0), - // then - new ConstantExpression(null, 0), - // else - new ConstantExpression(null, 0), - 0 - ), - new ArrayExpression([ - new ConstantExpression('foo', 0), - new ConstantExpression('bar', 0), - new ConstantExpression('label', 0), - new ConstantExpression('value in attributes', 0), + new ConditionalExpression( + // if + new ConstantExpression(true, 0), + // then + new ConstantExpression(null, 0), + // else + new ConstantExpression(null, 0), + 0 + ), + new ArrayExpression([ + new ConstantExpression('foo', 0), + new ConstantExpression('bar', 0), + new ConstantExpression('label', 0), + new ConstantExpression('value in attributes', 0), ], 0), ]); } else { From c825d4371be0a92e57a7f175d1a6c92768ba9cb5 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 8 Oct 2024 14:43:47 +0200 Subject: [PATCH 21/34] [TwigBridge] Add missing return type --- Tests/Extension/AbstractLayoutTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Extension/AbstractLayoutTestCase.php b/Tests/Extension/AbstractLayoutTestCase.php index fa7fcc95..5a541d7b 100644 --- a/Tests/Extension/AbstractLayoutTestCase.php +++ b/Tests/Extension/AbstractLayoutTestCase.php @@ -48,7 +48,7 @@ protected function setUp(): void /** * @return FormExtensionInterface[] */ - protected function getExtensions() + protected function getExtensions(): array { return [ new CsrfExtension($this->csrfTokenManager), From 170cf4436d8fb7e9edb1ec7b6473da7d9f230c6e Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 18 Oct 2024 16:04:52 +0200 Subject: [PATCH 22/34] Remove always true/false occurrences --- Command/DebugCommand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index d0aded57..1c2c1619 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -412,7 +412,6 @@ private function findWrongBundleOverrides(): array } if ($notFoundBundles = array_diff_key($bundleNames, $this->bundlesMetadata)) { - $alternatives = []; foreach ($notFoundBundles as $notFoundBundle => $path) { $alternatives[$path] = $this->findAlternatives($notFoundBundle, array_keys($this->bundlesMetadata)); } From 3e8cc6b655de5843c050fd60f6fa1598c3c2c7b1 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 23 Oct 2024 10:11:04 +0200 Subject: [PATCH 23/34] revert allowing Twig 4 As Twig 4 will not be released before Symfony 7.2 we should not claim compatibility before a stable Twig 4 release. --- Tests/Node/TransNodeTest.php | 6 ------ composer.json | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Tests/Node/TransNodeTest.php b/Tests/Node/TransNodeTest.php index 35288174..0b055cae 100644 --- a/Tests/Node/TransNodeTest.php +++ b/Tests/Node/TransNodeTest.php @@ -18,7 +18,6 @@ use Twig\Loader\LoaderInterface; use Twig\Node\Expression\NameExpression; use Twig\Node\TextNode; -use Twig\Runtime\LoopIterator; /** * @author Asmir Mustafic @@ -51,11 +50,6 @@ protected function getVariableGetterWithoutStrictCheck($name) protected function getVariableGetterWithStrictCheck($name) { - if (class_exists(LoopIterator::class)) { - return \sprintf('(array_key_exists("%1$s", $context) ? $context["%1$s"] : throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source))', $name); - } - - // for Twig 3 and older, can be removed when support for Twig 3 is dropped return \sprintf('(isset($context["%1$s"]) || array_key_exists("%1$s", $context) ? $context["%1$s"] : (function () { throw new RuntimeError(\'Variable "%1$s" does not exist.\', 0, $this->source); })())', $name); } } diff --git a/composer.json b/composer.json index 9d94e1c1..3af8ccbb 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.12|^4.0" + "twig/twig": "^3.12" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", From a5d88e50935a998eb84495098f2d1e3aa75de627 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 25 Oct 2024 18:06:58 +0200 Subject: [PATCH 24/34] fix merge --- Node/StopwatchNode.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Node/StopwatchNode.php b/Node/StopwatchNode.php index 6e584bd1..472b6280 100644 --- a/Node/StopwatchNode.php +++ b/Node/StopwatchNode.php @@ -27,11 +27,7 @@ final class StopwatchNode extends Node { public function __construct(Node $name, Node $body, AssignNameExpression|LocalVariable $var, int $lineno = 0) { - if (class_exists(FirstClassTwigCallableReady::class)) { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); - } else { - parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno, $tag); - } + parent::__construct(['body' => $body, 'name' => $name, 'var' => $var], [], $lineno); } public function compile(Compiler $compiler): void From f2f472a9d9b329c95a221c5136cf102187eff701 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Tue, 29 Oct 2024 10:53:53 +0800 Subject: [PATCH 25/34] [TwigBridge] Use INTERNAL_VAR_NAME instead of getVarName --- NodeVisitor/TranslationDefaultDomainNodeVisitor.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 7d50a2f6..858547c1 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -55,9 +55,8 @@ public function enterNode(Node $node, Environment $env): Node return $node; } - $var = $this->getVarName(); - $name = class_exists(AssignContextVariable::class) ? new AssignContextVariable($var, $node->getTemplateLine()) : new AssignNameExpression($var, $node->getTemplateLine()); - $this->scope->set('domain', class_exists(ContextVariable::class) ? new ContextVariable($var, $node->getTemplateLine()) : new NameExpression($var, $node->getTemplateLine())); + $name = class_exists(AssignContextVariable::class) ? new AssignContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); + $this->scope->set('domain', class_exists(ContextVariable::class) ? new ContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); if (class_exists(Nodes::class)) { return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); From e34839ab413e1dc32bd2cb9f95186af1bacaee7e Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 25 Oct 2024 19:03:09 +0200 Subject: [PATCH 26/34] use reproducible variable names in the default domain node visitor --- .../TranslationDefaultDomainNodeVisitor.php | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 858547c1..ba93f2fe 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -20,8 +20,7 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\Variable\AssignContextVariable; -use Twig\Node\Expression\Variable\ContextVariable; +use Twig\Node\Expression\Variable\LocalVariable; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\Node\Nodes; @@ -33,9 +32,8 @@ */ final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface { - private const INTERNAL_VAR_NAME = '__internal_trans_default_domain'; - private Scope $scope; + private int $nestingLevel = 0; public function __construct() { @@ -49,19 +47,25 @@ public function enterNode(Node $node, Environment $env): Node } if ($node instanceof TransDefaultDomainNode) { + ++$this->nestingLevel; + if ($node->getNode('expr') instanceof ConstantExpression) { $this->scope->set('domain', $node->getNode('expr')); return $node; } - $name = class_exists(AssignContextVariable::class) ? new AssignContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new AssignNameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine()); - $this->scope->set('domain', class_exists(ContextVariable::class) ? new ContextVariable(self::INTERNAL_VAR_NAME, $node->getTemplateLine()) : new NameExpression(self::INTERNAL_VAR_NAME, $node->getTemplateLine())); - if (class_exists(Nodes::class)) { + $name = new LocalVariable(null, $node->getTemplateLine()); + $this->scope->set('domain', $name); + return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); } + $var = '__internal_trans_default_domain_'.$this->nestingLevel; + $name = new AssignNameExpression($var, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); + return new SetNode(false, new Node([$name]), new Node([$node->getNode('expr')]), $node->getTemplateLine()); } @@ -94,6 +98,8 @@ public function enterNode(Node $node, Environment $env): Node public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { + --$this->nestingLevel; + return null; } From b7efcdfbd484bbf37214a6ce6c921d9502a96c75 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Thu, 14 Nov 2024 18:50:56 +0100 Subject: [PATCH 27/34] [TwigBridge] Remove leftover code --- TokenParser/TransTokenParser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TokenParser/TransTokenParser.php b/TokenParser/TransTokenParser.php index e60263a4..67b92dd8 100644 --- a/TokenParser/TransTokenParser.php +++ b/TokenParser/TransTokenParser.php @@ -74,7 +74,7 @@ public function parse(Token $token): Node $stream->expect(Token::BLOCK_END_TYPE); - return new TransNode($body, $domain, $count, $vars, $locale, $lineno, $this->getTag()); + return new TransNode($body, $domain, $count, $vars, $locale, $lineno); } public function decideTransFork(Token $token): bool From 9958f5a5b6640734fe4b24c18897191f77a02c61 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Wed, 20 Nov 2024 00:05:41 +0100 Subject: [PATCH 28/34] CS: apply minor indentation fixes --- EventListener/TemplateAttributeListener.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/EventListener/TemplateAttributeListener.php b/EventListener/TemplateAttributeListener.php index 7220f4c4..45a4e9cc 100644 --- a/EventListener/TemplateAttributeListener.php +++ b/EventListener/TemplateAttributeListener.php @@ -55,16 +55,16 @@ public function onKernelView(ViewEvent $event): void } $event->setResponse($attribute->stream - ? new StreamedResponse( - null !== $attribute->block - ? fn () => $this->twig->load($attribute->template)->displayBlock($attribute->block, $parameters) - : fn () => $this->twig->display($attribute->template, $parameters), - $status) - : new Response( - null !== $attribute->block - ? $this->twig->load($attribute->template)->renderBlock($attribute->block, $parameters) - : $this->twig->render($attribute->template, $parameters), - $status) + ? new StreamedResponse( + null !== $attribute->block + ? fn () => $this->twig->load($attribute->template)->displayBlock($attribute->block, $parameters) + : fn () => $this->twig->display($attribute->template, $parameters), + $status) + : new Response( + null !== $attribute->block + ? $this->twig->load($attribute->template)->renderBlock($attribute->block, $parameters) + : $this->twig->render($attribute->template, $parameters), + $status) ); } From 4b51036f31d510ee08e53ef90a232e7007870e32 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 2 Dec 2024 10:05:27 +0100 Subject: [PATCH 29/34] generate conflict-free variable names --- .../TranslationDefaultDomainNodeVisitor.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php index 9a0ecc9d..3b8196fa 100644 --- a/NodeVisitor/TranslationDefaultDomainNodeVisitor.php +++ b/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -21,7 +21,8 @@ use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; use Twig\Node\Expression\NameExpression; -use Twig\Node\Expression\Variable\LocalVariable; +use Twig\Node\Expression\Variable\AssignContextVariable; +use Twig\Node\Expression\Variable\ContextVariable; use Twig\Node\ModuleNode; use Twig\Node\Node; use Twig\Node\Nodes; @@ -34,7 +35,6 @@ final class TranslationDefaultDomainNodeVisitor implements NodeVisitorInterface { private Scope $scope; - private int $nestingLevel = 0; public function __construct() { @@ -48,22 +48,25 @@ public function enterNode(Node $node, Environment $env): Node } if ($node instanceof TransDefaultDomainNode) { - ++$this->nestingLevel; - if ($node->getNode('expr') instanceof ConstantExpression) { $this->scope->set('domain', $node->getNode('expr')); return $node; } + if (null === $templateName = $node->getTemplateName()) { + throw new \LogicException('Cannot traverse a node without a template name.'); + } + + $var = '__internal_trans_default_domain'.hash('xxh128', $templateName); + if (class_exists(Nodes::class)) { - $name = new LocalVariable(null, $node->getTemplateLine()); - $this->scope->set('domain', $name); + $name = new AssignContextVariable($var, $node->getTemplateLine()); + $this->scope->set('domain', new ContextVariable($var, $node->getTemplateLine())); return new SetNode(false, new Nodes([$name]), new Nodes([$node->getNode('expr')]), $node->getTemplateLine()); } - $var = '__internal_trans_default_domain_'.$this->nestingLevel; $name = new AssignNameExpression($var, $node->getTemplateLine()); $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); @@ -105,8 +108,6 @@ public function enterNode(Node $node, Environment $env): Node public function leaveNode(Node $node, Environment $env): ?Node { if ($node instanceof TransDefaultDomainNode) { - --$this->nestingLevel; - return null; } From 99958cee806b4977373eb95dd8fc6131e0ec3859 Mon Sep 17 00:00:00 2001 From: Benjamin Ellis Date: Thu, 30 Jan 2025 09:40:15 +0100 Subject: [PATCH 30/34] [Mime] use isRendered method to avoid rendering an email twice --- Mime/BodyRenderer.php | 2 +- Tests/Mime/BodyRendererTest.php | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Mime/BodyRenderer.php b/Mime/BodyRenderer.php index d5b6d14c..b7ae05f4 100644 --- a/Mime/BodyRenderer.php +++ b/Mime/BodyRenderer.php @@ -45,7 +45,7 @@ public function render(Message $message): void return; } - if (null === $message->getTextTemplate() && null === $message->getHtmlTemplate()) { + if ($message->isRendered()) { // email has already been rendered return; } diff --git a/Tests/Mime/BodyRendererTest.php b/Tests/Mime/BodyRendererTest.php index f5d37e7d..cce8ee9a 100644 --- a/Tests/Mime/BodyRendererTest.php +++ b/Tests/Mime/BodyRendererTest.php @@ -105,10 +105,14 @@ public function testRenderedOnce() ; $email->textTemplate('text'); + $this->assertFalse($email->isRendered()); $renderer->render($email); + $this->assertTrue($email->isRendered()); + $this->assertEquals('Text', $email->getTextBody()); $email->text('reset'); + $this->assertTrue($email->isRendered()); $renderer->render($email); $this->assertEquals('reset', $email->getTextBody()); From d6aecb7196bf610e63ebb64f937c33878d5d03b1 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 14 Feb 2025 10:47:54 +0100 Subject: [PATCH 31/34] [TwigBridge] Fix compatibility with Twig 3.21 --- TokenParser/DumpTokenParser.php | 18 +++++++++++++++++- TokenParser/FormThemeTokenParser.php | 10 +++++++--- TokenParser/StopwatchTokenParser.php | 4 +++- TokenParser/TransDefaultDomainTokenParser.php | 4 +++- TokenParser/TransTokenParser.php | 12 ++++++++---- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/TokenParser/DumpTokenParser.php b/TokenParser/DumpTokenParser.php index e671f9ba..9c12dc23 100644 --- a/TokenParser/DumpTokenParser.php +++ b/TokenParser/DumpTokenParser.php @@ -14,6 +14,7 @@ use Symfony\Bridge\Twig\Node\DumpNode; use Twig\Node\Expression\Variable\LocalVariable; use Twig\Node\Node; +use Twig\Node\Nodes; use Twig\Token; use Twig\TokenParser\AbstractTokenParser; @@ -34,13 +35,28 @@ public function parse(Token $token): Node { $values = null; if (!$this->parser->getStream()->test(Token::BLOCK_END_TYPE)) { - $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + $values = method_exists($this->parser, 'parseExpression') ? + $this->parseMultitargetExpression() : + $this->parser->getExpressionParser()->parseMultitargetExpression(); } $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); return new DumpNode(class_exists(LocalVariable::class) ? new LocalVariable(null, $token->getLine()) : $this->parser->getVarName(), $values, $token->getLine(), $this->getTag()); } + private function parseMultitargetExpression(): Node + { + $targets = []; + while (true) { + $targets[] = $this->parser->parseExpression(); + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Nodes($targets); + } + public function getTag(): string { return 'dump'; diff --git a/TokenParser/FormThemeTokenParser.php b/TokenParser/FormThemeTokenParser.php index b95a2a05..c5fc2311 100644 --- a/TokenParser/FormThemeTokenParser.php +++ b/TokenParser/FormThemeTokenParser.php @@ -29,12 +29,16 @@ public function parse(Token $token): Node $lineno = $token->getLine(); $stream = $this->parser->getStream(); - $form = $this->parser->getExpressionParser()->parseExpression(); + $parseExpression = method_exists($this->parser, 'parseExpression') + ? $this->parser->parseExpression(...) + : $this->parser->getExpressionParser()->parseExpression(...); + + $form = $parseExpression(); $only = false; if ($this->parser->getStream()->test(Token::NAME_TYPE, 'with')) { $this->parser->getStream()->next(); - $resources = $this->parser->getExpressionParser()->parseExpression(); + $resources = $parseExpression(); if ($this->parser->getStream()->nextIf(Token::NAME_TYPE, 'only')) { $only = true; @@ -42,7 +46,7 @@ public function parse(Token $token): Node } else { $resources = new ArrayExpression([], $stream->getCurrent()->getLine()); do { - $resources->addElement($this->parser->getExpressionParser()->parseExpression()); + $resources->addElement($parseExpression()); } while (!$stream->test(Token::BLOCK_END_TYPE)); } diff --git a/TokenParser/StopwatchTokenParser.php b/TokenParser/StopwatchTokenParser.php index 324f9d45..c478d9e6 100644 --- a/TokenParser/StopwatchTokenParser.php +++ b/TokenParser/StopwatchTokenParser.php @@ -38,7 +38,9 @@ public function parse(Token $token): Node $stream = $this->parser->getStream(); // {% stopwatch 'bar' %} - $name = $this->parser->getExpressionParser()->parseExpression(); + $name = method_exists($this->parser, 'parseExpression') ? + $this->parser->parseExpression() : + $this->parser->getExpressionParser()->parseExpression(); $stream->expect(Token::BLOCK_END_TYPE); diff --git a/TokenParser/TransDefaultDomainTokenParser.php b/TokenParser/TransDefaultDomainTokenParser.php index c6d850d0..a64a2332 100644 --- a/TokenParser/TransDefaultDomainTokenParser.php +++ b/TokenParser/TransDefaultDomainTokenParser.php @@ -25,7 +25,9 @@ final class TransDefaultDomainTokenParser extends AbstractTokenParser { public function parse(Token $token): Node { - $expr = $this->parser->getExpressionParser()->parseExpression(); + $expr = method_exists($this->parser, 'parseExpression') ? + $this->parser->parseExpression() : + $this->parser->getExpressionParser()->parseExpression(); $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); diff --git a/TokenParser/TransTokenParser.php b/TokenParser/TransTokenParser.php index e60263a4..2d17c9da 100644 --- a/TokenParser/TransTokenParser.php +++ b/TokenParser/TransTokenParser.php @@ -36,29 +36,33 @@ public function parse(Token $token): Node $vars = new ArrayExpression([], $lineno); $domain = null; $locale = null; + $parseExpression = method_exists($this->parser, 'parseExpression') + ? $this->parser->parseExpression(...) + : $this->parser->getExpressionParser()->parseExpression(...); + if (!$stream->test(Token::BLOCK_END_TYPE)) { if ($stream->test('count')) { // {% trans count 5 %} $stream->next(); - $count = $this->parser->getExpressionParser()->parseExpression(); + $count = $parseExpression(); } if ($stream->test('with')) { // {% trans with vars %} $stream->next(); - $vars = $this->parser->getExpressionParser()->parseExpression(); + $vars = $parseExpression(); } if ($stream->test('from')) { // {% trans from "messages" %} $stream->next(); - $domain = $this->parser->getExpressionParser()->parseExpression(); + $domain = $parseExpression(); } if ($stream->test('into')) { // {% trans into "fr" %} $stream->next(); - $locale = $this->parser->getExpressionParser()->parseExpression(); + $locale = $parseExpression(); } elseif (!$stream->test(Token::BLOCK_END_TYPE)) { throw new SyntaxError('Unexpected token. Twig was looking for the "with", "from", or "into" keyword.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); } From 4d706f1c890608fb0b4715d489b86f4b29a5e247 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 28 Feb 2025 18:45:24 +0100 Subject: [PATCH 32/34] [TwigBridge] Fix `ModuleNode` call in `TwigNodeProvider` --- Tests/NodeVisitor/TwigNodeProvider.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Tests/NodeVisitor/TwigNodeProvider.php b/Tests/NodeVisitor/TwigNodeProvider.php index 69cf6bec..4fc96d8a 100644 --- a/Tests/NodeVisitor/TwigNodeProvider.php +++ b/Tests/NodeVisitor/TwigNodeProvider.php @@ -15,6 +15,7 @@ use Symfony\Bridge\Twig\Node\TransNode; use Twig\Attribute\FirstClassTwigCallableReady; use Twig\Node\BodyNode; +use Twig\Node\EmptyNode; use Twig\Node\Expression\ArrayExpression; use Twig\Node\Expression\ConstantExpression; use Twig\Node\Expression\FilterExpression; @@ -28,13 +29,15 @@ class TwigNodeProvider { public static function getModule($content) { + $emptyNodeExists = class_exists(EmptyNode::class); + return new ModuleNode( new BodyNode([new ConstantExpression($content, 0)]), null, - new ArrayExpression([], 0), - new ArrayExpression([], 0), - new ArrayExpression([], 0), - null, + $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0), + $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0), + $emptyNodeExists ? new EmptyNode() : new ArrayExpression([], 0), + $emptyNodeExists ? new EmptyNode() : null, new Source('', '') ); } From acf7783002561656c9583455c77c4a6ecd72040f Mon Sep 17 00:00:00 2001 From: Alexander Hofbauer Date: Wed, 26 Mar 2025 17:02:02 +0100 Subject: [PATCH 33/34] [Form] Use duplicate_preferred_choices to set value of ChoiceType When the preferred choices are not duplicated an option has to be selected in the group of preferred choices. Closes #58561 --- .../views/Form/form_div_layout.html.twig | 2 +- Tests/Extension/AbstractDivLayoutTestCase.php | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/Resources/views/Form/form_div_layout.html.twig b/Resources/views/Form/form_div_layout.html.twig index 02628b5a..d43b40a0 100644 --- a/Resources/views/Form/form_div_layout.html.twig +++ b/Resources/views/Form/form_div_layout.html.twig @@ -85,7 +85,7 @@ {{- block('choice_widget_options') -}} {%- else -%} - + {%- endif -%} {% endfor %} {%- endblock choice_widget_options -%} diff --git a/Tests/Extension/AbstractDivLayoutTestCase.php b/Tests/Extension/AbstractDivLayoutTestCase.php index a02fca4b..bfbd458e 100644 --- a/Tests/Extension/AbstractDivLayoutTestCase.php +++ b/Tests/Extension/AbstractDivLayoutTestCase.php @@ -856,6 +856,56 @@ public function testMultipleChoiceExpandedWithLabelsSetFalseByCallable() ); } + public function testSingleChoiceWithoutDuplicatePreferredIsSelected() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&d', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c', 'Choice&D' => '&d'], + 'preferred_choices' => ['&b', '&d'], + 'duplicate_preferred_choices' => false, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --'], + '/select + [@name="name"] + [ + ./option[@value="&d"][@selected="selected"] + /following-sibling::option[@disabled="disabled"][.="-- sep --"] + /following-sibling::option[@value="&a"][not(@selected)] + /following-sibling::option[@value="&c"][not(@selected)] + ] + [count(./option)=5] +' + ); + } + + public function testSingleChoiceWithoutDuplicateNotPreferredIsSelected() + { + $form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\ChoiceType', '&d', [ + 'choices' => ['Choice&A' => '&a', 'Choice&B' => '&b', 'Choice&C' => '&c', 'Choice&D' => '&d'], + 'preferred_choices' => ['&b', '&d'], + 'duplicate_preferred_choices' => true, + 'multiple' => false, + 'expanded' => false, + ]); + + $this->assertWidgetMatchesXpath($form->createView(), ['separator' => '-- sep --'], + '/select + [@name="name"] + [ + ./option[@value="&d"][not(@selected)] + /following-sibling::option[@disabled="disabled"][.="-- sep --"] + /following-sibling::option[@value="&a"][not(@selected)] + /following-sibling::option[@value="&b"][not(@selected)] + /following-sibling::option[@value="&c"][not(@selected)] + /following-sibling::option[@value="&d"][@selected="selected"] + ] + [count(./option)=7] +' + ); + } + public function testFormEndWithRest() { $view = $this->factory->createNamedBuilder('name', 'Symfony\Component\Form\Extension\Core\Type\FormType') From bb423dfaa51b6d88b1d64197ae695a0c8ac73778 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 28 Mar 2025 14:07:35 +0100 Subject: [PATCH 34/34] [Twig] Fix tests --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2b7a6a39..f663de11 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "symfony/asset-mapper": "^6.3|^7.0", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/form": "^6.4|^7.0", + "symfony/form": "^6.4.20|^7.2.5", "symfony/html-sanitizer": "^6.1|^7.0", "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^6.4|^7.0",