diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php index 31c10a7..5b37a70 100644 --- a/core/lib/Drupal/Core/Theme/ThemeManager.php +++ b/core/lib/Drupal/Core/Theme/ThemeManager.php @@ -7,12 +7,12 @@ namespace Drupal\Core\Theme; +use Drupal\Core\Render\SafeString; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\StackedRouteMatchInterface; use Symfony\Component\HttpFoundation\RequestStack; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Template\Attribute; -use Drupal\Component\Utility\SafeMarkup; /** * Provides the default implementation of a theme manager. @@ -317,7 +317,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 +390,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..4697c2f 100644 --- a/core/modules/system/src/Tests/Theme/ThemeTest.php +++ b/core/modules/system/src/Tests/Theme/ThemeTest.php @@ -13,6 +13,7 @@ use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Route; +use Drupal\Component\Utility\SafeStringInterface; /** * Tests low-level theme functions. @@ -59,12 +60,18 @@ function testAttributeMerging() { * Test that _theme() returns expected data types. */ 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. + // theme_test_false is an implemented theme hook so \Drupal::theme() service + // should 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->assertIdentical($output, '', 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 %}