Issue #2501931 by alexpott, alimac, cmanalansan, davidhernandez, scotself, cilefen, RavindraSingh, peezy, YesCT, pwolanin: Remove SafeMarkup::set in twig_render_template() and ThemeManager and FieldPluginBase:advancedRender
parent
b7a80b09bc
commit
45a48e8e01
|
@ -518,7 +518,7 @@ class Renderer implements RendererInterface {
|
|||
$prefix = isset($elements['#prefix']) ? $this->xssFilterAdminIfUnsafe($elements['#prefix']) : '';
|
||||
$suffix = isset($elements['#suffix']) ? $this->xssFilterAdminIfUnsafe($elements['#suffix']) : '';
|
||||
|
||||
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
|
||||
$elements['#markup'] = SafeString::create($prefix . $elements['#children'] . $suffix);
|
||||
|
||||
// We've rendered this element (and its subtree!), now update the context.
|
||||
$context->update($elements);
|
||||
|
@ -553,7 +553,7 @@ class Renderer implements RendererInterface {
|
|||
$context->bubble();
|
||||
|
||||
$elements['#printed'] = TRUE;
|
||||
return SafeString::create($elements['#markup']);
|
||||
return $elements['#markup'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
namespace Drupal\Core\Theme;
|
||||
|
||||
use Drupal\Component\Utility\SafeStringInterface;
|
||||
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 +318,10 @@ class ThemeManager implements ThemeManagerInterface {
|
|||
$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 @@ class ThemeManager implements ThemeManagerInterface {
|
|||
$output = $render_function($template_file, $variables);
|
||||
}
|
||||
|
||||
return (string) $output;
|
||||
return ($output instanceof SafeStringInterface) ? $output : (string) $output;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,8 +26,8 @@ interface ThemeManagerInterface {
|
|||
* @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);
|
||||
|
||||
|
|
|
@ -91,11 +91,11 @@ class CommentFieldNameTest extends CommentTestBase {
|
|||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['field_name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($this->comment->getFieldName(), $output);
|
||||
$this->assertEqual($this->comment->getFieldName(), $output);
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['field_name']->advancedRender($view->result[1]);
|
||||
});
|
||||
$this->assertIdentical($this->customComment->getFieldName(), $output);
|
||||
$this->assertEqual($this->customComment->getFieldName(), $output);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@ services:
|
|||
arguments: ['Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem']
|
||||
tags:
|
||||
- { name: normalizer, priority: 20 }
|
||||
serializer.normalizer.safe_string:
|
||||
class: Drupal\serialization\Normalizer\SafeStringNormalizer
|
||||
tags:
|
||||
- { name: normalizer }
|
||||
serializer.normalizer.typed_data:
|
||||
class: Drupal\serialization\Normalizer\TypedDataNormalizer
|
||||
tags:
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\serialization\Normalizer\SafeStringNormalizer.
|
||||
*/
|
||||
|
||||
namespace Drupal\serialization\Normalizer;
|
||||
|
||||
/**
|
||||
* Normalizes SafeStringInterface objects into a string.
|
||||
*/
|
||||
class SafeStringNormalizer extends NormalizerBase {
|
||||
|
||||
/**
|
||||
* The interface or class that this Normalizer supports.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $supportedInterfaceOrClass = array('Drupal\Component\Utility\SafeStringInterface');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function normalize($object, $format = NULL, array $context = array()) {
|
||||
return (string) $object;
|
||||
}
|
||||
|
||||
}
|
|
@ -815,7 +815,10 @@ trait AssertContentTrait {
|
|||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$output = $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) {
|
||||
// The string cast is necessary because theme functions return
|
||||
// SafeStringInterface objects. This means we can assert that $expected
|
||||
// matches the theme output without having to worry about 0 == ''.
|
||||
$output = (string) $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) {
|
||||
return \Drupal::theme()->render($callback, $variables);
|
||||
});
|
||||
$this->verbose(
|
||||
|
|
|
@ -13,6 +13,7 @@ use Drupal\test_theme\ThemeClass;
|
|||
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,19 @@ class ThemeTest extends WebTestBase {
|
|||
* 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.
|
||||
$foos = array('null' => NULL, 'false' => FALSE, 'integer' => 1, 'string' => 'foo');
|
||||
// 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', 'empty_string' => '');
|
||||
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)));
|
||||
$this->assertTrue($output instanceof SafeStringInterface || is_string($output), format_string('\Drupal::theme() returns an object that implements SafeStringInterface or a string for data type !type.', array('!type' => $type)));
|
||||
if ($output instanceof SafeStringInterface) {
|
||||
$this->assertIdentical((string) $example, $output->__toString());
|
||||
}
|
||||
elseif (is_string($output)) {
|
||||
$this->assertIdentical($output, '', 'A string will be return when the theme returns an empty string.');
|
||||
}
|
||||
}
|
||||
|
||||
// suggestionnotimplemented is not an implemented theme hook so \Drupal::theme() service
|
||||
|
|
|
@ -63,7 +63,7 @@ class HandlerFieldUserNameTest extends UserTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
|
||||
$this->assertEqual($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -167,8 +167,10 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
|
|||
* @param \Drupal\views\ResultRow $values
|
||||
* The values retrieved from a single row of a view's query result.
|
||||
*
|
||||
* @return string
|
||||
* The rendered output.
|
||||
* @return string|\Drupal\Component\Utility\SafeStringInterface
|
||||
* The rendered output. If the output is safe it will be wrapped in an
|
||||
* object that implements SafeStringInterface. If it is empty or unsafe it
|
||||
* will be a string.
|
||||
*
|
||||
*/
|
||||
public function render(ResultRow $values);
|
||||
|
@ -202,8 +204,10 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
|
|||
* @param \Drupal\views\ResultRow $values
|
||||
* The values retrieved from a single row of a view's query result.
|
||||
*
|
||||
* @return string
|
||||
* The advanced rendered output.
|
||||
* @return string|\Drupal\Component\Utility\SafeStringInterface
|
||||
* The advanced rendered output. If the output is safe it will be wrapped in
|
||||
* an object that implements SafeStringInterface. If it is empty or unsafe
|
||||
* it will be a string.
|
||||
*
|
||||
*/
|
||||
public function advancedRender(ResultRow $values);
|
||||
|
@ -236,29 +240,13 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
|
|||
* - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
|
||||
* - html: Make sure that the html is correct.
|
||||
*
|
||||
* @return string
|
||||
* The rendered string.
|
||||
* @return string|\Drupal\Component\Utility\SafeStringInterface
|
||||
* The rendered output. If the output is safe it will be wrapped in an
|
||||
* object that implements SafeStringInterface. If it is empty or unsafe it
|
||||
* will be a string.
|
||||
*/
|
||||
public function renderText($alter);
|
||||
|
||||
/**
|
||||
* Trims the field down to the specified length.
|
||||
*
|
||||
* @param array $alter
|
||||
* The alter array of options to use.
|
||||
* - max_length: Maximum length of the string, the rest gets truncated.
|
||||
* - word_boundary: Trim only on a word boundary.
|
||||
* - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
|
||||
* - html: Make sure that the html is correct.
|
||||
*
|
||||
* @param string $value
|
||||
* The string which should be trimmed.
|
||||
*
|
||||
* @return string
|
||||
* The rendered trimmed string.
|
||||
*/
|
||||
public function renderTrimText($alter, $value);
|
||||
|
||||
/**
|
||||
* Gets the 'render' tokens to use for advanced rendering.
|
||||
*
|
||||
|
@ -280,8 +268,10 @@ interface FieldHandlerInterface extends ViewsHandlerInterface {
|
|||
* @param \Drupal\views\ResultRow $values
|
||||
* Holds single row of a view's result set.
|
||||
*
|
||||
* @return string|false
|
||||
* Returns rendered output of the given theme implementation.
|
||||
* @return string|\Drupal\Component\Utility\SafeStringInterface
|
||||
* Returns rendered output of the given theme implementation. If the output
|
||||
* is safe it will be wrapped in an object that implements
|
||||
* SafeStringInterface. If it is empty or unsafe it will be a string.
|
||||
*/
|
||||
function theme(ResultRow $values);
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\views\Plugin\views\field;
|
|||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\SafeStringInterface;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
|
@ -1138,7 +1139,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
else {
|
||||
$value = $this->render($values);
|
||||
if (is_array($value)) {
|
||||
$value = (string) $this->getRenderer()->render($value);
|
||||
$value = $this->getRenderer()->render($value);
|
||||
}
|
||||
$this->last_render = $value;
|
||||
$this->original_value = $value;
|
||||
|
@ -1169,14 +1170,16 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$value = (string) $this->getRenderer()->render($value);
|
||||
$value = $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.
|
||||
$this->last_render = $value;
|
||||
}
|
||||
|
||||
if (empty($this->last_render)) {
|
||||
// String cast is necessary to test emptiness of SafeStringInterface
|
||||
// objects.
|
||||
if (empty((string) $this->last_render)) {
|
||||
if ($this->isValueEmpty($this->last_render, $this->options['empty_zero'], FALSE)) {
|
||||
$alter = $this->options['alter'];
|
||||
$alter['alter_text'] = 1;
|
||||
|
@ -1185,9 +1188,6 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
$this->last_render = $this->renderText($alter);
|
||||
}
|
||||
}
|
||||
// @todo Fix this in https://www.drupal.org/node/2280961.
|
||||
$this->last_render = SafeMarkup::set($this->last_render);
|
||||
|
||||
|
||||
return $this->last_render;
|
||||
}
|
||||
|
@ -1196,6 +1196,10 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function isValueEmpty($value, $empty_zero, $no_skip_empty = TRUE) {
|
||||
// Convert SafeStringInterface to a string for checking.
|
||||
if ($value instanceof SafeStringInterface) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
if (!isset($value)) {
|
||||
$empty = TRUE;
|
||||
}
|
||||
|
@ -1213,7 +1217,14 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function renderText($alter) {
|
||||
$value = $this->last_render;
|
||||
// We need to preserve the safeness of the value regardless of the
|
||||
// alterations made by this method. Any alterations or replacements made
|
||||
// within this method need to ensure that at the minimum the result is
|
||||
// XSS admin filtered. See self::renderAltered() as an example that does.
|
||||
$value_is_safe = SafeMarkup::isSafe($this->last_render);
|
||||
// Cast to a string so that empty checks and string functions work as
|
||||
// expected.
|
||||
$value = (string) $this->last_render;
|
||||
|
||||
if (!empty($alter['alter_text']) && $alter['text'] !== '') {
|
||||
$tokens = $this->getRenderTokens($alter);
|
||||
|
@ -1239,6 +1250,9 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
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 = ViewsRenderPipelineSafeString::create($value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
@ -1275,6 +1289,12 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
if (!empty($alter['nl2br'])) {
|
||||
$value = nl2br($value);
|
||||
}
|
||||
|
||||
// Preserve whether or not the string is safe. Since $suffix comes from
|
||||
// \Drupal::l(), it is safe to append.
|
||||
if ($value_is_safe) {
|
||||
$value = ViewsRenderPipelineSafeString::create($value . $suffix);
|
||||
}
|
||||
$this->last_render_text = $value;
|
||||
|
||||
if (!empty($alter['make_link']) && (!empty($alter['path']) || !empty($alter['url']))) {
|
||||
|
@ -1284,20 +1304,42 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
|
|||
$value = $this->renderAsLink($alter, $value, $tokens);
|
||||
}
|
||||
|
||||
return $value . $suffix;
|
||||
// Preserve whether or not the string is safe. Since $suffix comes from
|
||||
// \Drupal::l(), it is safe to append.
|
||||
if ($value_is_safe) {
|
||||
return ViewsRenderPipelineSafeString::create($value . $suffix);
|
||||
}
|
||||
else {
|
||||
// If the string is not already marked safe, it is still OK to return it
|
||||
// because it will be sanitized by Twig.
|
||||
return $value . $suffix;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this field as user-defined altered text.
|
||||
*/
|
||||
protected function renderAltered($alter, $tokens) {
|
||||
return ViewsRenderPipelineSafeString::create($this->viewsTokenReplace($alter['text'], $tokens));
|
||||
return $this->viewsTokenReplace($alter['text'], $tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Trims the field down to the specified length.
|
||||
*
|
||||
* @param array $alter
|
||||
* The alter array of options to use.
|
||||
* - max_length: Maximum length of the string, the rest gets truncated.
|
||||
* - word_boundary: Trim only on a word boundary.
|
||||
* - ellipsis: Show an ellipsis (…) at the end of the trimmed string.
|
||||
* - html: Make sure that the html is correct.
|
||||
*
|
||||
* @param string $value
|
||||
* The string which should be trimmed.
|
||||
*
|
||||
* @return string
|
||||
* The rendered trimmed string.
|
||||
*/
|
||||
public function renderTrimText($alter, $value) {
|
||||
protected function renderTrimText($alter, $value) {
|
||||
if (!empty($alter['strip_tags'])) {
|
||||
// NOTE: It's possible that some external fields might override the
|
||||
// element type.
|
||||
|
|
|
@ -64,9 +64,9 @@ class FieldFileSizeTest extends ViewUnitTestBase {
|
|||
// Test with the bytes option.
|
||||
$view->field['age']->options['file_size_display'] = 'bytes';
|
||||
$this->assertEqual($view->field['age']->advancedRender($view->result[0]), '');
|
||||
$this->assertEqual($view->field['age']->advancedRender($view->result[1]), 10);
|
||||
$this->assertEqual($view->field['age']->advancedRender($view->result[2]), 1000);
|
||||
$this->assertEqual($view->field['age']->advancedRender($view->result[3]), 10000);
|
||||
$this->assertEqual($view->field['age']->advancedRender($view->result[1]), '10');
|
||||
$this->assertEqual($view->field['age']->advancedRender($view->result[2]), '1000');
|
||||
$this->assertEqual($view->field['age']->advancedRender($view->result[3]), '10000');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -324,7 +324,8 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
/**
|
||||
* Tests the hide if empty functionality.
|
||||
*
|
||||
* This tests alters the result to get easier and less coupled results.
|
||||
* This tests alters the result to get easier and less coupled results. It is
|
||||
* important that assertIdentical() is used in this test since in PHP 0 == ''.
|
||||
*/
|
||||
function _testHideIfEmpty() {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
|
@ -349,7 +350,7 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'By default, a string should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_name, 'By default, a string should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
|
@ -363,14 +364,14 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, '0', 'By default, 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, '0', 'By default, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'By default, "0" should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "0", 'By default, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are not rewritten and non-zero empty values are hidden.
|
||||
$view->field['name']->options['hide_alter_empty'] = TRUE;
|
||||
|
@ -382,7 +383,7 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
|
@ -396,14 +397,14 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are not rewritten and all empty values are hidden.
|
||||
$view->field['name']->options['hide_alter_empty'] = TRUE;
|
||||
|
@ -437,28 +438,28 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to an empty string and non-zero empty results are hidden.
|
||||
$view->field['name']->options['hide_alter_empty'] = TRUE;
|
||||
|
@ -472,7 +473,7 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
|
@ -486,14 +487,14 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to zero as a string and non-zero empty
|
||||
// results are hidden.
|
||||
|
@ -508,28 +509,28 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as an integer.
|
||||
$view->result[0]->{$column_map_reversed['name']} = 0;
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to a valid string and non-zero empty
|
||||
// results are hidden.
|
||||
|
@ -544,7 +545,7 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
|
@ -558,14 +559,14 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
|
||||
|
||||
// Test zero as a string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
|
||||
$this->assertIdentical((string) $render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
|
||||
|
||||
// Test when results are rewritten to zero as a string and all empty
|
||||
// original values and results are hidden.
|
||||
|
@ -580,7 +581,7 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "", 'If the rewritten string is zero, it should be treated as empty.');
|
||||
$this->assertIdentical((string) $render, "", 'If the rewritten string is zero, it should be treated as empty.');
|
||||
|
||||
// Test an empty string.
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
|
@ -623,20 +624,20 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
|
||||
$this->assertIdentical((string) $render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
|
||||
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
|
||||
$this->assertIdentical((string) $render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
|
||||
|
||||
$view->result[0]->{$column_map_reversed['name']} = "0";
|
||||
$view->field['name']->options['empty_zero'] = TRUE;
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
|
||||
$this->assertIdentical((string) $render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
|
||||
|
||||
$view->result[0]->{$column_map_reversed['name']} = "";
|
||||
$view->field['name']->options['alter']['alter_text'] = TRUE;
|
||||
|
@ -645,13 +646,13 @@ class FieldUnitTest extends ViewUnitTestBase {
|
|||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
|
||||
$this->assertIdentical((string) $render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
|
||||
|
||||
$view->field['name']->options['hide_alter_empty'] = TRUE;
|
||||
$render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
|
||||
return $view->field['name']->advancedRender($view->result[0]);
|
||||
});
|
||||
$this->assertIdentical($render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
|
||||
$this->assertIdentical((string) $render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,6 +35,18 @@ class RendererTest extends RendererTestBase {
|
|||
'#children' => '',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Reset the static list of SafeStrings to prevent bleeding between tests.
|
||||
$reflected_class = new \ReflectionClass('\Drupal\Component\Utility\SafeMarkup');
|
||||
$reflected_property = $reflected_class->getProperty('safeStrings');
|
||||
$reflected_property->setAccessible(true);
|
||||
$reflected_property->setValue([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::render
|
||||
* @covers ::doRender
|
||||
|
@ -47,7 +59,15 @@ class RendererTest extends RendererTestBase {
|
|||
$setup_code();
|
||||
}
|
||||
|
||||
$this->assertSame($expected, (string) $this->renderer->renderRoot($build));
|
||||
if (isset($build['#markup'])) {
|
||||
$this->assertFalse(SafeMarkup::isSafe($build['#markup']), 'The #markup value is not marked safe before rendering.');
|
||||
}
|
||||
$render_output = $this->renderer->renderRoot($build);
|
||||
$this->assertSame($expected, (string) $render_output);
|
||||
if ($render_output !== '') {
|
||||
$this->assertTrue(SafeMarkup::isSafe($render_output), 'Output of render is marked safe.');
|
||||
$this->assertTrue(SafeMarkup::isSafe($build['#markup']), 'The #markup value is marked safe after rendering.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\SafeString;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
|
@ -44,7 +45,7 @@ function twig_init(Extension $theme) {
|
|||
* @param array $variables
|
||||
* A keyed array of variables that will appear in the output.
|
||||
*
|
||||
* @return string
|
||||
* @return string|\Drupal\Component\Utility\SafeStringInterface
|
||||
* The output generated by the template, plus any debug information.
|
||||
*/
|
||||
function twig_render_template($template_file, array $variables) {
|
||||
|
@ -74,7 +75,7 @@ function twig_render_template($template_file, array $variables) {
|
|||
}
|
||||
if ($twig_service->isDebug()) {
|
||||
$output['debug_prefix'] .= "\n\n<!-- THEME DEBUG -->";
|
||||
$output['debug_prefix'] .= "\n<!-- THEME HOOK: '" . SafeMarkup::checkPlain($variables['theme_hook_original']) . "' -->";
|
||||
$output['debug_prefix'] .= "\n<!-- THEME HOOK: '" . htmlspecialchars($variables['theme_hook_original'], ENT_QUOTES, 'UTF-8') . "' -->";
|
||||
// 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<!-- FILE NAME SUGGESTIONS:\n " . SafeMarkup::checkPlain(implode("\n ", $suggestions)) . "\n-->";
|
||||
$output['debug_info'] .= "\n<!-- FILE NAME SUGGESTIONS:\n " . htmlspecialchars(implode("\n ", $suggestions), ENT_QUOTES, 'UTF-8') . "\n-->";
|
||||
}
|
||||
$output['debug_info'] .= "\n<!-- BEGIN OUTPUT from '" . SafeMarkup::checkPlain($template_file) . "' -->\n";
|
||||
$output['debug_suffix'] .= "\n<!-- END OUTPUT from '" . SafeMarkup::checkPlain($template_file) . "' -->\n\n";
|
||||
$output['debug_info'] .= "\n<!-- BEGIN OUTPUT from '" . htmlspecialchars($template_file, ENT_QUOTES, 'UTF-8') . "' -->\n";
|
||||
$output['debug_suffix'] .= "\n<!-- END OUTPUT from '" . htmlspecialchars($template_file, ENT_QUOTES, 'UTF-8') . "' -->\n\n";
|
||||
}
|
||||
return SafeMarkup::set(implode('', $output));
|
||||
// This output has already been rendered and is therefore considered safe.
|
||||
return SafeString::create(implode('', $output));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue