Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
16 / 16 |
CRAP | |
100.00% |
64 / 64 |
TwigExtension | |
100.00% |
1 / 1 |
|
100.00% |
22 / 22 |
65 | |
100.00% |
64 / 64 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setGenerators | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
setUrlGenerator | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setThemeManager | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
setDateFormatter | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getFunctions | |
100.00% |
1 / 1 |
1 | |
100.00% |
8 / 8 |
|||
anonymous function | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
getFilters | |
100.00% |
1 / 1 |
1 | |
100.00% |
10 / 10 |
|||
getNodeVisitors | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getTokenParsers | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getName | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getPath | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
getUrl | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
getLink | |
100.00% |
1 / 1 |
5 | |
100.00% |
0 / 0 |
|||
getActiveTheme | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getActiveThemePath | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isUrlGenerationSafe | |
100.00% |
1 / 1 |
8 | |
100.00% |
5 / 5 |
|||
attachLibrary | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
escapePlaceholder | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
escapeFilter | |
100.00% |
1 / 1 |
20 | |
100.00% |
15 / 15 |
|||
renderVar | |
100.00% |
1 / 1 |
13 | |
100.00% |
9 / 9 |
|||
safeJoin | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Core\Template\TwigExtension. | |
* | |
* This provides a Twig extension that registers various Drupal specific | |
* extensions to Twig. | |
* | |
* @see \Drupal\Core\CoreServiceProvider | |
*/ | |
namespace Drupal\Core\Template; | |
use Drupal\Component\Utility\Html; | |
use Drupal\Component\Utility\SafeMarkup; | |
use Drupal\Component\Render\MarkupInterface; | |
use Drupal\Core\Datetime\DateFormatter; | |
use Drupal\Core\Render\RenderableInterface; | |
use Drupal\Core\Render\RendererInterface; | |
use Drupal\Core\Routing\UrlGeneratorInterface; | |
use Drupal\Core\Theme\ThemeManagerInterface; | |
use Drupal\Core\Url; | |
/** | |
* A class providing Drupal Twig extensions. | |
* | |
* Specifically Twig functions, filter and node visitors. | |
* | |
* @see \Drupal\Core\CoreServiceProvider | |
*/ | |
class TwigExtension extends \Twig_Extension { | |
/** | |
* The URL generator. | |
* | |
* @var \Drupal\Core\Routing\UrlGeneratorInterface | |
*/ | |
protected $urlGenerator; | |
/** | |
* The renderer. | |
* | |
* @var \Drupal\Core\Render\RendererInterface | |
*/ | |
protected $renderer; | |
/** | |
* The theme manager. | |
* | |
* @var \Drupal\Core\Theme\ThemeManagerInterface | |
*/ | |
protected $themeManager; | |
/** | |
* The date formatter. | |
* | |
* @var \Drupal\Core\Datetime\DateFormatter | |
*/ | |
protected $dateFormatter; | |
/** | |
* Constructs \Drupal\Core\Template\TwigExtension. | |
* | |
* @param \Drupal\Core\Render\RendererInterface $renderer | |
* The renderer. | |
*/ | |
public function __construct(RendererInterface $renderer) { | |
$this->renderer = $renderer; | |
} | |
/** | |
* Sets the URL generator. | |
* | |
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator | |
* The URL generator. | |
* | |
* @return $this | |
* | |
* @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. | |
* Use \Drupal\Core\Template\TwigExtension::setUrlGenerator(). | |
*/ | |
public function setGenerators(UrlGeneratorInterface $url_generator) { | |
return $this->setUrlGenerator($url_generator); | |
} | |
/** | |
* Sets the URL generator. | |
* | |
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator | |
* The URL generator. | |
* | |
* @return $this | |
*/ | |
public function setUrlGenerator(UrlGeneratorInterface $url_generator) { | |
$this->urlGenerator = $url_generator; | |
return $this; | |
} | |
/** | |
* Sets the theme manager. | |
* | |
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager | |
* The theme manager. | |
* | |
* @return $this | |
*/ | |
public function setThemeManager(ThemeManagerInterface $theme_manager) { | |
$this->themeManager = $theme_manager; | |
return $this; | |
} | |
/** | |
* Sets the date formatter. | |
* | |
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter | |
* The date formatter. | |
* | |
* @return $this | |
*/ | |
public function setDateFormatter(DateFormatter $date_formatter) { | |
$this->dateFormatter = $date_formatter; | |
return $this; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getFunctions() { | |
return [ | |
// This function will receive a renderable array, if an array is detected. | |
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'))), | |
new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), | |
new \Twig_SimpleFunction('link', array($this, 'getLink')), | |
new \Twig_SimpleFunction('file_url', function ($uri) { | |
return file_url_transform_relative(file_create_url($uri)); | |
}), | |
new \Twig_SimpleFunction('attach_library', [$this, 'attachLibrary']), | |
new \Twig_SimpleFunction('active_theme_path', [$this, 'getActiveThemePath']), | |
new \Twig_SimpleFunction('active_theme', [$this, 'getActiveTheme']), | |
]; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getFilters() { | |
return array( | |
// Translation filters. | |
new \Twig_SimpleFilter('t', 't', array('is_safe' => array('html'))), | |
new \Twig_SimpleFilter('trans', 't', array('is_safe' => array('html'))), | |
// The "raw" filter is not detectable when parsing "trans" tags. To detect | |
// which prefix must be used for translation (@, !, %), we must clone the | |
// "raw" filter and give it identifiable names. These filters should only | |
// be used in "trans" tags. | |
// @see TwigNodeTrans::compileString() | |
new \Twig_SimpleFilter('placeholder', [$this, 'escapePlaceholder'], array('is_safe' => array('html'), 'needs_environment' => TRUE)), | |
// Replace twig's escape filter with our own. | |
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: | |
// https://github.com/fabpot/Twig/issues/1420 | |
new \Twig_SimpleFilter('safe_join', [$this, 'safeJoin'], ['needs_environment' => true, 'is_safe' => ['html']]), | |
// Array filters. | |
new \Twig_SimpleFilter('without', 'twig_without'), | |
// CSS class and ID filters. | |
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', array($this, 'renderVar')), | |
new \Twig_SimpleFilter('format_date', array($this->dateFormatter, 'format')), | |
); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getNodeVisitors() { | |
// The node visitor is needed to wrap all variables with | |
// render_var -> TwigExtension->renderVar() function. | |
return array( | |
new TwigNodeVisitor(), | |
); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getTokenParsers() { | |
return array( | |
new TwigTransTokenParser(), | |
); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getName() { | |
return 'drupal_core'; | |
} | |
/** | |
* Generates a URL path given a route name and parameters. | |
* | |
* @param $name | |
* The name of the route. | |
* @param array $parameters | |
* An associative array of route parameters names and values. | |
* @param array $options | |
* (optional) An associative array of additional options. The 'absolute' | |
* option is forced to be FALSE. | |
* @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute(). | |
* | |
* @return string | |
* The generated URL path (relative URL) for the given route. | |
*/ | |
public function getPath($name, $parameters = array(), $options = array()) { | |
$options['absolute'] = FALSE; | |
return $this->urlGenerator->generateFromRoute($name, $parameters, $options); | |
} | |
/** | |
* Generates an absolute URL given a route name and parameters. | |
* | |
* @param $name | |
* The name of the route. | |
* @param array $parameters | |
* An associative array of route parameter names and values. | |
* @param array $options | |
* (optional) An associative array of additional options. The 'absolute' | |
* option is forced to be TRUE. | |
* | |
* @return string | |
* The generated absolute URL for the given route. | |
* | |
* @todo Add an option for scheme-relative URLs. | |
*/ | |
public function getUrl($name, $parameters = array(), $options = array()) { | |
// Generate URL. | |
$options['absolute'] = TRUE; | |
$generated_url = $this->urlGenerator->generateFromRoute($name, $parameters, $options, TRUE); | |
// Return as render array, so we can bubble the bubbleable metadata. | |
$build = ['#markup' => $generated_url->getGeneratedUrl()]; | |
$generated_url->applyTo($build); | |
return $build; | |
} | |
/** | |
* Gets a rendered link from an url object. | |
* | |
* @param string $text | |
* The link text for the anchor tag as a translated string. | |
* @param \Drupal\Core\Url|string $url | |
* The URL object or string used for the link. | |
* @param array|\Drupal\Core\Template\Attribute $attributes | |
* An optional array or Attribute object of link attributes. | |
* | |
* @return array | |
* A render array representing a link to the given URL. | |
*/ | |
public function getLink($text, $url, $attributes = []) { | |
if (!$url instanceof Url) { | |
$url = Url::fromUri($url); | |
} | |
if ($attributes) { | |
if ($attributes instanceof Attribute) { | |
$attributes = $attributes->toArray(); | |
} | |
if ($existing_attributes = $url->getOption('attributes')) { | |
$attributes = array_merge($existing_attributes, $attributes); | |
} | |
$url->setOption('attributes', $attributes); | |
} | |
$build = [ | |
'#type' => 'link', | |
'#title' => $text, | |
'#url' => $url, | |
]; | |
return $build; | |
} | |
/** | |
* Gets the name of the active theme. | |
* | |
* @return string | |
* The name of the active theme. | |
*/ | |
public function getActiveTheme() { | |
return $this->themeManager->getActiveTheme()->getName(); | |
} | |
/** | |
* Gets the path of the active theme. | |
* | |
* @return string | |
* The path to the active theme. | |
*/ | |
public function getActiveThemePath() { | |
return $this->themeManager->getActiveTheme()->getPath(); | |
} | |
/** | |
* Determines at compile time whether the generated URL will be safe. | |
* | |
* Saves the unneeded automatic escaping for performance reasons. | |
* | |
* The URL generation process percent encodes non-alphanumeric characters. | |
* Thus, the only character within an URL that must be escaped in HTML is the | |
* ampersand ("&") which separates query params. Thus we cannot mark | |
* the generated URL as always safe, but only when we are sure there won't be | |
* multiple query params. This is the case when there are none or only one | |
* constant parameter given. For instance, we know beforehand this will not | |
* need to be escaped: | |
* - path('route') | |
* - path('route', {'param': 'value'}) | |
* But the following may need to be escaped: | |
* - path('route', var) | |
* - path('route', {'param': ['val1', 'val2'] }) // a sub-array | |
* - path('route', {'param1': 'value1', 'param2': 'value2'}) | |
* If param1 and param2 reference placeholders in the route, it would not | |
* need to be escaped, but we don't know that in advance. | |
* | |
* @param \Twig_Node $args_node | |
* The arguments of the path/url functions. | |
* | |
* @return array | |
* An array with the contexts the URL is safe | |
*/ | |
public function isUrlGenerationSafe(\Twig_Node $args_node) { | |
// Support named arguments. | |
$parameter_node = $args_node->hasNode('parameters') ? $args_node->getNode('parameters') : ($args_node->hasNode(1) ? $args_node->getNode(1) : NULL); | |
if (!isset($parameter_node) || $parameter_node instanceof \Twig_Node_Expression_Array && count($parameter_node) <= 2 && | |
(!$parameter_node->hasNode(1) || $parameter_node->getNode(1) instanceof \Twig_Node_Expression_Constant)) { | |
return array('html'); | |
} | |
return array(); | |
} | |
/** | |
* Attaches an asset library to the template, and hence to the response. | |
* | |
* Allows Twig templates to attach asset libraries using | |
* @code | |
* {{ attach_library('extension/library_name') }} | |
* @endcode | |
* | |
* @param string $library | |
* An asset library. | |
*/ | |
public function attachLibrary($library) { | |
// Use Renderer::render() on a temporary render array to get additional | |
// bubbleable metadata on the render stack. | |
$template_attached = ['#attached' => ['library' => [$library]]]; | |
$this->renderer->render($template_attached); | |
} | |
/** | |
* Provides a placeholder wrapper around ::escapeFilter. | |
* | |
* @param \Twig_Environment $env | |
* A Twig_Environment instance. | |
* @param mixed $string | |
* The value to be escaped. | |
* | |
* @return string|null | |
* The escaped, rendered output, or NULL if there is no valid output. | |
*/ | |
public function escapePlaceholder($env, $string) { | |
return '<em class="placeholder">' . $this->escapeFilter($env, $string) . '</em>'; | |
} | |
/** | |
* Overrides twig_escape_filter(). | |
* | |
* Replacement function for Twig's escape filter. | |
* | |
* Note: This function should be kept in sync with | |
* theme_render_and_autoescape(). | |
* | |
* @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. | |
* | |
* @todo Refactor this to keep it in sync with theme_render_and_autoescape() | |
* in https://www.drupal.org/node/2575065 | |
*/ | |
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 || $arg instanceof MarkupInterface)) { | |
return $arg; | |
} | |
$return = NULL; | |
if (is_scalar($arg)) { | |
$return = (string) $arg; | |
} | |
elseif (is_object($arg)) { | |
if ($arg instanceof RenderableInterface) { | |
$arg = $arg->toRenderable(); | |
} | |
elseif (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('Object of type ' . get_class($arg) . ' cannot be printed.'); | |
} | |
} | |
// 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 Html::escape($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 scalars as it is likely they come from the escape filter. | |
if (is_scalar($arg)) { | |
return $arg; | |
} | |
if (is_object($arg)) { | |
if ($arg instanceof RenderableInterface) { | |
$arg = $arg->toRenderable(); | |
} | |
elseif (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('Object of type ' . get_class($arg) . ' cannot be printed.'); | |
} | |
} | |
// 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); | |
} | |
/** | |
* Joins several strings together safely. | |
* | |
* @param \Twig_Environment $env | |
* A Twig_Environment instance. | |
* @param mixed[]|\Traversable|NULL $value | |
* The pieces to join. | |
* @param string $glue | |
* The delimiter with which to join the string. Defaults to an empty string. | |
* This value is expected to be safe for output and user provided data | |
* should never be used as a glue. | |
* | |
* @return string | |
* The strings joined together. | |
*/ | |
public function safeJoin(\Twig_Environment $env, $value, $glue = '') { | |
if ($value instanceof \Traversable) { | |
$value = iterator_to_array($value, false); | |
} | |
return implode($glue, array_map(function($item) use ($env) { | |
// If $item is not marked safe then it will be escaped. | |
return $this->escapeFilter($env, $item, 'html', NULL, TRUE); | |
}, (array) $value)); | |
} | |
} |