Loading core/lib/Drupal/Core/Template/TwigExtension.php +31 −9 Changes for core/lib/Drupal/Core/Template/TwigExtension.php: 31 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -116,6 +116,10 @@ public function getFunctions() { * {@inheritdoc} */ public function getFilters() { $escape_options = [ 'needs_environment' => TRUE, 'is_safe_callback' => [static::class, 'escapeFilterIsSafe'], ]; return [ // Translation filters. new TwigFilter('t', 't', ['is_safe' => ['html']]), Loading @@ -127,15 +131,14 @@ public function getFilters() { // @see TwigNodeTrans::compileString() new TwigFilter('placeholder', [$this, 'escapePlaceholder'], ['is_safe' => ['html'], 'needs_environment' => TRUE]), // Replace twig's escape filter with our own. new TwigFilter( 'drupal_escape', [$this, 'escapeFilter'], [ 'needs_environment' => TRUE, 'is_safe_callback' => 'twig_escape_filter_is_safe', ] ), // Replace Twig's escape filters with our own MarkupInterface-aware // filter. Filters registered by later extensions override those with // the same name, and this extension is always added after Twig's // EscaperExtension. Twig's auto-escaping always uses the filter // named 'escape'. new TwigFilter('escape', [$this, 'escapeFilter'], $escape_options), new TwigFilter('e', [$this, 'escapeFilter'], $escape_options), new TwigFilter('drupal_escape', [$this, 'escapeFilter'], $escape_options), // Implements safe joining. // @todo Make that the default for |join? Upstream issue: Loading Loading @@ -402,6 +405,25 @@ public function escapePlaceholder(Environment $env, $string) { return $return ? '<em class="placeholder">' . $return . '</em>' : NULL; } /** * Determines which strategies the output of the escape filter is safe for. * * @param \Twig\Node\Node $filter_args * The arguments passed to the escape filter. * * @return string[] * The escaping strategies the output is safe for. */ public static function escapeFilterIsSafe(Node $filter_args): array { foreach ($filter_args as $arg) { if ($arg instanceof ConstantExpression) { return [$arg->getAttribute('value')]; } return []; } return ['html']; } /** * Overrides twig_escape_filter(). * Loading core/lib/Drupal/Core/Template/TwigNodeVisitor.php +1 −5 Changes for core/lib/Drupal/Core/Template/TwigNodeVisitor.php: 1 added line, 5 removed lines. Original line number Diff line number Diff line Loading @@ -57,13 +57,9 @@ public function leaveNode(Node $node, Environment $env): ?Node { $line ); } // Change the 'escape' filter to our own 'drupal_escape' filter. elseif ($node instanceof FilterExpression) { $name = $node->getAttribute('twig_callable')->getName(); if ('escape' == $name || 'e' == $name) { // Use our own escape filter that is MarkupInterface aware. $node->setAttribute('twig_callable', $env->getFilter('drupal_escape')); if (in_array($name, ['escape', 'e', 'drupal_escape'], TRUE)) { // Store that we have a filter active already that knows // how to deal with render arrays. $this->skipRenderVarFunction = TRUE; Loading core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +40 −0 Changes for core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php: 40 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -144,6 +144,46 @@ public static function providerTestEscaping(): array { ]; } /** * Tests that Twig's escape filters are replaced with our own. */ #[DataProvider('providerTestEscapeFilterOverride')] public function testEscapeFilterOverride(string $template, string $expected): void { $loader = new ArrayLoader(['test' => $template]); $twig = new Environment($loader, [ 'cache' => FALSE, 'autoescape' => 'html', ]); $twig->addExtension($this->systemUnderTest); // Explicit and automatic escaping must both call our filter. $source = $twig->compileSource($loader->getSourceContext('test')); $this->assertStringContainsString(sprintf("\$this->extensions['%s']->escapeFilter(", TwigExtension::class), $source); $this->assertSame($expected, $twig->render('test', [ 'text' => '<b>', 'markup' => Markup::create('<b>'), ])); } /** * Provides test data for testEscapeFilterOverride(). * * @return array * An array of test data, each containing a Twig template string and the * expected rendered output. */ public static function providerTestEscapeFilterOverride(): array { return [ 'auto-escaped string' => ['{{ text }}', '<b>'], 'auto-escaped markup' => ['{{ markup }}', '<b>'], 'escape filter' => ['{{ text|escape }}', '<b>'], 'e filter' => ['{{ text|e }}', '<b>'], 'escape filter with html strategy' => ['{{ text|e("html") }}', '<b>'], 'filter before auto-escaping' => ['{{ text|upper }}', '<B>'], ]; } /** * Tests the active_theme function. */ Loading Loading
core/lib/Drupal/Core/Template/TwigExtension.php +31 −9 Changes for core/lib/Drupal/Core/Template/TwigExtension.php: 31 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -116,6 +116,10 @@ public function getFunctions() { * {@inheritdoc} */ public function getFilters() { $escape_options = [ 'needs_environment' => TRUE, 'is_safe_callback' => [static::class, 'escapeFilterIsSafe'], ]; return [ // Translation filters. new TwigFilter('t', 't', ['is_safe' => ['html']]), Loading @@ -127,15 +131,14 @@ public function getFilters() { // @see TwigNodeTrans::compileString() new TwigFilter('placeholder', [$this, 'escapePlaceholder'], ['is_safe' => ['html'], 'needs_environment' => TRUE]), // Replace twig's escape filter with our own. new TwigFilter( 'drupal_escape', [$this, 'escapeFilter'], [ 'needs_environment' => TRUE, 'is_safe_callback' => 'twig_escape_filter_is_safe', ] ), // Replace Twig's escape filters with our own MarkupInterface-aware // filter. Filters registered by later extensions override those with // the same name, and this extension is always added after Twig's // EscaperExtension. Twig's auto-escaping always uses the filter // named 'escape'. new TwigFilter('escape', [$this, 'escapeFilter'], $escape_options), new TwigFilter('e', [$this, 'escapeFilter'], $escape_options), new TwigFilter('drupal_escape', [$this, 'escapeFilter'], $escape_options), // Implements safe joining. // @todo Make that the default for |join? Upstream issue: Loading Loading @@ -402,6 +405,25 @@ public function escapePlaceholder(Environment $env, $string) { return $return ? '<em class="placeholder">' . $return . '</em>' : NULL; } /** * Determines which strategies the output of the escape filter is safe for. * * @param \Twig\Node\Node $filter_args * The arguments passed to the escape filter. * * @return string[] * The escaping strategies the output is safe for. */ public static function escapeFilterIsSafe(Node $filter_args): array { foreach ($filter_args as $arg) { if ($arg instanceof ConstantExpression) { return [$arg->getAttribute('value')]; } return []; } return ['html']; } /** * Overrides twig_escape_filter(). * Loading
core/lib/Drupal/Core/Template/TwigNodeVisitor.php +1 −5 Changes for core/lib/Drupal/Core/Template/TwigNodeVisitor.php: 1 added line, 5 removed lines. Original line number Diff line number Diff line Loading @@ -57,13 +57,9 @@ public function leaveNode(Node $node, Environment $env): ?Node { $line ); } // Change the 'escape' filter to our own 'drupal_escape' filter. elseif ($node instanceof FilterExpression) { $name = $node->getAttribute('twig_callable')->getName(); if ('escape' == $name || 'e' == $name) { // Use our own escape filter that is MarkupInterface aware. $node->setAttribute('twig_callable', $env->getFilter('drupal_escape')); if (in_array($name, ['escape', 'e', 'drupal_escape'], TRUE)) { // Store that we have a filter active already that knows // how to deal with render arrays. $this->skipRenderVarFunction = TRUE; Loading
core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php +40 −0 Changes for core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php: 40 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -144,6 +144,46 @@ public static function providerTestEscaping(): array { ]; } /** * Tests that Twig's escape filters are replaced with our own. */ #[DataProvider('providerTestEscapeFilterOverride')] public function testEscapeFilterOverride(string $template, string $expected): void { $loader = new ArrayLoader(['test' => $template]); $twig = new Environment($loader, [ 'cache' => FALSE, 'autoescape' => 'html', ]); $twig->addExtension($this->systemUnderTest); // Explicit and automatic escaping must both call our filter. $source = $twig->compileSource($loader->getSourceContext('test')); $this->assertStringContainsString(sprintf("\$this->extensions['%s']->escapeFilter(", TwigExtension::class), $source); $this->assertSame($expected, $twig->render('test', [ 'text' => '<b>', 'markup' => Markup::create('<b>'), ])); } /** * Provides test data for testEscapeFilterOverride(). * * @return array * An array of test data, each containing a Twig template string and the * expected rendered output. */ public static function providerTestEscapeFilterOverride(): array { return [ 'auto-escaped string' => ['{{ text }}', '<b>'], 'auto-escaped markup' => ['{{ markup }}', '<b>'], 'escape filter' => ['{{ text|escape }}', '<b>'], 'e filter' => ['{{ text|e }}', '<b>'], 'escape filter with html strategy' => ['{{ text|e("html") }}', '<b>'], 'filter before auto-escaping' => ['{{ text|upper }}', '<B>'], ]; } /** * Tests the active_theme function. */ Loading