Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
287
core/lib/Drupal/Core/Template/Attribute.php
Normal file
287
core/lib/Drupal/Core/Template/Attribute.php
Normal file
|
@ -0,0 +1,287 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\Attribute.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
use Drupal\Component\Utility\SafeStringInterface;
|
||||
|
||||
/**
|
||||
* Collects, sanitizes, and renders HTML attributes.
|
||||
*
|
||||
* To use, optionally pass in an associative array of defined attributes, or
|
||||
* add attributes using array syntax. For example:
|
||||
* @code
|
||||
* $attributes = new Attribute(array('id' => 'socks'));
|
||||
* $attributes['class'] = array('black-cat', 'white-cat');
|
||||
* $attributes['class'][] = 'black-white-cat';
|
||||
* echo '<cat' . $attributes . '>';
|
||||
* // Produces <cat id="socks" class="black-cat white-cat black-white-cat">
|
||||
* @endcode
|
||||
*
|
||||
* $attributes always prints out all the attributes. For example:
|
||||
* @code
|
||||
* $attributes = new Attribute(array('id' => 'socks'));
|
||||
* $attributes['class'] = array('black-cat', 'white-cat');
|
||||
* $attributes['class'][] = 'black-white-cat';
|
||||
* echo '<cat class="cat ' . $attributes['class'] . '"' . $attributes . '>';
|
||||
* // Produces <cat class="cat black-cat white-cat black-white-cat" id="socks" class="cat black-cat white-cat black-white-cat">
|
||||
* @endcode
|
||||
*
|
||||
* When printing out individual attributes to customize them within a Twig
|
||||
* template, use the "without" filter to prevent attributes that have already
|
||||
* been printed from being printed again. For example:
|
||||
* @code
|
||||
* <cat class="{{ attributes.class }} my-custom-class"{{ attributes|without('class') }}>
|
||||
* {# Produces <cat class="cat black-cat white-cat black-white-cat my-custom-class" id="socks"> #}
|
||||
* @endcode
|
||||
*
|
||||
* The attribute keys and values are automatically sanitized for output with
|
||||
* htmlspecialchars() and the entire attribute string is marked safe for output.
|
||||
*/
|
||||
class Attribute implements \ArrayAccess, \IteratorAggregate, SafeStringInterface {
|
||||
/**
|
||||
* Stores the attribute data.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $storage = array();
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Template\Attribute object.
|
||||
*
|
||||
* @param array $attributes
|
||||
* An associative array of key-value pairs to be converted to attributes.
|
||||
*/
|
||||
public function __construct($attributes = array()) {
|
||||
foreach ($attributes as $name => $value) {
|
||||
$this->offsetSet($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetGet().
|
||||
*/
|
||||
public function offsetGet($name) {
|
||||
if (isset($this->storage[$name])) {
|
||||
return $this->storage[$name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetSet().
|
||||
*/
|
||||
public function offsetSet($name, $value) {
|
||||
$this->storage[$name] = $this->createAttributeValue($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the different types of attribute values.
|
||||
*
|
||||
* @param string $name
|
||||
* The attribute name.
|
||||
* @param mixed $value
|
||||
* The attribute value.
|
||||
*
|
||||
* @return \Drupal\Core\Template\AttributeValueBase
|
||||
* An AttributeValueBase representation of the attribute's value.
|
||||
*/
|
||||
protected function createAttributeValue($name, $value) {
|
||||
// If the value is already an AttributeValueBase object, return it
|
||||
// straight away.
|
||||
if ($value instanceOf AttributeValueBase) {
|
||||
return $value;
|
||||
}
|
||||
// An array value or 'class' attribute name are forced to always be an
|
||||
// AttributeArray value for consistency.
|
||||
if (is_array($value) || $name == 'class') {
|
||||
// Cast the value to an array if the value was passed in as a string.
|
||||
// @todo Decide to fix all the broken instances of class as a string
|
||||
// in core or cast them.
|
||||
$value = new AttributeArray($name, (array) $value);
|
||||
}
|
||||
elseif (is_bool($value)) {
|
||||
$value = new AttributeBoolean($name, $value);
|
||||
}
|
||||
elseif (!is_object($value)) {
|
||||
$value = new AttributeString($name, $value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetUnset().
|
||||
*/
|
||||
public function offsetUnset($name) {
|
||||
unset($this->storage[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetExists().
|
||||
*/
|
||||
public function offsetExists($name) {
|
||||
return isset($this->storage[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds classes or merges them on to array of existing CSS classes.
|
||||
*
|
||||
* @param string|array ...
|
||||
* CSS classes to add to the class attribute array.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addClass() {
|
||||
$args = func_get_args();
|
||||
if ($args) {
|
||||
$classes = array();
|
||||
foreach ($args as $arg) {
|
||||
// Merge the values passed in from the classes array.
|
||||
// The argument is cast to an array to support comma separated single
|
||||
// values or one or more array arguments.
|
||||
$classes = array_merge($classes, (array) $arg);
|
||||
}
|
||||
|
||||
// Merge if there are values, just add them otherwise.
|
||||
if (isset($this->storage['class']) && $this->storage['class'] instanceOf AttributeArray) {
|
||||
// Merge the values passed in from the class value array.
|
||||
$classes = array_merge($this->storage['class']->value(), $classes);
|
||||
$this->storage['class']->exchangeArray($classes);
|
||||
}
|
||||
else {
|
||||
$this->offsetSet('class', $classes);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets values for an attribute key.
|
||||
*
|
||||
* @param string $attribute
|
||||
* Name of the attribute.
|
||||
* @param string|array $value
|
||||
* Value(s) to set for the given attribute key.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttribute($attribute, $value) {
|
||||
$this->offsetSet($attribute, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an attribute from an Attribute object.
|
||||
*
|
||||
* @param string|array ...
|
||||
* Attributes to remove from the attribute array.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeAttribute() {
|
||||
$args = func_get_args();
|
||||
foreach ($args as $arg) {
|
||||
// Support arrays or multiple arguments.
|
||||
if (is_array($arg)) {
|
||||
foreach ($arg as $value) {
|
||||
unset($this->storage[$value]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
unset($this->storage[$arg]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes argument values from array of existing CSS classes.
|
||||
*
|
||||
* @param string|array ...
|
||||
* CSS classes to remove from the class attribute array.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeClass() {
|
||||
// With no class attribute, there is no need to remove.
|
||||
if (isset($this->storage['class']) && $this->storage['class'] instanceOf AttributeArray) {
|
||||
$args = func_get_args();
|
||||
$classes = array();
|
||||
foreach ($args as $arg) {
|
||||
// Merge the values passed in from the classes array.
|
||||
// The argument is cast to an array to support comma separated single
|
||||
// values or one or more array arguments.
|
||||
$classes = array_merge($classes, (array) $arg);
|
||||
}
|
||||
|
||||
// Remove the values passed in from the value array. Use array_values() to
|
||||
// ensure that the array index remains sequential.
|
||||
$classes = array_values(array_diff($this->storage['class']->value(), $classes));
|
||||
$this->storage['class']->exchangeArray($classes);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the class array has the given CSS class.
|
||||
*
|
||||
* @param string $class
|
||||
* The CSS class to check for.
|
||||
*
|
||||
* @return bool
|
||||
* Returns TRUE if the class exists, or FALSE otherwise.
|
||||
*/
|
||||
public function hasClass($class) {
|
||||
if (isset($this->storage['class']) && $this->storage['class'] instanceOf AttributeArray) {
|
||||
return in_array($class, $this->storage['class']->value());
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __toString() method.
|
||||
*/
|
||||
public function __toString() {
|
||||
$return = '';
|
||||
/** @var \Drupal\Core\Template\AttributeValueBase $value */
|
||||
foreach ($this->storage as $name => $value) {
|
||||
$rendered = $value->render();
|
||||
if ($rendered) {
|
||||
$return .= ' ' . $rendered;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __clone() method.
|
||||
*/
|
||||
public function __clone() {
|
||||
foreach ($this->storage as $name => $value) {
|
||||
$this->storage[$name] = clone $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new \ArrayIterator($this->storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole array.
|
||||
*/
|
||||
public function storage() {
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
}
|
104
core/lib/Drupal/Core/Template/AttributeArray.php
Normal file
104
core/lib/Drupal/Core/Template/AttributeArray.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\AttributeArray.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* A class that defines a type of Attribute that can be added to as an array.
|
||||
*
|
||||
* To use with Attribute, the array must be specified.
|
||||
* Correct:
|
||||
* @code
|
||||
* $attributes = new Attribute();
|
||||
* $attributes['class'] = array();
|
||||
* $attributes['class'][] = 'cat';
|
||||
* @endcode
|
||||
* Incorrect:
|
||||
* @code
|
||||
* $attributes = new Attribute();
|
||||
* $attributes['class'][] = 'cat';
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Template\Attribute
|
||||
*/
|
||||
class AttributeArray extends AttributeValueBase implements \ArrayAccess, \IteratorAggregate {
|
||||
|
||||
/**
|
||||
* Ensures empty array as a result of array_filter will not print '$name=""'.
|
||||
*
|
||||
* @see \Drupal\Core\Template\AttributeArray::__toString()
|
||||
* @see \Drupal\Core\Template\AttributeValueBase::render()
|
||||
*/
|
||||
const RENDER_EMPTY_ATTRIBUTE = FALSE;
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetGet().
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
return $this->value[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetSet().
|
||||
*/
|
||||
public function offsetSet($offset, $value) {
|
||||
if (isset($offset)) {
|
||||
$this->value[$offset] = $value;
|
||||
}
|
||||
else {
|
||||
$this->value[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetUnset().
|
||||
*/
|
||||
public function offsetUnset($offset) {
|
||||
unset($this->value[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetExists().
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
return isset($this->value[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __toString() method.
|
||||
*/
|
||||
public function __toString() {
|
||||
// Filter out any empty values before printing.
|
||||
$this->value = array_unique(array_filter($this->value));
|
||||
return htmlspecialchars(implode(' ', $this->value), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator().
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new \ArrayIterator($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange the array for another one.
|
||||
*
|
||||
* @see ArrayObject::exchangeArray
|
||||
*
|
||||
* @param array $input
|
||||
* The array input to replace the internal value.
|
||||
*
|
||||
* @return array
|
||||
* The old array value.
|
||||
*/
|
||||
public function exchangeArray($input) {
|
||||
$old = $this->value;
|
||||
$this->value = $input;
|
||||
return $old;
|
||||
}
|
||||
|
||||
}
|
46
core/lib/Drupal/Core/Template/AttributeBoolean.php
Normal file
46
core/lib/Drupal/Core/Template/AttributeBoolean.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\AttributeBoolean.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* A class that defines a type of boolean HTML attribute.
|
||||
*
|
||||
* Boolean HTML attributes are not attributes with values of TRUE/FALSE.
|
||||
* They are attributes that if they exist in the tag, they are TRUE.
|
||||
* Examples include selected, disabled, checked, readonly.
|
||||
*
|
||||
* To set a boolean attribute on the Attribute class, set it to TRUE.
|
||||
* @code
|
||||
* $attributes = new Attribute();
|
||||
* $attributes['disabled'] = TRUE;
|
||||
* echo '<select' . $attributes . '/>';
|
||||
* // produces <select disabled>;
|
||||
* $attributes['disabled'] = FALSE;
|
||||
* echo '<select' . $attributes . '/>';
|
||||
* // produces <select>;
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Template\Attribute
|
||||
*/
|
||||
class AttributeBoolean extends AttributeValueBase {
|
||||
|
||||
/**
|
||||
* Overrides AttributeValueBase::render().
|
||||
*/
|
||||
public function render() {
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __toString() method.
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->value === FALSE ? '' : htmlspecialchars($this->name, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
}
|
34
core/lib/Drupal/Core/Template/AttributeString.php
Normal file
34
core/lib/Drupal/Core/Template/AttributeString.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\AttributeString.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* A class that represents most standard HTML attributes.
|
||||
*
|
||||
* To use with the Attribute class, set the key to be the attribute name
|
||||
* and the value the attribute value.
|
||||
* @code
|
||||
* $attributes = new Attribute(array());
|
||||
* $attributes['id'] = 'socks';
|
||||
* $attributes['style'] = 'background-color:white';
|
||||
* echo '<cat ' . $attributes . '>';
|
||||
* // Produces: <cat id="socks" style="background-color:white">.
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Template\Attribute
|
||||
*/
|
||||
class AttributeString extends AttributeValueBase {
|
||||
|
||||
/**
|
||||
* Implements the magic __toString() method.
|
||||
*/
|
||||
public function __toString() {
|
||||
return htmlspecialchars($this->value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
}
|
74
core/lib/Drupal/Core/Template/AttributeValueBase.php
Normal file
74
core/lib/Drupal/Core/Template/AttributeValueBase.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\AttributeValueBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* Defines the base class for an attribute type.
|
||||
*
|
||||
* @see \Drupal\Core\Template\Attribute
|
||||
*/
|
||||
abstract class AttributeValueBase {
|
||||
|
||||
/**
|
||||
* Renders '$name=""' if $value is an empty string.
|
||||
*
|
||||
* @see \Drupal\Core\Template\AttributeValueBase::render()
|
||||
*/
|
||||
const RENDER_EMPTY_ATTRIBUTE = TRUE;
|
||||
|
||||
/**
|
||||
* The value itself.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* The name of the value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Template\AttributeValueBase object.
|
||||
*/
|
||||
public function __construct($name, $value) {
|
||||
$this->name = $name;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the attribute.
|
||||
*
|
||||
* While __toString only returns the value in a string form, render()
|
||||
* contains the name of the attribute as well.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the attribute.
|
||||
*/
|
||||
public function render() {
|
||||
$value = (string) $this;
|
||||
if (isset($this->value) && static::RENDER_EMPTY_ATTRIBUTE || !empty($value)) {
|
||||
return htmlspecialchars($this->name, ENT_QUOTES, 'UTF-8') . '="' . $value . '"';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw value.
|
||||
*/
|
||||
public function value() {
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the magic __toString() method.
|
||||
*/
|
||||
abstract function __toString();
|
||||
|
||||
}
|
63
core/lib/Drupal/Core/Template/Loader/FilesystemLoader.php
Normal file
63
core/lib/Drupal/Core/Template/Loader/FilesystemLoader.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\Loader\FilesystemLoader.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template\Loader;
|
||||
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
|
||||
/**
|
||||
* Loads templates from the filesystem.
|
||||
*
|
||||
* This loader adds module and theme template paths as namespaces to the Twig
|
||||
* filesystem loader so that templates can be referenced by namespace, like
|
||||
* @block/block.html.twig or @mytheme/page.html.twig.
|
||||
*/
|
||||
class FilesystemLoader extends \Twig_Loader_Filesystem {
|
||||
|
||||
/**
|
||||
* Constructs a new FilesystemLoader object.
|
||||
*
|
||||
* @param string|array $paths
|
||||
* A path or an array of paths to check for templates.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler service.
|
||||
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
|
||||
* The theme handler service.
|
||||
*/
|
||||
public function __construct($paths = array(), ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
|
||||
parent::__construct($paths);
|
||||
|
||||
// Add namespaced paths for modules and themes.
|
||||
$namespaces = array();
|
||||
foreach ($module_handler->getModuleList() as $name => $extension) {
|
||||
$namespaces[$name] = $extension->getPath();
|
||||
}
|
||||
foreach ($theme_handler->listInfo() as $name => $extension) {
|
||||
$namespaces[$name] = $extension->getPath();
|
||||
}
|
||||
|
||||
foreach ($namespaces as $name => $path) {
|
||||
$this->addPath($path . '/templates', $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a path where templates are stored.
|
||||
*
|
||||
* @param string $path
|
||||
* A path where to look for templates.
|
||||
* @param string $namespace
|
||||
* (optional) A path name.
|
||||
*/
|
||||
public function addPath($path, $namespace = self::MAIN_NAMESPACE) {
|
||||
// Invalidate the cache.
|
||||
$this->cache = array();
|
||||
$this->paths[$namespace][] = rtrim($path, '/\\');
|
||||
}
|
||||
|
||||
}
|
42
core/lib/Drupal/Core/Template/Loader/StringLoader.php
Normal file
42
core/lib/Drupal/Core/Template/Loader/StringLoader.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\Loader\StringLoader.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template\Loader;
|
||||
|
||||
/**
|
||||
* Loads string templates, also known as inline templates.
|
||||
*
|
||||
* This loader is intended to be used in a Twig loader chain and whitelists
|
||||
* string templates that begin with the following comment:
|
||||
* @code
|
||||
* {# inline_template_start #}
|
||||
* @endcode
|
||||
*
|
||||
* This class override ensures that the string loader behaves as expected in
|
||||
* the loader chain. If Twig's string loader is used as is, any string (even a
|
||||
* reference to a file-based Twig template) is treated as a valid template and
|
||||
* is rendered instead of a \Twig_Error_Loader exception being thrown.
|
||||
*
|
||||
* @see \Drupal\Core\Template\TwigEnvironment::renderInline()
|
||||
* @see \Drupal\Core\Render\Element\InlineTemplate
|
||||
* @see twig_render_template()
|
||||
*/
|
||||
class StringLoader extends \Twig_Loader_String {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function exists($name) {
|
||||
if (strpos($name, '{# inline_template_start #}') === 0) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
69
core/lib/Drupal/Core/Template/Loader/ThemeRegistryLoader.php
Normal file
69
core/lib/Drupal/Core/Template/Loader/ThemeRegistryLoader.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\Loader\ThemeRegistryLoader.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template\Loader;
|
||||
|
||||
use Drupal\Core\Theme\Registry;
|
||||
|
||||
/**
|
||||
* Loads templates based on information from the Drupal theme registry.
|
||||
*
|
||||
* Allows for template inheritance based on the currently active template.
|
||||
*/
|
||||
class ThemeRegistryLoader extends \Twig_Loader_Filesystem {
|
||||
|
||||
/**
|
||||
* The theme registry used to determine which template to use.
|
||||
*
|
||||
* @var \Drupal\Core\Theme\Registry
|
||||
*/
|
||||
protected $themeRegistry;
|
||||
|
||||
/**
|
||||
* Constructs a new ThemeRegistryLoader object.
|
||||
*
|
||||
* @param \Drupal\Core\Theme\Registry $theme_registry
|
||||
* The theme registry.
|
||||
*/
|
||||
public function __construct(Registry $theme_registry) {
|
||||
$this->themeRegistry = $theme_registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the requested template.
|
||||
*
|
||||
* @param string $name
|
||||
* The name of the template to load.
|
||||
*
|
||||
* @return string
|
||||
* The path to the template.
|
||||
*
|
||||
* @throws \Twig_Error_Loader
|
||||
* Thrown if a template matching $name cannot be found.
|
||||
*/
|
||||
protected function findTemplate($name) {
|
||||
// Allow for loading based on the Drupal theme registry.
|
||||
$hook = str_replace('.html.twig', '', strtr($name, '-', '_'));
|
||||
$theme_registry = $this->themeRegistry->getRuntime();
|
||||
|
||||
if ($theme_registry->has($hook)) {
|
||||
$info = $theme_registry->get($hook);
|
||||
if (isset($info['path'])) {
|
||||
$path = $info['path'] . '/' . $name;
|
||||
}
|
||||
elseif (isset($info['template'])) {
|
||||
$path = $info['template'] . '.html.twig';
|
||||
}
|
||||
if (isset($path) && is_file($path)) {
|
||||
return $this->cache[$name] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Twig_Error_Loader(sprintf('Unable to find template "%s" in the Drupal theme registry.', $name));
|
||||
}
|
||||
|
||||
}
|
207
core/lib/Drupal/Core/Template/TwigEnvironment.php
Normal file
207
core/lib/Drupal/Core/Template/TwigEnvironment.php
Normal file
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\TwigEnvironment.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
use Drupal\Core\PhpStorage\PhpStorageFactory;
|
||||
|
||||
/**
|
||||
* A class that defines a Twig environment for Drupal.
|
||||
*
|
||||
* Instances of this class are used to store the configuration and extensions,
|
||||
* and are used to load templates from the file system or other locations.
|
||||
*
|
||||
* @see core\vendor\twig\twig\lib\Twig\Environment.php
|
||||
*/
|
||||
class TwigEnvironment extends \Twig_Environment {
|
||||
protected $cache_object = NULL;
|
||||
protected $storage = NULL;
|
||||
|
||||
/**
|
||||
* Static cache of template classes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $templateClasses;
|
||||
|
||||
/**
|
||||
* Constructs a TwigEnvironment object and stores cache and storage
|
||||
* internally.
|
||||
*
|
||||
* @param string $root
|
||||
* The app root.
|
||||
* @param \Twig_LoaderInterface $loader
|
||||
* The Twig loader or loader chain.
|
||||
* @param array $options
|
||||
* The options for the Twig environment.
|
||||
*/
|
||||
public function __construct($root, \Twig_LoaderInterface $loader = NULL, $options = array()) {
|
||||
// @todo Pass as arguments from the DIC.
|
||||
$this->cache_object = \Drupal::cache();
|
||||
|
||||
// Ensure that twig.engine is loaded, given that it is needed to render a
|
||||
// template because functions like TwigExtension::escapeFilter() are called.
|
||||
require_once $root . '/core/themes/engines/twig/twig.engine';
|
||||
|
||||
$this->templateClasses = array();
|
||||
|
||||
$options += array(
|
||||
// @todo Ensure garbage collection of expired files.
|
||||
'cache' => TRUE,
|
||||
'debug' => FALSE,
|
||||
'auto_reload' => NULL,
|
||||
);
|
||||
// Ensure autoescaping is always on.
|
||||
$options['autoescape'] = TRUE;
|
||||
|
||||
$this->loader = $loader;
|
||||
parent::__construct($this->loader, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the compiled template needs an update.
|
||||
*/
|
||||
protected function isFresh($cache_filename, $name) {
|
||||
$cid = 'twig:' . $cache_filename;
|
||||
$obj = $this->cache_object->get($cid);
|
||||
$mtime = isset($obj->data) ? $obj->data : FALSE;
|
||||
return $mtime === FALSE || $this->isTemplateFresh($name, $mtime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the source and write the compiled template to disk.
|
||||
*/
|
||||
public function updateCompiledTemplate($cache_filename, $name) {
|
||||
$source = $this->loader->getSource($name);
|
||||
$compiled_source = $this->compileSource($source, $name);
|
||||
$this->storage()->save($cache_filename, $compiled_source);
|
||||
// Save the last modification time
|
||||
$cid = 'twig:' . $cache_filename;
|
||||
$this->cache_object->set($cid, REQUEST_TIME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Twig_Environment::loadTemplate().
|
||||
*
|
||||
* We need to overwrite this function to integrate with drupal_php_storage().
|
||||
*
|
||||
* This is a straight copy from loadTemplate() changed to use
|
||||
* drupal_php_storage().
|
||||
*
|
||||
* @param string $name
|
||||
* The template name or the string which should be rendered as template.
|
||||
* @param int $index
|
||||
* The index if it is an embedded template.
|
||||
*
|
||||
* @return \Twig_TemplateInterface
|
||||
* A template instance representing the given template name.
|
||||
*
|
||||
* @throws \Twig_Error_Loader
|
||||
* When the template cannot be found.
|
||||
* @throws \Twig_Error_Syntax
|
||||
* When an error occurred during compilation.
|
||||
*/
|
||||
public function loadTemplate($name, $index = NULL) {
|
||||
$cls = $this->getTemplateClass($name, $index);
|
||||
|
||||
if (isset($this->loadedTemplates[$cls])) {
|
||||
return $this->loadedTemplates[$cls];
|
||||
}
|
||||
|
||||
if (!class_exists($cls, FALSE)) {
|
||||
$cache_filename = $this->getCacheFilename($name);
|
||||
|
||||
if ($cache_filename === FALSE) {
|
||||
$compiled_source = $this->compileSource($this->loader->getSource($name), $name);
|
||||
eval('?' . '>' . $compiled_source);
|
||||
}
|
||||
else {
|
||||
|
||||
// If autoreload is on, check that the template has not been
|
||||
// modified since the last compilation.
|
||||
if ($this->isAutoReload() && !$this->isFresh($cache_filename, $name)) {
|
||||
$this->updateCompiledTemplate($cache_filename, $name);
|
||||
}
|
||||
|
||||
if (!$this->storage()->load($cache_filename)) {
|
||||
$this->updateCompiledTemplate($cache_filename, $name);
|
||||
$this->storage()->load($cache_filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->runtimeInitialized) {
|
||||
$this->initRuntime();
|
||||
}
|
||||
|
||||
return $this->loadedTemplates[$cls] = new $cls($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PHP code storage object to use for the compiled Twig files.
|
||||
*
|
||||
* @return \Drupal\Component\PhpStorage\PhpStorageInterface
|
||||
*/
|
||||
protected function storage() {
|
||||
if (!isset($this->storage)) {
|
||||
$this->storage = PhpStorageFactory::get('twig');
|
||||
}
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template class associated with the given string.
|
||||
*
|
||||
* @param string $name
|
||||
* The name for which to calculate the template class name.
|
||||
* @param int $index
|
||||
* The index if it is an embedded template.
|
||||
*
|
||||
* @return string
|
||||
* The template class name.
|
||||
*/
|
||||
public function getTemplateClass($name, $index = NULL) {
|
||||
// We override this method to add caching because it gets called multiple
|
||||
// times when the same template is used more than once. For example, a page
|
||||
// rendering 50 nodes without any node template overrides will use the same
|
||||
// node.html.twig for the output of each node and the same compiled class.
|
||||
$cache_index = $name . (NULL === $index ? '' : '_' . $index);
|
||||
if (!isset($this->templateClasses[$cache_index])) {
|
||||
$this->templateClasses[$cache_index] = $this->templateClassPrefix . hash('sha256', $this->loader->getCacheKey($name)) . (NULL === $index ? '' : '_' . $index);
|
||||
}
|
||||
return $this->templateClasses[$cache_index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a twig string directly.
|
||||
*
|
||||
* Warning: You should use the render element 'inline_template' together with
|
||||
* the #template attribute instead of this method directly.
|
||||
* On top of that you have to ensure that the template string is not dynamic
|
||||
* but just an ordinary static php string, because there may be installations
|
||||
* using read-only PHPStorage that want to generate all possible twig
|
||||
* templates as part of a build step. So it is important that an automated
|
||||
* script can find the templates and extract them. This is only possible if
|
||||
* the template is a regular string.
|
||||
*
|
||||
* @param string $template_string
|
||||
* The template string to render with placeholders.
|
||||
* @param array $context
|
||||
* An array of parameters to pass to the template.
|
||||
*
|
||||
* @return string
|
||||
* The rendered inline template.
|
||||
*
|
||||
* @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;
|
||||
return $this->loadTemplate($template_string, NULL)->render($context);
|
||||
}
|
||||
|
||||
}
|
513
core/lib/Drupal/Core/Template/TwigExtension.php
Normal file
513
core/lib/Drupal/Core/Template/TwigExtension.php
Normal file
|
@ -0,0 +1,513 @@
|
|||
<?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\SafeMarkup;
|
||||
use Drupal\Component\Utility\SafeStringInterface;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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('url_from_path', array($this, 'getUrlFromPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
|
||||
new \Twig_SimpleFunction('link', array($this, 'getLink')),
|
||||
new \Twig_SimpleFunction('file_url', 'file_create_url'),
|
||||
new \Twig_SimpleFunction('attach_library', [$this, 'attachLibrary']),
|
||||
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('passthrough', 'twig_raw_filter', array('is_safe' => array('html'))),
|
||||
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', 'twig_drupal_join_filter', array('is_safe' => array('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')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 cacheability metadata.
|
||||
$build = ['#markup' => $generated_url->getGeneratedUrl()];
|
||||
$generated_url->applyTo($build);
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an absolute URL given a path.
|
||||
*
|
||||
* @param string $path
|
||||
* The path.
|
||||
* @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 path.
|
||||
*
|
||||
* @deprecated in Drupal 8.0.x-dev and will be removed before Drupal 8.0.0.
|
||||
*/
|
||||
public function getUrlFromPath($path, $options = array()) {
|
||||
// Generate URL.
|
||||
$options['absolute'] = TRUE;
|
||||
$generated_url = $this->urlGenerator->generateFromPath($path, $options, TRUE);
|
||||
|
||||
// Return as render array, so we can bubble the cacheability 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 $attributes
|
||||
* An optional array of link attributes.
|
||||
*
|
||||
* @return array
|
||||
* A render array representing a link to the given URL.
|
||||
*/
|
||||
public function getLink($text, $url, array $attributes = []) {
|
||||
if (!$url instanceof Url) {
|
||||
$url = Url::fromUri($url);
|
||||
}
|
||||
if ($attributes) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. E.g. 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.
|
||||
*
|
||||
* @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 || $arg instanceOf SafeStringInterface)) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
190
core/lib/Drupal/Core/Template/TwigNodeTrans.php
Normal file
190
core/lib/Drupal/Core/Template/TwigNodeTrans.php
Normal file
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\TwigNodeTrans.
|
||||
*
|
||||
* This Twig extension was originally based on Twig i18n extension. It has been
|
||||
* severely modified to work properly with the complexities of the Drupal
|
||||
* translation system.
|
||||
*
|
||||
* @see http://twig.sensiolabs.org/doc/extensions/i18n.html
|
||||
* @see https://github.com/fabpot/Twig-extensions
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* A class that defines the Twig 'trans' tag for Drupal.
|
||||
*/
|
||||
class TwigNodeTrans extends \Twig_Node {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(\Twig_NodeInterface $body, \Twig_NodeInterface $plural = NULL, \Twig_Node_Expression $count = NULL, \Twig_Node_Expression $options = NULL, $lineno, $tag = NULL) {
|
||||
parent::__construct(array(
|
||||
'count' => $count,
|
||||
'body' => $body,
|
||||
'plural' => $plural,
|
||||
'options' => $options,
|
||||
), array(), $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function compile(\Twig_Compiler $compiler) {
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$options = $this->getNode('options');
|
||||
|
||||
list($singular, $tokens) = $this->compileString($this->getNode('body'));
|
||||
$plural = NULL;
|
||||
|
||||
if (NULL !== $this->getNode('plural')) {
|
||||
list($plural, $pluralTokens) = $this->compileString($this->getNode('plural'));
|
||||
$tokens = array_merge($tokens, $pluralTokens);
|
||||
}
|
||||
|
||||
// Start writing with the function to be called.
|
||||
$compiler->write('echo ' . (empty($plural) ? 't' : '\Drupal::translation()->formatPlural') . '(');
|
||||
|
||||
// Move the count to the beginning of the parameters list.
|
||||
if (!empty($plural)) {
|
||||
$compiler->raw('abs(')->subcompile($this->getNode('count'))->raw('), ');
|
||||
}
|
||||
|
||||
// Write the singular text parameter.
|
||||
$compiler->subcompile($singular);
|
||||
|
||||
// Write the plural text parameter, if necessary.
|
||||
if (!empty($plural)) {
|
||||
$compiler->raw(', ')->subcompile($plural);
|
||||
}
|
||||
|
||||
// Write any tokens found as an associative array parameter, otherwise just
|
||||
// leave as an empty array.
|
||||
$compiler->raw(', array(');
|
||||
foreach ($tokens as $token) {
|
||||
$compiler->string($token->getAttribute('placeholder'))->raw(' => ')->subcompile($token)->raw(', ');
|
||||
}
|
||||
$compiler->raw(')');
|
||||
|
||||
// Write any options passed.
|
||||
if (!empty($options)) {
|
||||
$compiler->raw(', ')->subcompile($options);
|
||||
}
|
||||
|
||||
// Write function closure.
|
||||
$compiler->raw(')');
|
||||
|
||||
// @todo Add debug output, see https://www.drupal.org/node/2512672
|
||||
|
||||
// End writing.
|
||||
$compiler->raw(";\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the text and tokens for the "trans" tag.
|
||||
*
|
||||
* @param \Twig_NodeInterface $body
|
||||
* The node to compile.
|
||||
*
|
||||
* @return array
|
||||
* Returns an array containing the two following parameters:
|
||||
* - string $text
|
||||
* The extracted text.
|
||||
* - array $tokens
|
||||
* The extracted tokens as new \Twig_Node_Expression_Name instances.
|
||||
*/
|
||||
protected function compileString(\Twig_NodeInterface $body) {
|
||||
if ($body instanceof \Twig_Node_Expression_Name || $body instanceof \Twig_Node_Expression_Constant || $body instanceof \Twig_Node_Expression_TempName) {
|
||||
return array($body, array());
|
||||
}
|
||||
|
||||
$tokens = array();
|
||||
if (count($body)) {
|
||||
$text = '';
|
||||
|
||||
foreach ($body as $node) {
|
||||
if (get_class($node) === 'Twig_Node' && $node->getNode(0) instanceof \Twig_Node_SetTemp) {
|
||||
$node = $node->getNode(1);
|
||||
}
|
||||
|
||||
if ($node instanceof \Twig_Node_Print) {
|
||||
$n = $node->getNode('expr');
|
||||
while ($n instanceof \Twig_Node_Expression_Filter) {
|
||||
$n = $n->getNode('node');
|
||||
}
|
||||
|
||||
$args = $n;
|
||||
|
||||
// Support TwigExtension->renderVar() function in chain.
|
||||
if ($args instanceof \Twig_Node_Expression_Function) {
|
||||
$args = $n->getNode('arguments')->getNode(0);
|
||||
}
|
||||
|
||||
// Detect if a token implements one of the filters reserved for
|
||||
// modifying the prefix of a token. The default prefix used for
|
||||
// translations is "@". This escapes the printed token and makes them
|
||||
// safe for templates.
|
||||
// @see TwigExtension::getFilters()
|
||||
$argPrefix = '@';
|
||||
while ($args instanceof \Twig_Node_Expression_Filter) {
|
||||
switch ($args->getNode('filter')->getAttribute('value')) {
|
||||
case 'passthrough':
|
||||
$argPrefix = '!';
|
||||
break;
|
||||
case 'placeholder':
|
||||
$argPrefix = '%';
|
||||
break;
|
||||
}
|
||||
$args = $args->getNode('node');
|
||||
}
|
||||
if ($args instanceof \Twig_Node_Expression_GetAttr) {
|
||||
$argName = array();
|
||||
// Reuse the incoming expression.
|
||||
$expr = $args;
|
||||
// Assemble a valid argument name by walking through the expression.
|
||||
$argName[] = $args->getNode('attribute')->getAttribute('value');
|
||||
while ($args->hasNode('node')) {
|
||||
$args = $args->getNode('node');
|
||||
if ($args instanceof \Twig_Node_Expression_Name) {
|
||||
$argName[] = $args->getAttribute('name');
|
||||
}
|
||||
else {
|
||||
$argName[] = $args->getNode('attribute')->getAttribute('value');
|
||||
}
|
||||
}
|
||||
$argName = array_reverse($argName);
|
||||
$argName = implode('.', $argName);
|
||||
}
|
||||
else {
|
||||
$argName = $n->getAttribute('name');
|
||||
if (!is_null($args)) {
|
||||
$argName = $args->getAttribute('name');
|
||||
}
|
||||
$expr = new \Twig_Node_Expression_Name($argName, $n->getLine());
|
||||
}
|
||||
$placeholder = sprintf('%s%s', $argPrefix, $argName);
|
||||
$text .= $placeholder;
|
||||
$expr->setAttribute('placeholder', $placeholder);
|
||||
$tokens[] = $expr;
|
||||
}
|
||||
else {
|
||||
$text .= $node->getAttribute('data');
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$text = $body->getAttribute('data');
|
||||
}
|
||||
|
||||
return array(new \Twig_Node(array(new \Twig_Node_Expression_Constant(trim($text), $body->getLine()))), $tokens);
|
||||
}
|
||||
|
||||
}
|
70
core/lib/Drupal/Core/Template/TwigNodeVisitor.php
Normal file
70
core/lib/Drupal/Core/Template/TwigNodeVisitor.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\TwigNodeVisitor.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* Provides a Twig_NodeVisitor to change the generated parse-tree.
|
||||
*
|
||||
* This is used to ensure that everything printed is wrapped via the
|
||||
* TwigExtension->renderVar() function in order to just write {{ content }}
|
||||
* in templates instead of having to write {{ render_var(content) }}.
|
||||
*
|
||||
* @see twig_render
|
||||
*/
|
||||
class TwigNodeVisitor implements \Twig_NodeVisitorInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
|
||||
// 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)) {
|
||||
// No need to add the callback, we have escape active already.
|
||||
unset($this->skipRenderVarFunction);
|
||||
return $node;
|
||||
}
|
||||
$class = get_class($node);
|
||||
$line = $node->getLine();
|
||||
return new $class(
|
||||
new \Twig_Node_Expression_Function('render_var', new \Twig_Node(array($node->getNode('expr'))), $line),
|
||||
$line
|
||||
);
|
||||
}
|
||||
// Change the 'escape' filter to our own 'drupal_escape' filter.
|
||||
else if ($node instanceof \Twig_Node_Expression_Filter) {
|
||||
$name = $node->getNode('filter')->getAttribute('value');
|
||||
if ('escape' == $name || 'e' == $name) {
|
||||
// Use our own escape filter that is SafeMarkup aware.
|
||||
$node->getNode('filter')->setAttribute('value', 'drupal_escape');
|
||||
|
||||
// Store that we have a filter active already that knows how to deal with render arrays.
|
||||
$this->skipRenderVarFunction = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function getPriority() {
|
||||
// Just above the Optimizer, which is the normal last one.
|
||||
return 256;
|
||||
}
|
||||
|
||||
}
|
109
core/lib/Drupal/Core/Template/TwigTransTokenParser.php
Normal file
109
core/lib/Drupal/Core/Template/TwigTransTokenParser.php
Normal file
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Template\TwigTransTokenParser.
|
||||
*
|
||||
* @see http://twig.sensiolabs.org/doc/extensions/i18n.html
|
||||
* @see https://github.com/fabpot/Twig-extensions
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* A class that defines the Twig 'trans' token parser for Drupal.
|
||||
*
|
||||
* The token parser converts a token stream created from template source
|
||||
* code into an Abstract Syntax Tree (AST). The AST will later be compiled
|
||||
* into PHP code usable for runtime execution of the template.
|
||||
*
|
||||
* @see \Twig_TokenParser
|
||||
*/
|
||||
class TwigTransTokenParser extends \Twig_TokenParser {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(\Twig_Token $token) {
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
$body = NULL;
|
||||
$options = NULL;
|
||||
$count = NULL;
|
||||
$plural = NULL;
|
||||
|
||||
if (!$stream->test(\Twig_Token::BLOCK_END_TYPE) && $stream->test(\Twig_Token::STRING_TYPE)) {
|
||||
$body = $this->parser->getExpressionParser()->parseExpression();
|
||||
}
|
||||
if (!$stream->test(\Twig_Token::BLOCK_END_TYPE) && $stream->test(\Twig_Token::NAME_TYPE, 'with')) {
|
||||
$stream->next();
|
||||
$options = $this->parser->getExpressionParser()->parseExpression();
|
||||
}
|
||||
if (!$body) {
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideForFork'));
|
||||
if ('plural' === $stream->next()->getValue()) {
|
||||
$count = $this->parser->getExpressionParser()->parseExpression();
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
$plural = $this->parser->subparse(array($this, 'decideForEnd'), TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
$this->checkTransString($body, $lineno);
|
||||
|
||||
$node = new TwigNodeTrans($body, $plural, $count, $options, $lineno, $this->getTag());
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a 'plural' switch or the end of a 'trans' tag.
|
||||
*/
|
||||
public function decideForFork($token) {
|
||||
return $token->test(array('plural', 'endtrans'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the end of a 'trans' tag.
|
||||
*/
|
||||
public function decideForEnd($token) {
|
||||
return $token->test('endtrans');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag() {
|
||||
return 'trans';
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that any nodes that are parsed are only of allowed types.
|
||||
*
|
||||
* @param \Twig_NodeInterface $body
|
||||
* The expression to check.
|
||||
* @param integer $lineno
|
||||
* The source line.
|
||||
*
|
||||
* @throws \Twig_Error_Syntax
|
||||
*/
|
||||
protected function checkTransString(\Twig_NodeInterface $body, $lineno) {
|
||||
foreach ($body as $node) {
|
||||
if (
|
||||
$node instanceof \Twig_Node_Text
|
||||
||
|
||||
($node instanceof \Twig_Node_Print && $node->getNode('expr') instanceof \Twig_Node_Expression_Name)
|
||||
||
|
||||
($node instanceof \Twig_Node_Print && $node->getNode('expr') instanceof \Twig_Node_Expression_GetAttr)
|
||||
||
|
||||
($node instanceof \Twig_Node_Print && $node->getNode('expr') instanceof \Twig_Node_Expression_Filter)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
throw new \Twig_Error_Syntax(sprintf('The text to be translated with "trans" can only contain references to simple variables'), $lineno);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue