diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php index 8c1e31f..dd8ce8f 100644 --- a/core/modules/views/src/Plugin/views/PluginBase.php +++ b/core/modules/views/src/Plugin/views/PluginBase.php @@ -9,6 +9,7 @@ use Drupal\Component\Plugin\DependentPluginInterface; use Drupal\Component\Utility\SafeMarkup; +use Drupal\Component\Utility\Xss; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -341,15 +342,18 @@ public function globalTokenReplace($string = '', array $options = array()) { * handle those as well as the new Twig-based tokens (e.g.: {{ field_name }}) * * @param $text - * String with possible tokens. + * Unsanitized string with possible tokens. * @param $tokens * Array of token => replacement_value items. * * @return String */ protected function viewsTokenReplace($text, $tokens) { - if (empty($tokens)) { - return $text; + // The "Administer views" permission is required to enter what ends up as + // $text so we can use the more permissive XSS filter. + $filtered_text = Xss::filterAdmin($text); + if (empty($tokens) || empty($filtered_text)) { + return $filtered_text; } // Separate Twig tokens from other tokens (e.g.: contextual filter tokens in @@ -371,10 +375,18 @@ protected function viewsTokenReplace($text, $tokens) { // through an inline template for rendering and replacement. $text = strtr($text, $other_tokens); if ($twig_tokens && !empty($text)) { + // Use the unfiltered text for the Twig template, then filter the output. + // Otherwise, Xss::filterAdmin() could remove valid Twig syntax before the + // template is parsed. $build = array( '#type' => 'inline_template', '#template' => $text, '#context' => $twig_tokens, + '#post_render' => [ + function ($children, $elements) { + return Xss::filterAdmin($children); + } + ], ); return $this->getRenderer()->render($build); diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index f17df18..44ffe57 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -1284,9 +1284,7 @@ public function renderText($alter) { * Render this field as user-defined altered text. */ protected function renderAltered($alter, $tokens) { - // Filter this right away as our substitutions are already sanitized. - $template = Xss::filterAdmin($alter['text']); - return $this->viewsTokenReplace($template, $tokens); + return $this->viewsTokenReplace($alter['text'], $tokens); } /** diff --git a/core/modules/views/src/Tests/Handler/FieldUnitTest.php b/core/modules/views/src/Tests/Handler/FieldUnitTest.php index 7c319d4..a6021ee 100644 --- a/core/modules/views/src/Tests/Handler/FieldUnitTest.php +++ b/core/modules/views/src/Tests/Handler/FieldUnitTest.php @@ -175,7 +175,7 @@ public function testFieldTokens() { $name_field_1->options['alter']['text'] = '{{ name_1 }} {{ name }}'; $name_field_2->options['alter']['alter_text'] = TRUE; - $name_field_2->options['alter']['text'] = '{{ name_2 }} {{ name_1 }}'; + $name_field_2->options['alter']['text'] = '{% if name_2|length > 3 %}{{ name_2 }} {{ name_1 }}{% endif %}'; foreach ($view->result as $row) { $expected_output_0 = $row->views_test_data_name; diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php index 0e45537..8b6c091 100644 --- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php @@ -453,6 +453,11 @@ public function testRenderAsLinkWithPathAndTokens($path, $tokens, $link_html) { '#type' => 'inline_template', '#template' => 'base:test-path/' . explode('/', $path)[1], '#context' => ['foo' => 123], + '#post_render' => [ + function ($children, $elements) { + return $children; + } + ], ]; $this->renderer->expects($this->once())