diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php index 31c10a7..7add727 100644 --- a/core/lib/Drupal/Core/Theme/ThemeManager.php +++ b/core/lib/Drupal/Core/Theme/ThemeManager.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Theme; +use Drupal\Core\Render\SafeString; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\StackedRouteMatchInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -317,7 +318,10 @@ public function render($hook, array $variables) { $output = ''; if (isset($info['function'])) { if (function_exists($info['function'])) { - $output = SafeMarkup::set($info['function']($variables)); + // Theme functions do not render via the theme engine, so the output + // is not autoescaped. However, we can only presume that the theme + // function has been written correctly and that the markup is safe. + $output = SafeString::create($info['function']($variables)); } } else { @@ -387,7 +391,7 @@ public function render($hook, array $variables) { $output = $render_function($template_file, $variables); } - return (string) $output; + return ($output instanceof SafeString) ? $output : (string) $output; } /** diff --git a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php index ebbfcb5..b61ff84 100644 --- a/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php +++ b/core/lib/Drupal/Core/Theme/ThemeManagerInterface.php @@ -26,8 +26,8 @@ * @param array $variables * An associative array of theme variables. * - * @return string - * The rendered output. + * @return string|\Drupal\Component\Utility\SafeStringInterface + * The rendered output, or a SafeString object. */ public function render($hook, array $variables); diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php index 1918d42..bc66e27 100644 --- a/core/modules/simpletest/src/AssertContentTrait.php +++ b/core/modules/simpletest/src/AssertContentTrait.php @@ -812,7 +812,7 @@ protected function assertThemeOutput($callback, array $variables = array(), $exp /** @var \Drupal\Core\Render\RendererInterface $renderer */ $renderer = \Drupal::service('renderer'); - $output = $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) { + $output = (string) $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) { return \Drupal::theme()->render($callback, $variables); }); $this->verbose( diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php index c2c0e0b..5503fea 100644 --- a/core/modules/system/src/Tests/Theme/ThemeTest.php +++ b/core/modules/system/src/Tests/Theme/ThemeTest.php @@ -60,11 +60,16 @@ function testAttributeMerging() { */ function testThemeDataTypes() { // theme_test_false is an implemented theme hook so \Drupal::theme() service should - // return a string, even though the theme function itself can return anything. + // return a string or an object that implements SafeStringInterface, even though the + // theme function itself can return anything. $foos = array('null' => NULL, 'false' => FALSE, 'integer' => 1, 'string' => 'foo'); foreach ($foos as $type => $example) { $output = \Drupal::theme()->render('theme_test_foo', array('foo' => $example)); - $this->assertTrue(is_string($output), format_string('\Drupal::theme() returns a string for data type !type.', array('!type' => $type))); + if (is_string($output)) { + $this->assertTrue(TRUE, format_string('\Drupal::theme() returns a string for data type !type.', array('!type' => $type))); + } else { + $this->assertTrue($output instanceof SafeStringInterface, format_string('\Drupal::theme() returns an object that implements SafeStringInterface for data type !type.', array('!type' => $type))); + } } // suggestionnotimplemented is not an implemented theme hook so \Drupal::theme() service diff --git a/core/modules/system/templates/dropbutton-wrapper.html.twig b/core/modules/system/templates/dropbutton-wrapper.html.twig index ca0ff7e..d4c8d90 100644 --- a/core/modules/system/templates/dropbutton-wrapper.html.twig +++ b/core/modules/system/templates/dropbutton-wrapper.html.twig @@ -12,7 +12,7 @@ * @ingroup themeable */ #} -{% if children %} +{% if children|length %} {% spaceless %}
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index c83b9c4..c6592e4 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -1173,7 +1173,7 @@ public function advancedRender(ResultRow $values) { } // This happens here so that renderAsLink can get the unaltered value of // this field as a token rather than the altered value. - $this->last_render = $value; + $this->last_render = (string) $value; } if (empty($this->last_render)) { @@ -1214,6 +1214,7 @@ public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) { */ public function renderText($alter) { $value = $this->last_render; + $value_is_safe = SafeMarkup::isSafe($value); if (!empty($alter['alter_text']) && $alter['text'] !== '') { $tokens = $this->getRenderTokens($alter); @@ -1239,6 +1240,9 @@ public function renderText($alter) { if ($alter['phase'] == static::RENDER_TEXT_PHASE_EMPTY && $no_rewrite_for_empty) { // If we got here then $alter contains the value of "No results text" // and so there is nothing left to do. + if ($value_is_safe) { + $value = SafeString::create($value); + } return $value; } @@ -1275,6 +1279,11 @@ public function renderText($alter) { if (!empty($alter['nl2br'])) { $value = nl2br($value); } + + // Preserve whether or not the string is safe. + if ($value_is_safe) { + $value = SafeString::create($value . $suffix); + } $this->last_render_text = $value; if (!empty($alter['make_link']) && (!empty($alter['path']) || !empty($alter['url']))) { @@ -1284,7 +1293,13 @@ public function renderText($alter) { $value = $this->renderAsLink($alter, $value, $tokens); } - return $value . $suffix; + // Preserve whether or not the string is safe. + if ($value_is_safe) { + return SafeString::create($value . $suffix); + } + else { + return $value . $suffix; + } } /** diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index af5ebe7..7f8e6eb 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -6,6 +6,7 @@ */ use Drupal\Component\Utility\SafeMarkup; +use Drupal\Core\Render\SafeString; use Drupal\Core\Extension\Extension; /** @@ -74,7 +75,7 @@ function twig_render_template($template_file, array $variables) { } if ($twig_service->isDebug()) { $output['debug_prefix'] .= "\n\n"; - $output['debug_prefix'] .= "\n"; + $output['debug_prefix'] .= "\n"; // If there are theme suggestions, reverse the array so more specific // suggestions are shown first. if (!empty($variables['theme_hook_suggestions'])) { @@ -108,12 +109,13 @@ function twig_render_template($template_file, array $variables) { $prefix = ($template == $current_template) ? 'x' : '*'; $suggestion = $prefix . ' ' . $template; } - $output['debug_info'] .= "\n"; + $output['debug_info'] .= "\n"; } - $output['debug_info'] .= "\n\n"; - $output['debug_suffix'] .= "\n\n\n"; + $output['debug_info'] .= "\n\n"; + $output['debug_suffix'] .= "\n\n\n"; } - return SafeMarkup::set(implode('', $output)); + // This output has already been rendered and is therefore considered safe. + return SafeString::create(implode('', $output)); } /**