Commit 651063e7 authored by alexpott's avatar alexpott

Issue #2464045 by fgm: Move twig_render_var/twig_drupal_escape_filter to...

Issue #2464045 by fgm: Move twig_render_var/twig_drupal_escape_filter to TwigExtension, inject the renderer in Twig extension and inline render() / show() function instead of calling it
parent 86a39ef4
......@@ -25,7 +25,7 @@
* @link theme_render theme and render systems @endlink so that the output can
* can be themed, escaped, and altered properly.
*
* @see twig_drupal_escape_filter()
* @see TwigExtension::escapeFilter()
* @see twig_render_template()
* @see sanitization
* @see theme_render
......
......@@ -44,7 +44,7 @@ public function __construct($root, \Twig_LoaderInterface $loader = NULL, $option
$this->cache_object = \Drupal::cache();
// Ensure that twig.engine is loaded, given that it is needed to render a
// template because functions like twig_drupal_escape_filter are called.
// template because functions like TwigExtension::escapeFilter() are called.
require_once $root . '/core/themes/engines/twig/twig.engine';
$this->templateClasses = array();
......
......@@ -12,6 +12,7 @@
namespace Drupal\Core\Template;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Url;
......@@ -89,7 +90,7 @@ public function setLinkGenerator(LinkGeneratorInterface $link_generator) {
public function getFunctions() {
return array(
// This function will receive a renderable array, if an array is detected.
new \Twig_SimpleFunction('render_var', 'twig_render_var'),
new \Twig_SimpleFunction('render_var', array($this, 'renderVar')),
// The url and path function are defined in close parallel to those found
// in \Symfony\Bridge\Twig\Extension\RoutingExtension
new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
......@@ -118,7 +119,7 @@ public function getFilters() {
new \Twig_SimpleFilter('placeholder', 'twig_raw_filter', array('is_safe' => array('html'))),
// Replace twig's escape filter with our own.
new \Twig_SimpleFilter('drupal_escape', 'twig_drupal_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
new \Twig_SimpleFilter('drupal_escape', [$this, 'escapeFilter'], array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
// Implements safe joining.
// @todo Make that the default for |join? Upstream issue:
......@@ -132,7 +133,7 @@ public function getFilters() {
new \Twig_SimpleFilter('clean_class', '\Drupal\Component\Utility\Html::getClass'),
new \Twig_SimpleFilter('clean_id', '\Drupal\Component\Utility\Html::getId'),
// This filter will render a renderable array to use the string results.
new \Twig_SimpleFilter('render', 'twig_render_var'),
new \Twig_SimpleFilter('render', array($this, 'renderVar')),
);
}
......@@ -141,7 +142,7 @@ public function getFilters() {
*/
public function getNodeVisitors() {
// The node visitor is needed to wrap all variables with
// render_var -> twig_render_var() function.
// render_var -> TwigExtension->renderVar() function.
return array(
new TwigNodeVisitor(),
);
......@@ -298,4 +299,148 @@ public function attachLibrary($library) {
$this->renderer->render($template_attached);
}
/**
* Overrides twig_escape_filter().
*
* Replacement function for Twig's escape filter.
*
* @param \Twig_Environment $env
* A Twig_Environment instance.
* @param mixed $arg
* The value to be escaped.
* @param string $strategy
* The escaping strategy. Defaults to 'html'.
* @param string $charset
* The charset.
* @param bool $autoescape
* Whether the function is called by the auto-escaping feature (TRUE) or by
* the developer (FALSE).
*
* @return string|null
* The escaped, rendered output, or NULL if there is no valid output.
*/
public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $charset = NULL, $autoescape = FALSE) {
// Check for a numeric zero int or float.
if ($arg === 0 || $arg === 0.0) {
return 0;
}
// Return early for NULL and empty arrays.
if ($arg == NULL) {
return NULL;
}
// Keep Twig_Markup objects intact to support autoescaping.
if ($autoescape && $arg instanceOf \Twig_Markup) {
return $arg;
}
$return = NULL;
if (is_scalar($arg)) {
$return = (string) $arg;
}
elseif (is_object($arg)) {
if (method_exists($arg, '__toString')) {
$return = (string) $arg;
}
// You can't throw exceptions in the magic PHP __toString methods, see
// http://php.net/manual/en/language.oop5.magic.php#object.tostring so
// we also support a toString method.
elseif (method_exists($arg, 'toString')) {
$return = $arg->toString();
}
else {
throw new \Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg))));
}
}
// We have a string or an object converted to a string: Autoescape it!
if (isset($return)) {
if ($autoescape && SafeMarkup::isSafe($return, $strategy)) {
return $return;
}
// Drupal only supports the HTML escaping strategy, so provide a
// fallback for other strategies.
if ($strategy == 'html') {
return SafeMarkup::checkPlain($return);
}
return twig_escape_filter($env, $return, $strategy, $charset, $autoescape);
}
// This is a normal render array, which is safe by definition, with
// special simple cases already handled.
// Early return if this element was pre-rendered (no need to re-render).
if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
return $arg['#markup'];
}
$arg['#printed'] = FALSE;
return $this->renderer->render($arg);
}
/**
* Wrapper around render() for twig printed output.
*
* If an object is passed that has no __toString method an exception is thrown;
* other objects are casted to string. However in the case that the object is an
* instance of a Twig_Markup object it is returned directly to support auto
* escaping.
*
* If an array is passed it is rendered via render() and scalar values are
* returned directly.
*
* @param mixed $arg
* String, Object or Render Array.
*
* @return mixed
* The rendered output or an Twig_Markup object.
*
* @see render
* @see TwigNodeVisitor
*/
public function renderVar($arg) {
// Check for a numeric zero int or float.
if ($arg === 0 || $arg === 0.0) {
return 0;
}
// Return early for NULL and empty arrays.
if ($arg == NULL) {
return NULL;
}
// Optimize for strings as it is likely they come from the escape filter.
if (is_string($arg)) {
return $arg;
}
if (is_scalar($arg)) {
return $arg;
}
if (is_object($arg)) {
if (method_exists($arg, '__toString')) {
return (string) $arg;
}
// You can't throw exceptions in the magic PHP __toString methods, see
// http://php.net/manual/en/language.oop5.magic.php#object.tostring so
// we also support a toString method.
elseif (method_exists($arg, 'toString')) {
return $arg->toString();
}
else {
throw new \Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg))));
}
}
// This is a render array, with special simple cases already handled.
// Early return if this element was pre-rendered (no need to re-render).
if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
return $arg['#markup'];
}
$arg['#printed'] = FALSE;
return $this->renderer->render($arg);
}
}
......@@ -136,7 +136,7 @@ protected function compileString(\Twig_NodeInterface $body) {
$args = $n;
// Support twig_render_var function in chain.
// Support TwigExtension->renderVar() function in chain.
if ($args instanceof \Twig_Node_Expression_Function) {
$args = $n->getNode('arguments')->getNode(0);
}
......
......@@ -11,7 +11,7 @@
* Provides a Twig_NodeVisitor to change the generated parse-tree.
*
* This is used to ensure that everything printed is wrapped via the
* twig_render_var() function in order to just write {{ content }}
* TwigExtension->renderVar() function in order to just write {{ content }}
* in templates instead of having to write {{ render_var(content) }}.
*
* @see twig_render
......@@ -29,7 +29,7 @@ function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
* {@inheritdoc}
*/
function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
// We use this to inject a call to render_var -> twig_render_var()
// We use this to inject a call to render_var -> TwigExtension->renderVar()
// before anything is printed.
if ($node instanceof \Twig_Node_Print) {
if (!empty($this->skipRenderVarFunction)) {
......
......@@ -63,4 +63,30 @@ function testTwigExtensionFunction() {
$this->assertNoText('The Quick Brown Fox Jumps Over The Lazy Dog 123.', 'Success: No text left behind.');
}
/**
* Tests output of integer and double 0 values of TwigExtension::escapeFilter().
*
* @see https://www.drupal.org/node/2417733
*/
public function testsRenderEscapedZeroValue() {
/** @var \Drupal\Core\Template\TwigExtension $extension */
$extension = \Drupal::service('twig.extension');
/** @var \Drupal\Core\Template\TwigEnvironment $twig */
$twig = \Drupal::service('twig');
$this->assertIdentical($extension->escapeFilter($twig, 0), 0, 'TwigExtension::escapeFilter() returns zero correctly when provided as an integer.');
$this->assertIdentical($extension->escapeFilter($twig, 0.0), 0, 'TwigExtension::escapeFilter() returns zero correctly when provided as a double.');
}
/**
* Tests output of integer and double 0 values of TwigExtension->renderVar().
*
* @see https://www.drupal.org/node/2417733
*/
public function testsRenderZeroValue() {
/** @var \Drupal\Core\Template\TwigExtension $extension */
$extension = \Drupal::service('twig.extension');
$this->assertIdentical($extension->renderVar(0), 0, 'TwigExtension::renderVar() renders zero correctly when provided as an integer.');
$this->assertIdentical($extension->renderVar(0.0), 0, 'TwigExtension::renderVar() renders zero correctly when provided as a double.');
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Theme\TwigEngineTest.
*/
namespace Drupal\Tests\Core\Theme;
use Drupal\Tests\UnitTestCase;
/**
* Test coverage for the file core/themes/engines/twig/twig.engine.
*
* @group Theme
*/
class TwigEngineTest extends UnitTestCase {
/**
* The mocked Twig environment.
*
* @var \Twig_Environment|\PHPUnit_Framework_MockObject_MockObject
*/
protected $twigEnvironment;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Ensure that twig.engine is loaded, it is needed to access
// twig_drupal_escape_filter().
require_once $this->root . '/core/themes/engines/twig/twig.engine';
$this->twigEnvironment = $this->getMock('\Twig_Environment');
}
/**
* Tests output of integer and double 0 values of twig_render_var().
*
* @see https://www.drupal.org/node/2417733
*/
public function testsRenderZeroValue() {
$this->assertSame(twig_render_var(0), 0, 'twig_render_var() renders zero correctly when provided as an integer.');
$this->assertSame(twig_render_var(0.0), 0, 'twig_render_var() renders zero correctly when provided as a double.');
}
/**
* Tests output of integer and double 0 values of twig_drupal_escape_filter().
*
* @see https://www.drupal.org/node/2417733
*/
public function testsRenderEscapedZeroValue() {
$this->assertSame(twig_drupal_escape_filter($this->twigEnvironment, 0), 0, 'twig_escape_filter() returns zero correctly when provided as an integer.');
$this->assertSame(twig_drupal_escape_filter($this->twigEnvironment, 0.0), 0, 'twig_escape_filter() returns zero correctly when provided as a double.');
}
}
......@@ -106,65 +106,6 @@ function twig_render_template($template_file, array $variables) {
return SafeMarkup::set(implode('', $output));
}
/**
* Wrapper around render() for twig printed output.
*
* If an object is passed that has no __toString method an exception is thrown;
* other objects are casted to string. However in the case that the object is an
* instance of a Twig_Markup object it is returned directly to support auto
* escaping.
*
* If an array is passed it is rendered via render() and scalar values are
* returned directly.
*
* @param mixed $arg
* String, Object or Render Array.
*
* @return mixed
* The rendered output or an Twig_Markup object.
*
* @see render
* @see TwigNodeVisitor
*/
function twig_render_var($arg) {
// Check for a numeric zero int or float.
if ($arg === 0 || $arg === 0.0) {
return 0;
}
// Return early for NULL and also true for empty arrays.
if ($arg == NULL) {
return NULL;
}
// Optimize for strings as it is likely they come from the escape filter.
if (is_string($arg)) {
return $arg;
}
if (is_scalar($arg)) {
return $arg;
}
if (is_object($arg)) {
if (method_exists($arg, '__toString')) {
return (string) $arg;
}
// You can't throw exceptions in the magic PHP __toString methods, see
// http://php.net/manual/en/language.oop5.magic.php#object.tostring so
// we also support a toString method.
elseif (method_exists($arg, 'toString')) {
return $arg->toString();
}
else {
throw new Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg))));
}
}
// This is a normal render array.
return render($arg);
}
/**
* Removes child elements from a copy of the original array.
*
......@@ -198,79 +139,6 @@ function twig_without($element) {
return $filtered_element;
}
/**
* Overrides twig_escape_filter().
*
* Replacement function for Twig's escape filter.
*
* @param \Twig_Environment $env
* A Twig_Environment instance.
* @param string $string
* The value to be escaped.
* @param string $strategy
* The escaping strategy. Defaults to 'html'.
* @param string $charset
* The charset.
* @param bool $autoescape
* Whether the function is called by the auto-escaping feature (TRUE) or by
* the developer (FALSE).
*
* @return string|null
* The escaped, rendered output, or NULL if there is no valid output.
*/
function twig_drupal_escape_filter(\Twig_Environment $env, $string, $strategy = 'html', $charset = NULL, $autoescape = FALSE) {
// Check for a numeric zero int or float.
if ($string === 0 || $string === 0.0) {
return 0;
}
// Return early for NULL or an empty array.
if ($string == NULL) {
return NULL;
}
// Keep Twig_Markup objects intact to support autoescaping.
if ($autoescape && $string instanceOf \Twig_Markup) {
return $string;
}
$return = NULL;
if (is_scalar($string)) {
$return = (string) $string;
}
elseif (is_object($string)) {
if (method_exists($string, '__toString')) {
$return = (string) $string;
}
// You can't throw exceptions in the magic PHP __toString methods, see
// http://php.net/manual/en/language.oop5.magic.php#object.tostring so
// we also support a toString method.
elseif (method_exists($string, 'toString')) {
$return = $string->toString();
}
else {
throw new \Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($string))));
}
}
// We have a string or an object converted to a string: Autoescape it!
if (isset($return)) {
if ($autoescape && SafeMarkup::isSafe($return, $strategy)) {
return $return;
}
// Drupal only supports the HTML escaping strategy, so provide a
// fallback for other strategies.
if ($strategy == 'html') {
return SafeMarkup::checkPlain($return);
}
return twig_escape_filter($env, $return, $strategy, $charset, $autoescape);
}
// This is a normal render array, which is safe by definition.
return render($string);
}
/**
* Overrides twig_join_filter().
*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment