diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php index 7a7b8ea..15d5223 100644 --- a/core/lib/Drupal/Component/Utility/SafeMarkup.php +++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php @@ -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 diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php index 8118105..9225712 100644 --- a/core/lib/Drupal/Core/Template/TwigEnvironment.php +++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php @@ -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(); diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index e53ad14..a978bde 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -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; @@ -58,70 +59,6 @@ public function __construct(RendererInterface $renderer) { } /** - * 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 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 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); - } - - /** * Sets the URL generator. * * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator @@ -182,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: @@ -360,4 +297,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); + } + } diff --git a/core/modules/system/src/Tests/Theme/TwigExtensionTest.php b/core/modules/system/src/Tests/Theme/TwigExtensionTest.php index 75ce938..7e25ae5 100644 --- a/core/modules/system/src/Tests/Theme/TwigExtensionTest.php +++ b/core/modules/system/src/Tests/Theme/TwigExtensionTest.php @@ -64,14 +64,29 @@ function testTwigExtensionFunction() { } /** + * 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, 'twig_render_var() renders zero correctly when provided as an integer.'); - $this->assertIdentical($extension->renderVar(0.0), 0, 'twig_render_var() renders zero correctly when provided as a double.'); + $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.'); } } diff --git a/core/tests/Drupal/Tests/Core/Theme/TwigEngineTest.php b/core/tests/Drupal/Tests/Core/Theme/TwigEngineTest.php deleted file mode 100644 index b98158d..0000000 --- a/core/tests/Drupal/Tests/Core/Theme/TwigEngineTest.php +++ /dev/null @@ -1,49 +0,0 @@ -root . '/core/themes/engines/twig/twig.engine'; - - $this->twigEnvironment = $this->getMock('\Twig_Environment'); - } - - /** - * 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.'); - } - -} diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index b828e72..9f8bc4a 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -140,79 +140,6 @@ function twig_without($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(). * * Safely joins several strings together.