Commit d3c33d58 authored by catch's avatar catch

Issue #2506581 by alexpott, Wim Leers, Fabianx, xjm, joelpittet, Cottser,...

Issue #2506581 by alexpott, Wim Leers, Fabianx, xjm, joelpittet, Cottser, dawehner: Remove SafeMarkup::set() from Renderer::doRender
parent 8c8bf756
......@@ -563,7 +563,7 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = FALSE)
$new = array(
'safe' => SafeMarkup::isSafe($message),
'message' => $message,
'message' => (string) $message,
);
if ($repeat || !in_array($new, $_SESSION['messages'][$type])) {
$_SESSION['messages'][$type][] = $new;
......
......@@ -164,15 +164,15 @@ protected function buildAttachmentsCommands(AjaxResponse $response, Request $req
$resource_commands = array();
if ($css_assets) {
$css_render_array = $this->cssCollectionRenderer->render($css_assets);
$resource_commands[] = new AddCssCommand($this->renderer->renderPlain($css_render_array));
$resource_commands[] = new AddCssCommand((string) $this->renderer->renderPlain($css_render_array));
}
if ($js_assets_header) {
$js_header_render_array = $this->jsCollectionRenderer->render($js_assets_header);
$resource_commands[] = new PrependCommand('head', $this->renderer->renderPlain($js_header_render_array));
$resource_commands[] = new PrependCommand('head', (string) $this->renderer->renderPlain($js_header_render_array));
}
if ($js_assets_footer) {
$js_footer_render_array = $this->jsCollectionRenderer->render($js_assets_footer);
$resource_commands[] = new AppendCommand('body', $this->renderer->renderPlain($js_footer_render_array));
$resource_commands[] = new AppendCommand('body', (string) $this->renderer->renderPlain($js_footer_render_array));
}
foreach (array_reverse($resource_commands) as $resource_command) {
$response->addCommand($resource_command, TRUE);
......
......@@ -37,10 +37,10 @@ protected function getRenderedContent() {
if (is_array($this->content)) {
$html = \Drupal::service('renderer')->renderRoot($this->content);
$this->attachedAssets = AttachedAssets::createFromRenderArray($this->content);
return $html;
return (string) $html;
}
else {
return $this->content;
return (string) $this->content;
}
}
......
......@@ -64,7 +64,7 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
}
}
$html = $this->drupalRenderRoot($main_content);
$html = (string) $this->drupalRenderRoot($main_content);
$response->setAttachments($main_content['#attached']);
// The selector for the insert command is NULL as the new content will
......@@ -72,7 +72,7 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
// behavior can be changed with #ajax['method'].
$response->addCommand(new InsertCommand(NULL, $html));
$status_messages = array('#type' => 'status_messages');
$output = $this->drupalRenderRoot($status_messages);
$output = (string) $this->drupalRenderRoot($status_messages);
if (!empty($output)) {
$response->addCommand(new PrependCommand(NULL, $output));
}
......
......@@ -77,11 +77,6 @@ public function get(array $elements) {
if (isset($cached_element['#cache_redirect'])) {
return $this->get($cached_element);
}
// Ensure that any safe properties are marked safe.
foreach ($cached_element['#safe_cache_properties'] as $cache_property) {
SafeMarkup::set($cached_element[$cache_property]);
}
unset($cached_element['#safe_cache_properties']);
// Return the cached element.
return $cached_element;
}
......@@ -333,7 +328,6 @@ public function getCacheableRenderArray(array $elements) {
'tags' => $elements['#cache']['tags'],
'max-age' => $elements['#cache']['max-age'],
],
'#safe_cache_properties' => []
];
// Preserve cacheable items if specified. If we are preserving any cacheable
......@@ -342,10 +336,10 @@ public function getCacheableRenderArray(array $elements) {
// the cache entry size.
if (!empty($elements['#cache_properties']) && is_array($elements['#cache_properties'])) {
$data['#cache_properties'] = $elements['#cache_properties'];
// Store whether any of the cache properties are safe strings.
// Ensure that any safe strings are a SafeString object.
foreach (Element::properties(array_flip($elements['#cache_properties'])) as $cache_property) {
if (isset($elements[$cache_property]) && !is_array($elements[$cache_property]) && SafeMarkup::isSafe($elements[$cache_property])) {
$data['#safe_cache_properties'][] = $cache_property;
if (isset($elements[$cache_property]) && is_scalar($elements[$cache_property]) && SafeMarkup::isSafe($elements[$cache_property])) {
$elements[$cache_property] = SafeString::create($elements[$cache_property]);
}
}
......@@ -357,12 +351,14 @@ public function getCacheableRenderArray(array $elements) {
$data['#markup'] = '';
// Cache only cacheable children's markup.
foreach ($cacheable_children as $key) {
$cacheable_items[$key] = ['#markup' => $cacheable_items[$key]['#markup']];
// We can assume that #markup is safe at this point.
$cacheable_items[$key] = ['#markup' => SafeString::create($cacheable_items[$key]['#markup'])];
}
}
$data += $cacheable_items;
}
$data['#markup'] = SafeString::create($data['#markup']);
return $data;
}
......
......@@ -9,6 +9,7 @@
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
......@@ -277,15 +278,9 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
if ($is_root_call) {
$this->replacePlaceholders($elements);
}
// Mark the element markup as safe. If we have cached children, we need
// to mark them as safe too. The parent markup contains the child
// markup, so if the parent markup is safe, then the markup of the
// individual children must be safe as well.
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
if (!empty($elements['#cache_properties'])) {
foreach (Element::children($cached_element) as $key) {
SafeMarkup::set($cached_element[$key]['#markup']);
}
// Mark the element markup as safe if is it a string.
if (is_string($elements['#markup'])) {
$elements['#markup'] = SafeString::create($elements['#markup']);
}
// The render cache item contains all the bubbleable rendering metadata
// for the subtree.
......@@ -410,7 +405,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$elements['#children'] = '';
}
if (isset($elements['#markup'])) {
if (!empty($elements['#markup'])) {
// @todo Decide how to support non-HTML in the render API in
// https://www.drupal.org/node/2501313.
$elements['#markup'] = $this->xssFilterAdminIfUnsafe($elements['#markup']);
......@@ -452,7 +447,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
foreach ($children as $key) {
$elements['#children'] .= $this->doRender($elements[$key]);
}
$elements['#children'] = SafeMarkup::set($elements['#children']);
$elements['#children'] = SafeString::create($elements['#children']);
}
// If #theme is not implemented and the element has raw #markup as a
......@@ -463,7 +458,7 @@ protected function doRender(&$elements, $is_root_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'] = SafeMarkup::set($elements['#markup'] . $elements['#children']);
$elements['#children'] = SafeString::create($elements['#markup'] . $elements['#children']);
}
// Let the theme functions in #theme_wrappers add markup around the rendered
......@@ -551,8 +546,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$context->bubble();
$elements['#printed'] = TRUE;
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
return $elements['#markup'];
return SafeString::create($elements['#markup']);
}
/**
......@@ -710,17 +704,18 @@ public function addCacheableDependency(array &$elements, $dependency) {
* Note: This method only filters if $string is not marked safe already. This
* ensures that HTML intended for display is not filtered.
*
* @param string $string
* @param string|\Drupal\Core\Render\SafeString $string
* A string.
*
* @return string
* The escaped string. If SafeMarkup::isSafe($string) returns TRUE, it won't
* be escaped again.
* @return \Drupal\Core\Render\SafeString
* The escaped string wrapped in a SafeString object. If
* SafeMarkup::isSafe($string) returns TRUE, it won't be escaped again.
*/
protected function xssFilterAdminIfUnsafe($string) {
// @todo https://www.drupal.org/node/2506581 replace with
// SafeMarkup::isSafe() and Xss::filterAdmin().
return SafeMarkup::checkAdminXss($string);
if (!SafeMarkup::isSafe($string)) {
$string = Xss::filterAdmin($string);
}
return SafeString::create($string);
}
}
......@@ -27,7 +27,7 @@ interface RendererInterface {
* @param array $elements
* The structured array describing the data to be rendered.
*
* @return string
* @return \Drupal\Component\Utility\SafeStringInterface
* The rendered HTML.
*
* @see ::render()
......@@ -58,7 +58,7 @@ public function renderRoot(&$elements);
* @param array $elements
* The structured array describing the data to be rendered.
*
* @return string
* @return \Drupal\Component\Utility\SafeStringInterface
* The rendered HTML.
*
* @see ::renderRoot()
......@@ -302,7 +302,7 @@ public function renderPlain(&$elements);
* (Internal use only.) Whether this is a recursive call or not. See
* ::renderRoot().
*
* @return string
* @return \Drupal\Component\Utility\SafeStringInterface
* The rendered HTML.
*
* @throws \LogicException
......
<?php
/**
* @file
* Contains \Drupal\Core\Render\SafeString.
*/
namespace Drupal\Core\Render;
use Drupal\Component\Utility\SafeStringInterface;
use Drupal\Component\Utility\Unicode;
/**
* Defines an object that passes safe strings through the render system.
*
* This object should only be constructed with a known safe string. If there is
* any risk that the string contains user-entered data that has not been
* filtered first, it must not be used.
*
* @internal
* This object is marked as internal because it should only be used during
* rendering. Currently, there is no use case for this object by contrib or
* custom code.
*
* @see \Drupal\Core\Template\TwigExtension::escapeFilter
* @see \Twig_Markup
* @see \Drupal\Component\Utility\SafeMarkup
*/
class SafeString implements SafeStringInterface, \Countable {
/**
* The safe string.
*
* @var string
*/
protected $string;
/**
* Creates a SafeString object if necessary.
*
* If $string is equal to a blank string then it is not necessary to create a
* SafeString object. If $string is an object that implements
* SafeStringInterface it is returned unchanged.
*
* @param mixed $string
* The string to mark as safe. This value will be cast to a string.
*
* @return string|\Drupal\Component\Utility\SafeStringInterface
* A safe string.
*/
public static function create($string) {
if ($string instanceof SafeStringInterface) {
return $string;
}
$string = (string) $string;
if ($string === '') {
return '';
}
$safe_string = new static();
$safe_string->string = $string;
return $safe_string;
}
/**
* Returns the string version of the SafeString object.
*
* @return string
* The safe string content.
*/
public function __toString() {
return $this->string;
}
/**
* Returns the string length.
*
* @return int
* The length of the string.
*/
public function count() {
return Unicode::strlen($this->string);
}
}
......@@ -7,6 +7,8 @@
namespace Drupal\Core\StringTranslation;
use Drupal\Component\Utility\SafeStringInterface;
/**
* Provides a class to wrap a translatable string.
*
......@@ -16,7 +18,7 @@
*
* @see \Drupal\Core\Annotation\Translation
*/
class TranslationWrapper {
class TranslationWrapper implements SafeStringInterface {
use StringTranslationTrait;
/**
......
......@@ -9,6 +9,7 @@
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\Render\SafeString;
/**
* A class that defines a Twig environment for Drupal.
......@@ -194,17 +195,15 @@ public function getTemplateClass($name, $index = NULL) {
* @param array $context
* An array of parameters to pass to the template.
*
* @return string
* The rendered inline template.
* @return \Drupal\Component\Utility\SafeStringInterface|string
* The rendered inline template as a SafeString object.
*
* @see \Drupal\Core\Template\Loader\StringLoader::exists()
*/
public function renderInline($template_string, array $context = array()) {
// Prefix all inline templates with a special comment.
$template_string = '{# inline_template_start #}' . $template_string;
// @todo replace with object implementating SafeStringInterface in
// https://www.drupal.org/node/2506581.
return SafeMarkup::set($this->loadTemplate($template_string, NULL)->render($context));
return SafeString::create($this->loadTemplate($template_string, NULL)->render($context));
}
}
......@@ -9,6 +9,7 @@
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\SafeStringInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\GeneratedLink;
use Drupal\Core\Link;
......@@ -106,6 +107,13 @@ public function generate($text, Url $url, $collect_bubbleable_metadata = FALSE)
$variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId();
}
// Ensure that query values are strings.
array_walk($variables['options']['query'], function(&$value) {
if ($value instanceof SafeStringInterface) {
$value = (string) $value;
}
});
// Set the "active" class if the 'set_active_class' option is not empty.
if (!empty($variables['options']['set_active_class']) && !$url->isExternal()) {
// Add a "data-drupal-link-query" attribute to let the
......
......@@ -190,14 +190,14 @@ protected function verifyRenderCacheHandling() {
public function testBlockViewBuilderAlter() {
// Establish baseline.
$build = $this->getBlockRenderArray();
$this->assertIdentical($this->renderer->renderRoot($build), 'Llamas &gt; unicorns!');
$this->assertIdentical((string) $this->renderer->renderRoot($build), 'Llamas &gt; unicorns!');
// Enable the block view alter hook that adds a suffix, for basic testing.
\Drupal::state()->set('block_test_view_alter_suffix', TRUE);
Cache::invalidateTags($this->block->getCacheTagsToInvalidate());
$build = $this->getBlockRenderArray();
$this->assertTrue(isset($build['#suffix']) && $build['#suffix'] === '<br>Goodbye!', 'A block with content is altered.');
$this->assertIdentical($this->renderer->renderRoot($build), 'Llamas &gt; unicorns!<br>Goodbye!');
$this->assertIdentical((string) $this->renderer->renderRoot($build), 'Llamas &gt; unicorns!<br>Goodbye!');
\Drupal::state()->set('block_test_view_alter_suffix', FALSE);
// Force a request via GET so we can test the render cache.
......@@ -218,7 +218,7 @@ public function testBlockViewBuilderAlter() {
$expected_keys = array_merge($default_keys, array($alter_add_key));
$build = $this->getBlockRenderArray();
$this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
$this->assertIdentical($this->renderer->renderRoot($build), '');
$this->assertIdentical((string) $this->renderer->renderRoot($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array_merge($default_tags, ['rendered']);
......@@ -233,7 +233,7 @@ public function testBlockViewBuilderAlter() {
$build = $this->getBlockRenderArray();
sort($build['#cache']['tags']);
$this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.');
$this->assertIdentical($this->renderer->renderRoot($build), '');
$this->assertIdentical((string) $this->renderer->renderRoot($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array_merge($default_tags, [$alter_add_tag, 'rendered']);
......@@ -246,7 +246,7 @@ public function testBlockViewBuilderAlter() {
\Drupal::state()->set('block_test_view_alter_append_pre_render_prefix', TRUE);
$build = $this->getBlockRenderArray();
$this->assertFalse(isset($build['#prefix']), 'The appended #pre_render callback has not yet run before rendering.');
$this->assertIdentical($this->renderer->renderRoot($build), 'Hiya!<br>');
$this->assertIdentical((string) $this->renderer->renderRoot($build), 'Hiya!<br>');
$this->assertTrue(isset($build['#prefix']) && $build['#prefix'] === 'Hiya!<br>', 'A cached block without content is altered.');
// Restore the previous request method.
......
......@@ -143,7 +143,7 @@ function contact_mail($key, &$message, $params) {
$message['subject'] .= t('[!form] !subject', $variables, $options);
$message['body'][] = t("!sender-name (!sender-url) sent a message using the contact form at !form-url.", $variables, $options);
$build = entity_view($contact_message, 'mail', $language->getId());
$message['body'][] = \Drupal::service('renderer')->renderPlain($build);
$message['body'][] = (string) \Drupal::service('renderer')->renderPlain($build);
break;
case 'page_autoreply':
......@@ -162,7 +162,7 @@ function contact_mail($key, &$message, $params) {
$message['body'][] = t("!sender-name (!sender-url) has sent you a message via your contact form at !site-name.", $variables, $options);
$message['body'][] = t("If you don't want to receive such emails, you can change your settings at !recipient-edit-url.", $variables, $options);
$build = entity_view($contact_message, 'mail', $language->getId());
$message['body'][] = \Drupal::service('renderer')->renderPlain($build);
$message['body'][] = (string) \Drupal::service('renderer')->renderPlain($build);
break;
}
}
......
......@@ -44,7 +44,7 @@ public function render(Request $request) {
'#type' => 'contextual_links',
'#contextual_links' => _contextual_id_to_links($id),
);
$rendered[$id] = $this->container->get('renderer')->renderRoot($element);
$rendered[$id] = (string) $this->container->get('renderer')->renderRoot($element);
}
return new JsonResponse($rendered);
......
......@@ -48,7 +48,7 @@ public function getUntransformedText(EntityInterface $entity, $field_name, $lang
// Direct text editing is only supported for single-valued fields.
$field = $entity->getTranslation($langcode)->$field_name;
$editable_text = check_markup($field->value, $field->format, $langcode, array(FilterInterface::TYPE_TRANSFORM_REVERSIBLE, FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE));
$response->addCommand(new GetUntransformedTextCommand($editable_text));
$response->addCommand(new GetUntransformedTextCommand((string) $editable_text));
return $response;
}
......
......@@ -288,7 +288,7 @@ function filter_fallback_format() {
* FilterInterface::TYPE_HTML_RESTRICTOR is the only type that cannot be
* skipped.
*
* @return string
* @return \Drupal\Component\Utility\SafeStringInterface
* The filtered text.
*
* @see filter_process_text()
......
......@@ -60,7 +60,7 @@ function testCheckMarkupFilterOrder() {
$text = "<p>Llamas are <not> awesome!</p>";
$expected_filtered_text = "&lt;p&gt;Llamas are awesome!&lt;/p&gt;";
$this->assertIdentical(check_markup($text, 'crazy'), $expected_filtered_text, 'Filters applied in correct order.');
$this->assertEqual(check_markup($text, 'crazy'), $expected_filtered_text, 'Filters applied in correct order.');
}
/**
......@@ -73,14 +73,14 @@ function testCheckMarkupFilterSubset() {
$actual_filtered_text = check_markup($text, 'filtered_html', '', array());
$this->verbose("Actual:<pre>$actual_filtered_text</pre>Expected:<pre>$expected_filtered_text</pre>");
$this->assertIdentical(
$this->assertEqual(
$actual_filtered_text,
$expected_filtered_text,
'Expected filter result.'
);
$actual_filtered_text_without_html_generators = check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_MARKUP_LANGUAGE));
$this->verbose("Actual:<pre>$actual_filtered_text_without_html_generators</pre>Expected:<pre>$expected_filter_text_without_html_generators</pre>");
$this->assertIdentical(
$this->assertEqual(
$actual_filtered_text_without_html_generators,
$expected_filter_text_without_html_generators,
'Expected filter result when skipping FilterInterface::TYPE_MARKUP_LANGUAGE filters.'
......@@ -91,7 +91,7 @@ function testCheckMarkupFilterSubset() {
// most extensive test possible.
$actual_filtered_text_without_html_generators = check_markup($text, 'filtered_html', '', array(FilterInterface::TYPE_HTML_RESTRICTOR, FilterInterface::TYPE_MARKUP_LANGUAGE));
$this->verbose("Actual:<pre>$actual_filtered_text_without_html_generators</pre>Expected:<pre>$expected_filter_text_without_html_generators</pre>");
$this->assertIdentical(
$this->assertEqual(
$actual_filtered_text_without_html_generators,
$expected_filter_text_without_html_generators,
'Expected filter result when skipping FilterInterface::TYPE_MARKUP_LANGUAGE filters, even when trying to disable filters of the FilterInterface::TYPE_HTML_RESTRICTOR type.'
......
......@@ -216,7 +216,7 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
$response->addCommand(new FieldFormSavedCommand($output, $other_view_modes));
}
else {
$output = $this->renderer->renderRoot($form);
$output = (string) $this->renderer->renderRoot($form);
// When working with a hidden form, we don't want its CSS/JS to be loaded.
if ($request->request->get('nocssjs') !== 'true') {
$response->setAttachments($form['#attached']);
......@@ -228,7 +228,7 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
$status_messages = array(
'#type' => 'status_messages'
);
$response->addCommand(new FieldFormValidationErrorsCommand($this->renderer->renderRoot($status_messages)));
$response->addCommand(new FieldFormValidationErrorsCommand((string) $this->renderer->renderRoot($status_messages)));
}
}
......@@ -275,7 +275,7 @@ protected function renderField(EntityInterface $entity, $field_name, $langcode,
$output = $this->moduleHandler()->invoke($module, 'quickedit_render_field', $args);
}
return $this->renderer->renderRoot($output);
return (string) $this->renderer->renderRoot($output);
}
/**
......
......@@ -108,7 +108,7 @@ public function testSerializerResponses() {
// Mock the request content type by setting it on the display handler.
$view->display_handler->setContentType('json');
$output = $view->preview();
$this->assertIdentical($actual_json, drupal_render_root($output), 'The expected JSON preview output was found.');
$this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
// Test a 403 callback.
$this->drupalGet('test/serialize/denied');
......
......@@ -398,7 +398,7 @@ protected function assertRaw($raw, $message = '', $group = 'Other') {
if (!$message) {
$message = SafeMarkup::format('Raw "@raw" found', array('@raw' => $raw));
}
return $this->assert(strpos($this->getRawContent(), $raw) !== FALSE, $message, $group);
return $this->assert(strpos($this->getRawContent(), (string) $raw) !== FALSE, $message, $group);
}
/**
......@@ -425,7 +425,7 @@ protected function assertNoRaw($raw, $message = '', $group = 'Other') {
if (!$message) {
$message = SafeMarkup::format('Raw "@raw" not found', array('@raw' => $raw));
}
return $this->assert(strpos($this->getRawContent(), $raw) === FALSE, $message, $group);
return $this->assert(strpos($this->getRawContent(), (string) $raw) === FALSE, $message, $group);
}
/**
......
......@@ -43,7 +43,7 @@ protected function setUp() {
* Assertion message.
*/
protected function assertElements(array $elements, $expected_html, $message) {
$actual_html = \Drupal::service('renderer')->renderRoot($elements);
$actual_html = (string) \Drupal::service('renderer')->renderRoot($elements);
$out = '<table><tr>';
$out .= '<td valign="top"><pre>' . SafeMarkup::checkPlain($expected_html) . '</pre></td>';
......
......@@ -612,10 +612,10 @@ protected function doTestLanguageFallback($entity_type) {
// Get an view builder.
$controller = $this->entityManager->getViewBuilder($entity_type);
$entity2_build = $controller->view($entity2);
$entity2_output = $renderer->renderRoot($entity2_build);
$entity2_output = (string) $renderer->renderRoot($entity2_build);
$translation = $this->entityManager->getTranslationFromContext($entity2, $default_langcode);
$translation_build = $controller->view($translation);
$translation_output = $renderer->renderRoot($translation_build);
$translation_output = (string) $renderer->renderRoot($translation_build);
$this->assertIdentical($entity2_output, $translation_output, 'When the entity has no translation no fallback is applied.');
// Checks that entity translations are rendered properly.
......
......@@ -390,7 +390,7 @@ function ($children, $elements) {
],
);
return $this->getRenderer()->render($build);
return (string) $this->getRenderer()->render($build);
}
else {
return $text;
......
......@@ -67,7 +67,7 @@ public static function buildResponse($view_id, $display_id, array $args = []) {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$output = $renderer->renderRoot($build);
$output = (string) $renderer->renderRoot($build);
if (empty($output)) {
throw new NotFoundHttpException();
......
......@@ -16,6 +16,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Render\SafeString;
use Drupal\Core\Url as CoreUrl;
use Drupal\views\Plugin\views\HandlerBase;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
......@@ -1137,7 +1138,7 @@ public function advancedRender(ResultRow $values) {
else {
$value = $this->render($values);
if (is_array($value)) {
$value = $this->getRenderer()->render($value);
$value = (string) $this->getRenderer()->render($value);
}
$this->last_render = $value;
$this->original_value = $value;
......@@ -1150,7 +1151,7 @@ public function advancedRender(ResultRow $values) {
foreach ($raw_items as $count => $item) {
$value = $this->render_item($count, $item);
if (is_array($value)) {
$value = $this->getRenderer()->render($value);
$value = (string) $this->getRenderer()->render($value);
}
$this->last_render = $value;
$this->original_value = $this->last_render;
......@@ -1168,7 +1169,7 @@ public function advancedRender(ResultRow $values) {
}
if (is_array($value)) {
$value = $this->getRenderer()->render($value);
$value = (string) $this->getRenderer()->render($value);
}
// This happens here so that renderAsLink can get the unaltered value of
// this field as a token rather than the altered value.
......@@ -1290,7 +1291,7 @@ public function renderText($alter) {
* Render this field as user-defined altered text.
*/
protected function renderAltered($alter, $tokens) {
return $this->viewsTokenReplace($alter['text'], $tokens);
return SafeString::create($this->viewsTokenReplace($alter['text'], $tokens));
}
/**
......
......@@ -217,7 +217,7 @@ public function getField($index, $field_id) {
if (empty($this->view->style_plugin) || !is_object($this->view->style_plugin) || empty($field_id)) {
return '';
}
return $this->view->style_plugin->getField($index, $field_id);
return (string) $this->view->style_plugin->getField($index, $field_id);
}
}
......@@ -8,10 +8,10 @@
namespace Drupal\views\Plugin\views\style;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\SafeString;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\views\Plugin\views\wizard\WizardInterface;
......@@ -706,8 +706,9 @@ protected function renderFields(array $result) {
$placeholders = array_keys($post_render_tokens);
$values = array_values($post_render_tokens);
foreach ($this->rendered_fields[$index] as &$rendered_field) {
$rendered_field = str_replace($placeholders, $values, $rendered_field);
SafeMarkup::set($rendered_field);
// Placeholders and rendered fields have been processed by the
// render system and are therefore safe.
$rendered_field = SafeString::create(str_replace($placeholders, $values, $rendered_field));
}
}
}
......@@ -744,7 +745,7 @@ public function elementPreRenderRow(array $data) {
* @param string $field
* The ID of the field.
*
* @return string|null
* @return \Drupal\Core\Render\SafeString|null
* The output of the field, or NULL if it was empty.
*/
public function getField($index, $field) {
......
......@@ -51,11 +51,11 @@ function testSimple() {
$view->preview();
$counter = $view->style_plugin->getField(0, 'counter');
$this->assertEqual($counter, 1, format_string('Make sure the expected number (@expected) patches with the rendered number (@counter)', array('@expected' => 1, '@counter' => $counter)));