diff --git a/core/includes/common.inc b/core/includes/common.inc index f30bcdf..c465349 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -20,6 +20,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Language\Language; use Drupal\Core\Site\Settings; +use Drupal\Core\Template\SafeMarkup; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; use Drupal\Core\PhpStorage\PhpStorageFactory; @@ -914,8 +915,10 @@ function l($text, $path, array $options = array()) { // Sanitize the link text if necessary. $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']); - - return '' . $text . ''; + // $attributes and $url is known to be safe. + $safe = $text instanceof SafeMarkup; + $link = '' . $text . ''; + return $safe ? new SafeMarkup($link) : $link; } /** @@ -2857,6 +2860,7 @@ function drupal_pre_render_conditional_comments($elements) { * - meta: To provide meta information, such as a page refresh. * - link: To refer to stylesheets and other contextual information. * - script: To load JavaScript. + * This is not HTML escaped, do not pass in user input. * - #attributes: (optional) An array of HTML attributes to apply to the * tag. * - #value: (optional) A string containing tag content, such as inline @@ -2869,7 +2873,8 @@ function drupal_pre_render_conditional_comments($elements) { function drupal_pre_render_html_tag($element) { $attributes = isset($element['#attributes']) ? new Attribute($element['#attributes']) : ''; if (!isset($element['#value'])) { - $markup = '<' . $element['#tag'] . $attributes . " />\n"; + // Attributes is safe. + $markup = new SafeMarkup('<' . $element['#tag'] . $attributes . " />\n"); } else { $markup = '<' . $element['#tag'] . $attributes . '>'; @@ -2881,6 +2886,8 @@ function drupal_pre_render_html_tag($element) { $markup .= $element['#value_suffix']; } $markup .= '\n"; + // @TODO! + $markup = new SafeMarkup($markup); } if (!empty($element['#noscript'])) { $element['#markup'] = ''; @@ -3379,13 +3386,27 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { // is common for theme suggestions. $theme_is_implemented = ($elements['#children'] !== FALSE); } + $safe = TRUE; + $get_string = function ($element) use (&$safe) { + if (is_object($element) && method_exists($element, '__toString')) { + $markup = $element->__toString(); + } + else { + $markup = $element; + } + $safe = $safe && (!$markup || $element instanceof SafeMarkup || $markup instanceof SafeMarkup); + return $markup; + }; // If #theme is not implemented or #render_children is set and the element has // an empty #children attribute, render the children now. This is the same // process as drupal_render_children() but is inlined for speed. if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) { foreach ($children as $key) { - $elements['#children'] .= drupal_render($elements[$key], TRUE); + $elements['#children'] .= $get_string(drupal_render($elements[$key], TRUE)); + } + if ($safe) { + $elements['#children'] = new SafeMarkup($elements['#children']); } } @@ -3397,7 +3418,10 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { // required. Eventually #theme_wrappers will expect both #markup and // #children to be a single string as #children. if (!$theme_is_implemented && isset($elements['#markup'])) { - $elements['#children'] = $elements['#markup'] . $elements['#children']; + $elements['#children'] = $get_string($elements['#markup']) . $get_string($elements['#children']); + if ($safe) { + $elements['#children'] = new SafeMarkup($elements['#children']); + } } // Let the theme functions in #theme_wrappers add markup around the rendered @@ -3481,7 +3505,7 @@ function drupal_render(&$elements, $is_recursive_call = FALSE) { } $elements['#printed'] = TRUE; - return $elements['#markup']; + return $safe ? new SafeMarkup($elements['#markup']) : $elements['#markup']; } /** @@ -3504,12 +3528,15 @@ function drupal_render_children(&$element, $children_keys = NULL) { $children_keys = Element::children($element); } $output = ''; + $safe = TRUE; foreach ($children_keys as $key) { if (!empty($element[$key])) { - $output .= drupal_render($element[$key]); + $markup = drupal_render($element[$key]); + $safe = $safe && $markup instanceof SafeMarkup; + $output .= $markup; } } - return $output; + return $safe ? new SafeMarkup($output) : $output; } /** diff --git a/core/includes/form.inc b/core/includes/form.inc index 42206a4..3be1d3e 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -15,6 +15,7 @@ use Drupal\Core\Language\Language; use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; +use Drupal\Core\Template\SafeMarkup; use Drupal\Core\Utility\Color; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -983,7 +984,7 @@ function form_select_options($element, $choices = NULL) { $options .= form_select_options($element, $choice); $options .= ''; } - elseif (is_object($choice)) { + elseif (is_object($choice) && isset($choice->option)) { $options .= form_select_options($element, $choice->option); } else { @@ -998,7 +999,7 @@ function form_select_options($element, $choices = NULL) { $options .= ''; } } - return $options; + return new SafeMarkup($options); } /** @@ -2348,7 +2349,10 @@ function template_preprocess_input(&$variables) { function theme_input($variables) { $element = $variables['element']; $attributes = $variables['attributes']; - return '' . drupal_render_children($element); + $children = drupal_render_children($element); + $safe = $children instanceof SafeMarkup; + $return = '' . $children; + return $safe ? new SafeMarkup($return) : $return; } /** @@ -2976,8 +2980,6 @@ function theme_form_element_label($variables) { return ''; } - $title = Xss::filterAdmin($element['#title']); - $attributes = array(); // Style the label as class option to display inline with the element. if ($element['#title_display'] == 'after') { @@ -3008,7 +3010,10 @@ function theme_form_element_label($variables) { $attributes['class'][] = 'form-required'; } - return '' . $title . ''; + $title = Xss::filterAdmin($element['#title']); + + // Attribute is always safe and title is too so mark this safe. + return new SafeMarkup('' . $title . ''); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index e44f13d..e5a917f 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -21,6 +21,7 @@ use Drupal\Core\Page\MetaElement; use Drupal\Core\Template\Attribute; use Drupal\Core\Template\RenderWrapper; +use Drupal\Core\Template\SafeMarkup; use Drupal\Core\Theme\ThemeSettings; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Render\Element; @@ -650,7 +651,7 @@ function _theme($hook, $variables = array()) { // restore path_to_theme() $theme_path = $temp; - return (string) $output; + return $output; } /** @@ -2052,7 +2053,7 @@ function template_preprocess_html(&$variables) { // Wrap function calls in an object so they can be called when printed. $variables['head'] = new RenderWrapper(function() use ($page) { - return implode("\n", $page->getMetaElements()) . implode("\n", $page->getLinkElements()); + return new SafeMarkup(implode("\n", $page->getMetaElements()) . implode("\n", $page->getLinkElements())); }); $variables['styles'] = new RenderWrapper('drupal_get_css'); $variables['scripts'] = new RenderWrapper('drupal_get_js'); diff --git a/core/lib/Drupal/Component/Utility/String.php b/core/lib/Drupal/Component/Utility/String.php index 9796a5f..a71dc1a 100644 --- a/core/lib/Drupal/Component/Utility/String.php +++ b/core/lib/Drupal/Component/Utility/String.php @@ -6,6 +6,7 @@ */ namespace Drupal\Component\Utility; +use Drupal\Core\Template\SafeMarkup; /** * Provides helpers to operate on strings. @@ -31,7 +32,7 @@ class String { * @see drupal_validate_utf8() */ public static function checkPlain($text) { - return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); + return new SafeMarkup(htmlspecialchars($text, ENT_QUOTES, 'UTF-8')); } /** @@ -65,7 +66,8 @@ public static function decodeEntities($text) { * addition to formatting it. * * @param $string - * A string containing placeholders. + * A string containing placeholders. The string itself is not escaped, any + * unsafe content must be in $args and inserted via placeholders. * @param $args * An associative array of replacements to make. Occurrences in $string of * any key in $args are replaced with the corresponding value, after @@ -108,7 +110,7 @@ public static function format($string, array $args = array()) { // Pass-through. } } - return strtr($string, $args); + return new SafeMarkup(strtr($string, $args)); } /** @@ -123,7 +125,24 @@ public static function format($string, array $args = array()) { * The formatted text (html). */ public static function placeholder($text) { - return '' . static::checkPlain($text) . ''; + return new SafeMarkup('' . static::checkPlain($text) . ''); } + public static function concat($string1, $string2) { + $string = $string1 . $string2; + $safe = $string1 instanceof SafeMarkup && $string2 instanceof SafeMarkup; + return $safe ? new SafeMarkup($string) : $string; + } + + public static function implode($delimiter, array $array) { + $real_delimiter = ''; + $safe = TRUE; + $imploded = ''; + foreach ($array as $string) { + $safe = $safe && $string instanceof SafeMarkup; + $imploded .= $real_delimiter . $string; + $real_delimiter = $delimiter; + } + return $safe ? new SafeMarkup($imploded) : $imploded; + } } diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php index dc49913..8117ccf 100644 --- a/core/lib/Drupal/Component/Utility/Xss.php +++ b/core/lib/Drupal/Component/Utility/Xss.php @@ -6,6 +6,7 @@ */ namespace Drupal\Component\Utility; +use Drupal\Core\Template\SafeMarkup; /** * Provides helper to filter for cross-site scripting. @@ -90,7 +91,7 @@ public static function filter($string, $html_tags = array('a', 'em', 'strong', ' $splitter = function ($matches) use ($html_tags, $mode) { return static::split($matches[1], $html_tags, $mode); }; - return preg_replace_callback('% + return new SafeMarkup(preg_replace_callback('% ( <(?=[^a-zA-Z!/]) # a lone < | # or @@ -99,7 +100,7 @@ public static function filter($string, $html_tags = array('a', 'em', 'strong', ' <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string | # or > # just a > - )%x', $splitter, $string); + )%x', $splitter, $string)); } /** diff --git a/core/lib/Drupal/Core/Config/StorableConfigBase.php b/core/lib/Drupal/Core/Config/StorableConfigBase.php index 18c0f07..c45092b 100644 --- a/core/lib/Drupal/Core/Config/StorableConfigBase.php +++ b/core/lib/Drupal/Core/Config/StorableConfigBase.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\Config\Schema\SchemaIncompleteException; +use Drupal\Core\Template\SafeMarkup; use Drupal\Core\TypedData\PrimitiveInterface; use Drupal\Core\TypedData\Type\FloatInterface; use Drupal\Core\TypedData\Type\IntegerInterface; @@ -167,6 +168,9 @@ protected function validateValue($key, $value) { * Exception on unsupported/undefined data type deducted. */ protected function castValue($key, $value) { + if ($value instanceof SafeMarkup) { + $value = $value->__toString(); + } if ($value === NULL) { $value = NULL; } diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index fe23fb6..99784f7 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -101,7 +101,7 @@ public static function registerTwig(ContainerBuilder $container) { 'cache' => drupal_installation_attempted() ? FALSE : Settings::get('twig_cache', TRUE), // @todo Remove in followup issue // @see http://drupal.org/node/1712444. - 'autoescape' => FALSE, + 'autoescape' => TRUE, 'debug' => Settings::get('twig_debug', FALSE), 'auto_reload' => Settings::get('twig_auto_reload', NULL), )) diff --git a/core/lib/Drupal/Core/Extension/ThemeHandler.php b/core/lib/Drupal/Core/Extension/ThemeHandler.php index dbe2c78..791a927 100644 --- a/core/lib/Drupal/Core/Extension/ThemeHandler.php +++ b/core/lib/Drupal/Core/Extension/ThemeHandler.php @@ -626,6 +626,7 @@ public function getName($theme) { if (!isset($themes[$theme])) { throw new \InvalidArgumentException(String::format('Requested the name of a non-existing theme @theme', array('@theme' => $theme))); } + return \Drupal::config('system.site')->get('name'); return String::checkPlain($themes[$theme]->info['name']); } diff --git a/core/lib/Drupal/Core/Page/HeadElement.php b/core/lib/Drupal/Core/Page/HeadElement.php index 85055ad..70cf2b7 100644 --- a/core/lib/Drupal/Core/Page/HeadElement.php +++ b/core/lib/Drupal/Core/Page/HeadElement.php @@ -8,11 +8,12 @@ namespace Drupal\Core\Page; use Drupal\Core\Template\Attribute; +use Drupal\Core\Template\SafeMarkup; /** * This class represents an HTML element that appears in the HEAD tag. */ -class HeadElement { +class HeadElement extends SafeMarkup { /** * An array of attributes for this element. diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php index ead5d05..18a7430 100644 --- a/core/lib/Drupal/Core/Template/Attribute.php +++ b/core/lib/Drupal/Core/Template/Attribute.php @@ -31,7 +31,7 @@ * // Produces * @endcode */ -class Attribute implements \ArrayAccess, \IteratorAggregate { +class Attribute extends SafeMarkup implements \ArrayAccess, \IteratorAggregate { /** * Stores the attribute data. @@ -89,6 +89,9 @@ protected function createAttributeValue($name, $value) { elseif (!is_object($value)) { $value = new AttributeString($name, $value); } + elseif (!method_exists($value, 'render')) { + throw new \Exception('boo!'); + } return $value; } diff --git a/core/lib/Drupal/Core/Template/AttributeArray.php b/core/lib/Drupal/Core/Template/AttributeArray.php index 95e0ef3..4d8145f 100644 --- a/core/lib/Drupal/Core/Template/AttributeArray.php +++ b/core/lib/Drupal/Core/Template/AttributeArray.php @@ -66,7 +66,7 @@ public function offsetExists($offset) { * Implements the magic __toString() method. */ public function __toString() { - return String::checkPlain(implode(' ', $this->value)); + return (string) String::checkPlain(implode(' ', $this->value)); } /** diff --git a/core/lib/Drupal/Core/Template/AttributeBoolean.php b/core/lib/Drupal/Core/Template/AttributeBoolean.php index 4e9ea67..b5a0957 100644 --- a/core/lib/Drupal/Core/Template/AttributeBoolean.php +++ b/core/lib/Drupal/Core/Template/AttributeBoolean.php @@ -42,7 +42,7 @@ public function render() { * Implements the magic __toString() method. */ public function __toString() { - return $this->value === FALSE ? '' : String::checkPlain($this->name); + return $this->value === FALSE ? '' : (string) String::checkPlain($this->name); } } diff --git a/core/lib/Drupal/Core/Template/AttributeString.php b/core/lib/Drupal/Core/Template/AttributeString.php index 07211be..e1c69dc 100644 --- a/core/lib/Drupal/Core/Template/AttributeString.php +++ b/core/lib/Drupal/Core/Template/AttributeString.php @@ -30,7 +30,7 @@ class AttributeString extends AttributeValueBase { * Implements the magic __toString() method. */ public function __toString() { - return String::checkPlain($this->value); + return (string) String::checkPlain($this->value); } } diff --git a/core/lib/Drupal/Core/Template/SafeMarkup.php b/core/lib/Drupal/Core/Template/SafeMarkup.php new file mode 100644 index 0000000..a874c62 --- /dev/null +++ b/core/lib/Drupal/Core/Template/SafeMarkup.php @@ -0,0 +1,22 @@ +__toString(); + } +} diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index b547d95..88d5d60 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -13,6 +13,7 @@ use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Routing\UrlGeneratorInterface; +use Drupal\Core\Template\SafeMarkup; use Drupal\Core\Url; /** @@ -122,8 +123,10 @@ public function generateFromUrl($text, Url $url) { // Sanitize the link text if necessary. $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']); - - return '' . $text . ''; + // $attributes and $url is known to be safe. + $safe = $text instanceof SafeMarkup; + $link = '' . $text . ''; + return $safe ? new SafeMarkup($link) : $link; } /** diff --git a/core/modules/book/lib/Drupal/book/Tests/BookTest.php b/core/modules/book/lib/Drupal/book/Tests/BookTest.php index cfd7559..b5f0ee8 100644 --- a/core/modules/book/lib/Drupal/book/Tests/BookTest.php +++ b/core/modules/book/lib/Drupal/book/Tests/BookTest.php @@ -193,15 +193,15 @@ function checkBookNode(EntityInterface $node, $nodes, $previous = FALSE, $up = F // Check previous, up, and next links. if ($previous) { - $this->assertRaw(l(' ' . $previous->label(), 'node/' . $previous->id(), array('html' => TRUE, 'attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page')))), 'Previous page link found.'); + $this->assertRaw((string) l(' ' . $previous->label(), 'node/' . $previous->id(), array('html' => TRUE, 'attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page')))), 'Previous page link found.'); } if ($up) { - $this->assertRaw(l('Up', 'node/' . $up->id(), array('html'=> TRUE, 'attributes' => array('title' => t('Go to parent page')))), 'Up page link found.'); + $this->assertRaw((string) l('Up', 'node/' . $up->id(), array('html'=> TRUE, 'attributes' => array('title' => t('Go to parent page')))), 'Up page link found.'); } if ($next) { - $this->assertRaw(l($next->label() . ' ', 'node/' . $next->id(), array('html'=> TRUE, 'attributes' => array('rel' => array('next'), 'title' => t('Go to next page')))), 'Next page link found.'); + $this->assertRaw((string) l($next->label() . ' ', 'node/' . $next->id(), array('html'=> TRUE, 'attributes' => array('rel' => array('next'), 'title' => t('Go to next page')))), 'Next page link found.'); } // Compute the expected breadcrumb. diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 3efe9ad..cd17f6a 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -13,6 +13,7 @@ use Drupal\Core\Render\Element; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; +use Drupal\Core\Template\SafeMarkup; use Drupal\filter\FilterFormatInterface; use Drupal\filter\Plugin\FilterInterface; use Symfony\Component\HttpFoundation\Request; @@ -419,7 +420,7 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE, \Drupal::cache('filter')->set($cache_id, $text, Cache::PERMANENT, array('filter_format' => $format->id())); } - return $text; + return new SafeMarkup($text); } /** diff --git a/core/modules/filter/templates/filter-guidelines.html.twig b/core/modules/filter/templates/filter-guidelines.html.twig index 88a3b47..ecf9b94 100644 --- a/core/modules/filter/templates/filter-guidelines.html.twig +++ b/core/modules/filter/templates/filter-guidelines.html.twig @@ -20,6 +20,6 @@ */ #} -

{{ format.name|escape }}

+

{{ format.name }}

{{ tips }} diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php index 6217835..cca701d 100644 --- a/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php +++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumTest.php @@ -564,7 +564,7 @@ private function verifyForums(EntityInterface $node, $admin, $response = 200) { '#theme' => 'breadcrumb', '#breadcrumb' => $breadcrumb_build, ); - $this->assertRaw(drupal_render($breadcrumb), 'Breadcrumbs were displayed'); + $this->assertRaw((string) drupal_render($breadcrumb), 'Breadcrumbs were displayed'); // View forum edit node. $this->drupalGet('node/' . $node->id() . '/edit'); @@ -624,7 +624,7 @@ private function verifyForumView($forum, $parent = NULL) { '#theme' => 'breadcrumb', '#breadcrumb' => $breadcrumb_build, ); - $this->assertRaw(drupal_render($breadcrumb), 'Breadcrumbs were displayed'); + $this->assertRaw((string) drupal_render($breadcrumb), 'Breadcrumbs were displayed'); } /** diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php index 3d8309a..7066e21 100644 --- a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php +++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php @@ -55,7 +55,7 @@ function testPopularContentBlock() { $this->assertText('All time', 'Found the all time popular content.'); $this->assertText('Last viewed', 'Found the last viewed popular content.'); - $this->assertRaw(l($node->label(), 'node/' . $node->id()), 'Found link to visited node.'); + $this->assertRaw((string) l($node->label(), 'node/' . $node->id()), 'Found link to visited node.'); } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php index caa80a2..d3b21e3 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php @@ -277,7 +277,8 @@ function testDrupalRenderBasics() { ); foreach($types as $type) { - $this->assertIdentical(drupal_render($type['value']), $type['expected'], '"' . $type['name'] . '" input rendered correctly by drupal_render().'); + $value = drupal_render($type['value']); + $this->assertIdentical((string) $value, $type['expected'], '"' . $type['name'] . '" input rendered correctly by drupal_render().'); } } @@ -394,14 +395,14 @@ function testDrupalRenderThemeArguments() { '#theme' => 'common_test_foo', ); // Test that defaults work. - $this->assertEqual(drupal_render($element), 'foobar', 'Defaults work'); + $this->assertEqual((string) drupal_render($element), 'foobar', 'Defaults work'); $element = array( '#theme' => 'common_test_foo', '#foo' => $this->randomName(), '#bar' => $this->randomName(), ); // Tests that passing arguments to the theme function works. - $this->assertEqual(drupal_render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works'); + $this->assertEqual((string) drupal_render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works'); } /** @@ -470,7 +471,7 @@ function testDrupalRenderPostRenderCache() { $element = $test_element; $element['#markup'] = '

#cache disabled

'; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); @@ -486,7 +487,7 @@ function testDrupalRenderPostRenderCache() { $element['#cache'] = array('cid' => 'post_render_cache_test_GET'); $element['#markup'] = '

#cache enabled, GET

'; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -509,7 +510,7 @@ function testDrupalRenderPostRenderCache() { $element['#cache'] = array('cid' => 'post_render_cache_test_GET'); $element['#markup'] = '

#cache enabled, GET

'; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -526,7 +527,7 @@ function testDrupalRenderPostRenderCache() { $element['#cache'] = array('cid' => 'post_render_cache_test_POST'); $element['#markup'] = '

#cache enabled, POST

'; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -589,7 +590,7 @@ function testDrupalRenderChildrenPostRenderCache() { ); $element = $test_element; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -635,7 +636,7 @@ function testDrupalRenderChildrenPostRenderCache() { drupal_static_reset('_drupal_add_js'); $element = $test_element; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); @@ -647,7 +648,7 @@ function testDrupalRenderChildrenPostRenderCache() { unset($test_element['#cache']); $element = $test_element; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $settings = $this->parseDrupalSettings(drupal_get_js()); $expected_settings = $context_1 + $context_2 + $context_3; @@ -667,7 +668,7 @@ function testDrupalRenderChildrenPostRenderCache() { $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent'); $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child'); $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], '

overridden

', '#markup is overridden.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -742,7 +743,7 @@ function testDrupalRenderChildrenPostRenderCache() { $element = $test_element; $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent'); $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['foo'], 'bar', 'Original JavaScript setting is added to the page.'); @@ -754,7 +755,7 @@ function testDrupalRenderChildrenPostRenderCache() { $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child'); $element = $element['child']; $output = drupal_render($element); - $this->assertIdentical($output, '

overridden

', 'Output is overridden.'); + $this->assertIdentical((string) $output, '

overridden

', 'Output is overridden.'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $settings = $this->parseDrupalSettings(drupal_get_js()); $expected_settings = $context_2 + $context_3; @@ -790,7 +791,7 @@ function testDrupalRenderRenderCachePlaceholder() { drupal_static_reset('_drupal_add_js'); $element = $test_element; $output = drupal_render($element); - $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertIdentical((string) $output, $expected_output, 'Placeholder was replaced in output'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); @@ -803,7 +804,7 @@ function testDrupalRenderRenderCachePlaceholder() { $element = $test_element; $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET'); $output = drupal_render($element); - $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertIdentical((string) $output, $expected_output, 'Placeholder was replaced in output'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -840,7 +841,7 @@ function testDrupalRenderRenderCachePlaceholder() { $element = $test_element; $element['#cache'] = array('cid' => 'render_cache_placeholder_test_GET'); $output = drupal_render($element); - $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertIdentical((string) $output, $expected_output, 'Placeholder was replaced in output'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -880,7 +881,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { drupal_static_reset('_drupal_add_js'); $element = $container; $output = drupal_render($element); - $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertIdentical((string) $output, $expected_output, 'Placeholder was replaced in output'); $settings = $this->parseDrupalSettings(drupal_get_js()); $this->assertIdentical($settings['common_test'], $context, '#attached is modified; JavaScript setting is added to page.'); @@ -899,7 +900,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { $element['#children'] = drupal_render($child, TRUE); // Eventually, drupal_render() gets called on the root element. $output = drupal_render($element); - $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertIdentical((string) $output, $expected_output, 'Placeholder was replaced in output'); $this->assertTrue(isset($element['#printed']), 'No cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); $settings = $this->parseDrupalSettings(drupal_get_js()); @@ -996,7 +997,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { $child = &$element['test_element']; $element['#children'] = drupal_render($child, TRUE); $output = drupal_render($element); - $this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output'); + $this->assertIdentical((string) $output, $expected_output, 'Placeholder was replaced in output'); $this->assertFalse(isset($element['#printed']), 'Cache hit'); $this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.'); $settings = $this->parseDrupalSettings(drupal_get_js()); diff --git a/core/modules/system/templates/block--system-branding-block.html.twig b/core/modules/system/templates/block--system-branding-block.html.twig index 2a12c7a..4cf0f1a 100644 --- a/core/modules/system/templates/block--system-branding-block.html.twig +++ b/core/modules/system/templates/block--system-branding-block.html.twig @@ -23,7 +23,7 @@ {% endif %} {% if site_name %} {% endif %} {% if site_slogan %} diff --git a/core/modules/system/templates/datetime.html.twig b/core/modules/system/templates/datetime.html.twig index 25ef788..183b834 100644 --- a/core/modules/system/templates/datetime.html.twig +++ b/core/modules/system/templates/datetime.html.twig @@ -25,5 +25,4 @@ * @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime */ #} -{# @todo Revisit once http://drupal.org/node/1825952 is resolved. #} -{{ html ? text|raw : text|escape }} +{{ html ? text|raw : text }} diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermIndexTest.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermIndexTest.php index e79d159..dbe121c 100644 --- a/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermIndexTest.php +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Tests/TermIndexTest.php @@ -215,6 +215,6 @@ function testTaxonomyTermHierarchyBreadcrumbs() { // Verify that the page breadcrumbs include a link to the parent term. $this->drupalGet('taxonomy/term/' . $term1->id()); - $this->assertRaw(l($term2->getName(), 'taxonomy/term/' . $term2->id()), 'Parent term link is displayed when viewing the node.'); + $this->assertRaw((string) l($term2->getName(), 'taxonomy/term/' . $term2->id()), 'Parent term link is displayed when viewing the node.'); } } diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php index 25d8233..dd6a372 100644 --- a/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php @@ -55,7 +55,7 @@ function testNoReleasesAvailable() { // Cannot use $this->standardTests() because we need to check for the // 'No available releases found' string. $this->assertRaw('

' . t('Drupal core') . '

'); - $this->assertRaw(l(t('Drupal'), 'http://example.com/project/drupal')); + $this->assertRaw((string) l(t('Drupal'), 'http://example.com/project/drupal')); $this->assertText(t('Up to date')); $this->assertRaw('

' . t('Modules') . '

'); $this->assertNoText(t('Update available')); @@ -88,7 +88,7 @@ function testUpdateContribBasic() { $this->assertText(t('Up to date')); $this->assertRaw('

' . t('Modules') . '

'); $this->assertNoText(t('Update available')); - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); + $this->assertRaw((string) l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); } /** @@ -151,8 +151,8 @@ function testUpdateContribOrder() { // its own project on the report. $this->assertNoRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project does not appear.'); // The other two should be listed as projects. - $this->assertRaw(l(t('BBB Update test'), 'http://example.com/project/bbb_update_test'), 'Link to bbb_update_test project appears.'); - $this->assertRaw(l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), 'Link to bbb_update_test project appears.'); + $this->assertRaw((string) l(t('BBB Update test'), 'http://example.com/project/bbb_update_test'), 'Link to bbb_update_test project appears.'); + $this->assertRaw((string) l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), 'Link to bbb_update_test project appears.'); // We want to make sure we see the BBB project before the CCC project. // Instead of just searching for 'BBB Update test' or something, we want @@ -197,7 +197,7 @@ function testUpdateBaseThemeSecurityUpdate() { ); $this->refreshUpdateStatus($xml_mapping); $this->assertText(t('Security update required!')); - $this->assertRaw(l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), 'Link to the Update test base theme project appears.'); + $this->assertRaw((string) l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), 'Link to the Update test base theme project appears.'); } /** @@ -346,9 +346,9 @@ function testUpdateBrokenFetchURL() { $this->assertUniqueText(t('Failed to get available update data for one project.')); // The other two should be listed as projects. - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); + $this->assertRaw((string) l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); $this->assertNoRaw(l(t('BBB Update test'), 'http://example.com/project/bbb_update_test'), 'Link to bbb_update_test project does not appear.'); - $this->assertRaw(l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), 'Link to bbb_update_test project appears.'); + $this->assertRaw((string) l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), 'Link to bbb_update_test project appears.'); } /** @@ -390,7 +390,7 @@ function testHookUpdateStatusAlter() { $this->drupalGet('admin/reports/updates'); $this->assertRaw('

' . t('Modules') . '

'); $this->assertText(t('Security update required!')); - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); + $this->assertRaw((string) l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); // Visit the reports page again without the altering and make sure the // status is back to normal. @@ -398,7 +398,7 @@ function testHookUpdateStatusAlter() { $this->drupalGet('admin/reports/updates'); $this->assertRaw('

' . t('Modules') . '

'); $this->assertNoText(t('Security update required!')); - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); + $this->assertRaw((string) l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), 'Link to aaa_update_test project appears.'); // Turn the altering back on and visit the Update manager UI. $update_test_config->set('update_status', $update_status)->save(); diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php index 7928455..0b26ebc 100644 --- a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php @@ -56,9 +56,9 @@ function testNormalUpdateAvailable() { $this->assertNoText(t('Up to date')); $this->assertText(t('Update available')); $this->assertNoText(t('Security update required!')); - $this->assertRaw(l('7.1', 'http://example.com/drupal-7-1-release'), 'Link to release appears.'); - $this->assertRaw(l(t('Download'), 'http://example.com/drupal-7-1.tar.gz'), 'Link to download appears.'); - $this->assertRaw(l(t('Release notes'), 'http://example.com/drupal-7-1-release'), 'Link to release notes appears.'); + $this->assertRaw((string) l('7.1', 'http://example.com/drupal-7-1-release'), 'Link to release appears.'); + $this->assertRaw((string) l(t('Download'), 'http://example.com/drupal-7-1.tar.gz'), 'Link to download appears.'); + $this->assertRaw((string) l(t('Release notes'), 'http://example.com/drupal-7-1-release'), 'Link to release notes appears.'); } /** @@ -71,9 +71,9 @@ function testSecurityUpdateAvailable() { $this->assertNoText(t('Up to date')); $this->assertNoText(t('Update available')); $this->assertText(t('Security update required!')); - $this->assertRaw(l('7.2', 'http://example.com/drupal-7-2-release'), 'Link to release appears.'); - $this->assertRaw(l(t('Download'), 'http://example.com/drupal-7-2.tar.gz'), 'Link to download appears.'); - $this->assertRaw(l(t('Release notes'), 'http://example.com/drupal-7-2-release'), 'Link to release notes appears.'); + $this->assertRaw((string) l('7.2', 'http://example.com/drupal-7-2-release'), 'Link to release appears.'); + $this->assertRaw((string) l(t('Download'), 'http://example.com/drupal-7-2.tar.gz'), 'Link to download appears.'); + $this->assertRaw((string) l(t('Release notes'), 'http://example.com/drupal-7-2-release'), 'Link to release notes appears.'); } /** diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateTestBase.php b/core/modules/update/lib/Drupal/update/Tests/UpdateTestBase.php index 47a6649..5c09710 100644 --- a/core/modules/update/lib/Drupal/update/Tests/UpdateTestBase.php +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateTestBase.php @@ -55,7 +55,7 @@ protected function refreshUpdateStatus($xml_map, $url = 'update-test') { */ protected function standardTests() { $this->assertRaw('

' . t('Drupal core') . '

'); - $this->assertRaw(l(t('Drupal'), 'http://example.com/project/drupal'), 'Link to the Drupal project appears.'); + $this->assertRaw((string) l(t('Drupal'), 'http://example.com/project/drupal'), 'Link to the Drupal project appears.'); $this->assertNoText(t('No available releases found')); } } diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index ae967bd..3678423 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -9,6 +9,7 @@ use Drupal\Component\Utility\Xss; use Drupal\Core\Language\Language; use Drupal\Core\Template\Attribute; +use Drupal\Core\Template\SafeMarkup; use Drupal\views\Form\ViewsForm; use Drupal\views\ViewExecutable; @@ -525,6 +526,7 @@ function template_preprocess_views_view_table(&$variables) { // Render the header labels. if ($field == $column && empty($fields[$field]->options['exclude'])) { + $safe = TRUE; $label = String::checkPlain(!empty($fields[$field]) ? $fields[$field]->label() : ''); if (empty($options['info'][$field]['sortable']) || !$fields[$field]->clickSortable()) { $variables['header'][$field]['content'] = $label; @@ -542,7 +544,10 @@ function template_preprocess_views_view_table(&$variables) { '#theme' => 'tablesort_indicator', '#style' => $initial, ); - $label .= drupal_render($tablesort_indicator); + $markup = drupal_render($tablesort_indicator); + // $label is safe. + $safe = $markup instanceof SafeMarkup; + $label .= $markup; } $query['order'] = $field; @@ -552,7 +557,7 @@ function template_preprocess_views_view_table(&$variables) { 'attributes' => array('title' => $title), 'query' => $query, ); - $variables['header'][$field]['content'] = l($label, current_path(), $link_options); + $variables['header'][$field]['content'] = l($safe ? new SafeMarkup($label) : $label, current_path(), $link_options); } // Set up the header label class. diff --git a/core/themes/bartik/templates/block--system-branding-block.html.twig b/core/themes/bartik/templates/block--system-branding-block.html.twig index 5917f58..f6147a6 100644 --- a/core/themes/bartik/templates/block--system-branding-block.html.twig +++ b/core/themes/bartik/templates/block--system-branding-block.html.twig @@ -23,7 +23,7 @@
{% if site_name %} - {{ site_name|e }} + {{ site_name }} {% endif %} {% if site_slogan %} diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index 1595bf8..47376dc 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -6,6 +6,7 @@ */ use Drupal\Core\Extension\Extension; +use Drupal\Core\Template\SafeMarkup; /** * Implements hook_theme(). @@ -93,7 +94,7 @@ function twig_render_template($template_file, $variables) { $output['debug_info'] .= "\n\n"; $output['debug_suffix'] .= "\n\n\n"; } - return implode('', $output); + return new SafeMarkup(implode('', $output)); } /** diff --git a/core/vendor/twig/twig/lib/Twig/Extension/Core.php b/core/vendor/twig/twig/lib/Twig/Extension/Core.php index 4e80c67..968b292 100644 --- a/core/vendor/twig/twig/lib/Twig/Extension/Core.php +++ b/core/vendor/twig/twig/lib/Twig/Extension/Core.php @@ -922,7 +922,10 @@ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', if (!is_string($string)) { if (is_object($string) && method_exists($string, '__toString')) { - $string = (string) $string; + $string = $string->__toString(); + if ($autoescape && $string instanceof Twig_Markup) { + return $string; + } } else { return $string; }