Issue #2114563 by joelpittet, martin107, Cottser: Remove TwigReference with the help of 'without' filter replacing 'show'/'hide' functions.
parent
d8c718b2f6
commit
7eda0ecba5
|
@ -125,7 +125,6 @@ class CoreServiceProvider implements ServiceProviderInterface {
|
|||
// When in the installer, twig_cache must be FALSE until we know the
|
||||
// files folder is writable.
|
||||
'cache' => drupal_installation_attempted() ? FALSE : settings()->get('twig_cache', TRUE),
|
||||
'base_template_class' => 'Drupal\Core\Template\TwigTemplate',
|
||||
// @todo Remove in followup issue
|
||||
// @see http://drupal.org/node/1712444.
|
||||
'autoescape' => FALSE,
|
||||
|
|
|
@ -21,25 +21,24 @@ class TwigExtension extends \Twig_Extension {
|
|||
// @todo re-add unset => twig_unset if this is really needed
|
||||
return array(
|
||||
// @todo Remove URL function once http://drupal.org/node/1778610 is resolved.
|
||||
'url' => new \Twig_Function_Function('url'),
|
||||
// These functions will receive a TwigReference object, if a render array is detected
|
||||
'hide' => new TwigReferenceFunction('twig_hide'),
|
||||
'render_var' => new TwigReferenceFunction('twig_render_var'),
|
||||
'show' => new TwigReferenceFunction('twig_show'),
|
||||
new \Twig_SimpleFunction('url', 'url'),
|
||||
// This function will receive a renderable array, if an array is detected.
|
||||
new \Twig_SimpleFunction('render_var', 'twig_render_var'),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFilters() {
|
||||
return array(
|
||||
't' => new \Twig_Filter_Function('t'),
|
||||
'trans' => new \Twig_Filter_Function('t'),
|
||||
new \Twig_SimpleFilter('t', 't'),
|
||||
new \Twig_SimpleFilter('trans', 't'),
|
||||
// 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()
|
||||
'passthrough' => new \Twig_Filter_Function('twig_raw_filter'),
|
||||
'placeholder' => new \Twig_Filter_Function('twig_raw_filter'),
|
||||
new \Twig_SimpleFilter('passthrough', 'twig_raw_filter'),
|
||||
new \Twig_SimpleFilter('placeholder', 'twig_raw_filter'),
|
||||
new \Twig_SimpleFilter('without', 'twig_without'),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -53,8 +52,6 @@ class TwigExtension extends \Twig_Extension {
|
|||
|
||||
public function getTokenParsers() {
|
||||
return array(
|
||||
new TwigFunctionTokenParser('hide'),
|
||||
new TwigFunctionTokenParser('show'),
|
||||
new TwigTransTokenParser(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Template\TwigNodeExpressionNameReference
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* A class that defines a reference to the name for all nodes that represent
|
||||
* expressions.
|
||||
*
|
||||
* @see core\vendor\twig\twig\lib\Twig\Node\Expression\Name.php
|
||||
*/
|
||||
class TwigNodeExpressionNameReference extends \Twig_Node_Expression_Name {
|
||||
|
||||
/**
|
||||
* Overrides Twig_Node_Expression_Name::compile().
|
||||
*/
|
||||
public function compile(\Twig_Compiler $compiler) {
|
||||
$name = $this->getAttribute('name');
|
||||
$compiler
|
||||
->raw('$this->getContextReference($context, ')
|
||||
->string($name)
|
||||
->raw(')');
|
||||
}
|
||||
|
||||
}
|
|
@ -19,36 +19,9 @@ namespace Drupal\Core\Template;
|
|||
class TwigNodeVisitor implements \Twig_NodeVisitorInterface {
|
||||
|
||||
/**
|
||||
* TRUE when this node is a function getting arguments by reference.
|
||||
*
|
||||
* For example: 'hide' or 'render' are such functions.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isReference = FALSE;
|
||||
|
||||
/**
|
||||
* Implements Twig_NodeVisitorInterface::enterNode().
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
|
||||
if ($node instanceof \Twig_Node_Expression_Function) {
|
||||
$name = $node->getAttribute('name');
|
||||
$func = $env->getFunction($name);
|
||||
|
||||
// Optimization: Do not support nested functions.
|
||||
if ($this->isReference && $func instanceof \Twig_Function_Function) {
|
||||
$this->isReference = FALSE;
|
||||
}
|
||||
if ($func instanceof TwigReferenceFunction) {
|
||||
// We need to create a TwigReference
|
||||
$this->isReference = TRUE;
|
||||
}
|
||||
}
|
||||
if ($node instanceof \Twig_Node_Print) {
|
||||
// Our injected render_var needs arguments passed by reference -- in case of render array
|
||||
$this->isReference = TRUE;
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
|
@ -62,26 +35,14 @@ class TwigNodeVisitor implements \Twig_NodeVisitorInterface {
|
|||
*/
|
||||
function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
|
||||
if ($node instanceof \Twig_Node_Print) {
|
||||
$this->isReference = FALSE;
|
||||
|
||||
$class = get_class($node);
|
||||
$line = $node->getLine();
|
||||
return new $class(
|
||||
new \Twig_Node_Expression_Function('render_var', new \Twig_Node(array($node->getNode('expr'))), $node->getLine()),
|
||||
$node->getLine()
|
||||
new \Twig_Node_Expression_Function('render_var', new \Twig_Node(array($node->getNode('expr'))), $line),
|
||||
$line
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->isReference) {
|
||||
if ($node instanceof \Twig_Node_Expression_Name) {
|
||||
$name = $node->getAttribute('name');
|
||||
return new TwigNodeExpressionNameReference($name, $node->getLine());
|
||||
}
|
||||
elseif ($node instanceof \Twig_Function_Function) {
|
||||
// Do something!
|
||||
$this->isReference = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Template\TwigReference.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* A class used to pass variables by reference while they are used in twig.
|
||||
*
|
||||
* This is done by saving a reference to the original render array within a
|
||||
* TwigReference via the setReference() method like this:
|
||||
* @code
|
||||
* $obj = new TwigReference();
|
||||
* $obj->setReference($variable);
|
||||
* @endcode
|
||||
*
|
||||
* When a TwigReference is accessed via the offsetGet method the resulting
|
||||
* reference is again wrapped within a TwigReference. Therefore references to
|
||||
* render arrays within render arrays are also retained.
|
||||
*
|
||||
* To unwrap TwigReference objects the reference can be retrieved out of the
|
||||
* object by calling the getReference() method like this:
|
||||
* @code
|
||||
* $variable = &$obj->getReference();
|
||||
* @endcode
|
||||
* This allows render(), hide() and show() to access the original variable and
|
||||
* change it. The process of unwrapping and passing by reference to this
|
||||
* functions is done transparently by the TwigReferenceFunctions helper class.
|
||||
*
|
||||
* @see TwigReferenceFunction
|
||||
* @see TwigReferenceFunctions
|
||||
*/
|
||||
class TwigReference extends \ArrayObject {
|
||||
|
||||
/**
|
||||
* Holds an internal reference to the original array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $writableRef = array();
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\Core\Template\TwigReference object.
|
||||
*
|
||||
* The argument to the constructor is ignored as it is not safe that this will
|
||||
* always be a reference.
|
||||
*
|
||||
* To set a reference use:
|
||||
* @code
|
||||
* $obj = new TwigReference();
|
||||
* $obj->setReference($variable);
|
||||
* @endcode
|
||||
*
|
||||
* @param $array
|
||||
* The array parameter is ignored and not passed to the parent
|
||||
*/
|
||||
public function __construct($array = NULL) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a reference in the internal storage.
|
||||
*
|
||||
* @param $array
|
||||
* The array to set as internal reference.
|
||||
*/
|
||||
public function setReference(&$array) {
|
||||
$this->exchangeArray($array);
|
||||
$this->writableRef = &$array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the internal storage.
|
||||
*
|
||||
* Should be called like:
|
||||
* @code
|
||||
* $reference = &$obj->getReference();
|
||||
* @endcode
|
||||
*
|
||||
* @return
|
||||
* Returns the stored internal reference.
|
||||
*/
|
||||
public function &getReference() {
|
||||
return $this->writableRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets offset in internal reference and internal storage to value.
|
||||
*
|
||||
* This is just for completeness, but should never be used, because
|
||||
* twig cannot set properties and should not.
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
* @param mixed $offset
|
||||
* The offset to assign the value to.
|
||||
* @param mixed $value
|
||||
* The value to set.
|
||||
*/
|
||||
public function offsetSet($offset, $value) {
|
||||
$this->writableRef[$offset] = $value;
|
||||
parent::offsetSet($offset, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves offset from internal reference.
|
||||
*
|
||||
* In case of a render array, it is wrapped again within a TwigReference
|
||||
* object.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* The offset to retrieve.
|
||||
*
|
||||
* @return mixed
|
||||
* Returns a TwigReference object wrapping the array if the retrieved offset
|
||||
* is a complex array (i.e. not an attribute). Else it returns the retrived
|
||||
* offset directly.
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
if (!is_array($this->writableRef[$offset]) || $offset[0] == '#') {
|
||||
return $this->writableRef[$offset];
|
||||
}
|
||||
|
||||
// Wrap the returned array in a new TwigReference.
|
||||
$x = clone $this; // clone is faster than new
|
||||
$x->setReference($this->writableRef[$offset]);
|
||||
return $x;
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Template\TwigReferenceFunction.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* This class is used to create functions requiring references like 'hide'.
|
||||
*/
|
||||
class TwigReferenceFunction extends \Twig_Function_Function {
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Template\TwigReferenceFunctions.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* A helper used to unwrap TwigReference objects transparently.
|
||||
*
|
||||
* This is providing a static magic function that makes it easier to unwrap
|
||||
* TwigReference objects and pass variables by reference to show(), hide() and
|
||||
* render().
|
||||
*
|
||||
* The problem is that twig passes variables only by value. The following is a
|
||||
* simplified version of the generated code by twig when the property "links" of
|
||||
* a render array stored in $content should be hidden:
|
||||
* @code
|
||||
* $_content_ = $content;
|
||||
* hide(getAttribute($_content_, 'links'));
|
||||
* @endcode
|
||||
* As hide() is operating on a copy of the original array the hidden property
|
||||
* is not set on the original $content variable.
|
||||
*
|
||||
* TwigReferenceFunctions can be used in combination with TwigReference to solve
|
||||
* this problem:
|
||||
* @code
|
||||
* // Internally getContextReference returns the array wrapped in a
|
||||
* // TwigReference if certain criteria are met
|
||||
* function getContextReference(&$content) {
|
||||
* $obj = new Drupal\Core\Template\TwigReference();
|
||||
* $obj->setReference($content);
|
||||
* return $obj;
|
||||
* }
|
||||
*
|
||||
* // [...]
|
||||
* // Simplified, generated twig code
|
||||
* $_content_ = getContextReference($content);
|
||||
*
|
||||
* Drupal\Core\Template\TwigReferenceFunctions::hide(
|
||||
* getAttribute($_content_, 'links')
|
||||
* );
|
||||
* @endcode
|
||||
* A TwigReference object is passed to the __callStatic function of
|
||||
* TwigReferenceFunctions. The method unwraps the TwigReference and calls the
|
||||
* hide() method essentially with a reference to $content['links'].
|
||||
*
|
||||
* Therefore the hidden property is correctly set and a successive call to
|
||||
* render() will not render the content twice.
|
||||
*
|
||||
* @see TwigReference
|
||||
* @see TwigReferenceFunction
|
||||
* @see TwigFactory
|
||||
*
|
||||
*/
|
||||
class TwigReferenceFunctions {
|
||||
|
||||
/**
|
||||
* Magic function to call functions called from twig templates with a
|
||||
* reference to the original variable.
|
||||
*
|
||||
* This checks if the array provided by value is containing a reference to
|
||||
* the original version. If yes it replaces the argument with its reference.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the function to call.
|
||||
* @param $arguments
|
||||
* The arguments to process and pass to the called function.
|
||||
*
|
||||
* @return mixed
|
||||
* Returns the output of the called function.
|
||||
*
|
||||
* @see TwigReference
|
||||
*/
|
||||
public static function __callStatic($name, $arguments) {
|
||||
foreach ($arguments as $key => $val) {
|
||||
if (is_object($val) && $val instanceof TwigReference) {
|
||||
$arguments[$key] = &$val->getReference();
|
||||
}
|
||||
}
|
||||
|
||||
// Needed to pass by reference -- could also restrict to maximum one
|
||||
// argument instead
|
||||
$args = array();
|
||||
foreach ($arguments as $key => &$arg) {
|
||||
$args[$key] = &$arg;
|
||||
}
|
||||
|
||||
return call_user_func_array($name, $args);
|
||||
}
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\Template\TwigTemplate.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Template;
|
||||
|
||||
/**
|
||||
* This is the base class for compiled Twig templates.
|
||||
*/
|
||||
abstract class TwigTemplate extends \Twig_Template {
|
||||
|
||||
/**
|
||||
* A class used to pass variables by reference while they are used in Twig.
|
||||
*/
|
||||
protected $twig_reference = NULL;
|
||||
|
||||
/**
|
||||
* List of the name of variables to be passed around as references.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $is_reference = array();
|
||||
|
||||
/**
|
||||
* List of the name of variables to be passed around by value.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $is_no_reference = array();
|
||||
|
||||
/**
|
||||
* @param array $context
|
||||
* The variables available to the template.
|
||||
* @param $item
|
||||
* The name of the variable.
|
||||
* @return mixed
|
||||
* The requested variable.
|
||||
*/
|
||||
protected function getContextReference(&$context, $item) {
|
||||
// Optimized version. NULL is a valid value for $context[$item], we only
|
||||
// want to error if it hasn't been defined at all.
|
||||
if (!isset($context[$item]) && !array_key_exists($item, $context)) {
|
||||
// We don't want to throw an exception, but issue a warning instead.
|
||||
// This is the easiest way to do so.
|
||||
// @todo Decide based on prod vs. dev setting
|
||||
$msg = new \Twig_Error(t('@item could not be found in _context', array('@item' => $item)));
|
||||
trigger_error($msg->getMessage(), E_USER_WARNING);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Return item instead of its reference inside a loop.
|
||||
// @todo 'hide' and 'show' are not supported inside a loop for now.
|
||||
// This should be a non-issue as soon as this lands:
|
||||
// @see http://drupal.org/node/1922304
|
||||
if (isset($context['_seq'])) {
|
||||
return $context[$item];
|
||||
}
|
||||
|
||||
// The first test also finds empty / null render arrays
|
||||
if (!$context[$item] || isset($this->is_no_reference[$item])) {
|
||||
return $context[$item];
|
||||
}
|
||||
|
||||
if (isset($context['_references'][$item])) {
|
||||
return $context['_references'][$item];
|
||||
}
|
||||
|
||||
// @todo Check if this is a render array (existence of #theme?)
|
||||
if ((!isset($this->is_reference[$item])) && ($context[$item] instanceof \TwigMarkup || !is_array($context[$item]))) {
|
||||
$this->is_no_reference[$item] = TRUE;
|
||||
return $context[$item];
|
||||
}
|
||||
|
||||
if ($this->twig_reference == NULL) {
|
||||
$this->twig_reference = new TwigReference();
|
||||
}
|
||||
$ref = clone $this->twig_reference; // clone is _much_ faster than new
|
||||
$ref->setReference($context[$item]);
|
||||
|
||||
// Save that this is a reference
|
||||
$context['_references'][$item] = $ref;
|
||||
$this->is_reference[$item] = TRUE;
|
||||
|
||||
return $ref;
|
||||
}
|
||||
}
|
|
@ -13,10 +13,9 @@
|
|||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% hide(form.place_blocks) %}
|
||||
<div class="layout-block-list clearfix">
|
||||
<div class="layout-region block-list-primary">
|
||||
{{ form }}
|
||||
{{ form|without('place_blocks') }}
|
||||
</div>
|
||||
<div class="layout-region block-list-secondary">
|
||||
{{ form.place_blocks }}
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
* - author: Comment author. Can be a link or plain text.
|
||||
* - content: The content-related items for the comment display. Use
|
||||
* {{ content }} to print them all, or print a subset such as
|
||||
* {{ content.field_example }}. Use hide(content.field_example) to temporarily
|
||||
* suppress the printing of a given element.
|
||||
* {{ content.field_example }}. Use the following code to temporarily suppress
|
||||
* the printing of a given child element:
|
||||
* @code
|
||||
* {{ content|without('field_example') }}
|
||||
* @endcode
|
||||
* - created: Formatted date and time for when the comment was created.
|
||||
* Preprocess functions can reformat it by calling format_date() with the
|
||||
* desired parameters on the 'comment.created' variable.
|
||||
|
@ -92,9 +95,7 @@
|
|||
</footer>
|
||||
|
||||
<div{{ content_attributes }}>
|
||||
{# We hide the links now so that we can render them later. #}
|
||||
{% hide(content.links) %}
|
||||
{{ content }}
|
||||
{{ content|without('links') }}
|
||||
|
||||
{% if signature %}
|
||||
<div class="user-signature">
|
||||
|
|
|
@ -15,11 +15,9 @@
|
|||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% hide(form.advanced) %}
|
||||
{% hide(form.actions) %}
|
||||
<div class="layout-node-form clearfix">
|
||||
<div class="layout-region layout-region-node-main">
|
||||
{{ form }}
|
||||
{{ form|without('advanced', 'actions') }}
|
||||
</div>
|
||||
<div class="layout-region layout-region-node-secondary">
|
||||
{{ form.advanced }}
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
* - label: The title of the node.
|
||||
* - content: All node items. Use {{ content }} to print them all,
|
||||
* or print a subset such as {{ content.field_example }}. Use
|
||||
* {% hide(content.field_example) %} to temporarily suppress the printing
|
||||
* of a given element.
|
||||
* {{ content|without('field_example') %} to temporarily suppress the printing
|
||||
* of a given child element.
|
||||
* - user_picture: The node author's picture from user-picture.html.twig.
|
||||
* - date: Formatted creation date. Preprocess functions can reformat it by
|
||||
* calling format_date() with the desired parameters on
|
||||
|
@ -93,9 +93,7 @@
|
|||
{% endif %}
|
||||
|
||||
<div{{ content_attributes }}>
|
||||
{# We hide links now so that we can render them later. #}
|
||||
{% hide(content.links) %}
|
||||
{{ content }}
|
||||
{{ content|without('links') }}
|
||||
</div>
|
||||
|
||||
{{ content.links }}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\Theme\TwigFilterTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Theme;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Tests Drupal's Twig filters.
|
||||
*/
|
||||
class TwigFilterTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array(
|
||||
'twig_theme_test',
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Twig Filters',
|
||||
'description' => 'Test Drupal\'s Twig filters.',
|
||||
'group' => 'Theme',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Twig "without" filter.
|
||||
*/
|
||||
public function testTwigWithoutFilter() {
|
||||
$this->drupalGet('/twig-theme-test/filter');
|
||||
|
||||
$elements = array(
|
||||
array(
|
||||
'expected' => '<div><strong>No author:</strong> You can only find truth with logic if you have already found truth without it.1874-1936.</div>',
|
||||
'message' => '"No author" was successfully rendered.',
|
||||
),
|
||||
array(
|
||||
'expected' => '<div><strong>Complete quote after without:</strong> You can only find truth with logic if you have already found truth without it.Gilbert Keith Chesterton1874-1936.</div>',
|
||||
'message' => '"Complete quote after without" was successfully rendered.',
|
||||
),
|
||||
array(
|
||||
'expected' => '<div><strong>Only author:</strong> Gilbert Keith Chesterton.</div>',
|
||||
'message' => '"Only author:" was successfully rendered.',
|
||||
),
|
||||
array(
|
||||
'expected' => '<div><strong>No author or date:</strong> You can only find truth with logic if you have already found truth without it..</div>',
|
||||
'message' => '"No author or date" was successfully rendered.',
|
||||
),
|
||||
array(
|
||||
'expected' => '<div><strong>Only date:</strong> 1874-1936.</div>',
|
||||
'message' => '"Only date" was successfully rendered.',
|
||||
),
|
||||
array(
|
||||
'expected' => '<div><strong>Complete quote again for good measure:</strong> You can only find truth with logic if you have already found truth without it.Gilbert Keith Chesterton1874-1936.</div>',
|
||||
'message' => '"Complete quote again for good measure" was successfully rendered.',
|
||||
),
|
||||
array(
|
||||
'expected' => '<div><strong>Marked-up:</strong>
|
||||
<blockquote>
|
||||
<p>You can only find truth with logic if you have already found truth without it.</p>
|
||||
<footer>
|
||||
– <cite><a href="#">Gilbert Keith Chesterton</a> <em>(1874-1936)</em></cite>
|
||||
</footer>
|
||||
</blockquote>',
|
||||
'message' => '"Marked-up quote" was successfully rendered.',
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($elements as $element) {
|
||||
$this->assertRaw($element['expected'], $element['message']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\system\Tests\Theme\TwigReferenceObjectTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Theme;
|
||||
|
||||
/**
|
||||
* TwigReferenceObjectTest class.
|
||||
*/
|
||||
class TwigReferenceObjectTest {
|
||||
public function __construct($nid, $title) {
|
||||
$this->nid = $nid;
|
||||
$this->title = $title;
|
||||
}
|
||||
}
|
|
@ -1,147 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\system\Tests\Theme\TwigReferenceUnitTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\Theme;
|
||||
|
||||
use Drupal\simpletest\UnitTestBase;
|
||||
use Drupal\Core\Template\TwigReference;
|
||||
use Drupal\Core\Template\TwigReferenceFunctions;
|
||||
|
||||
/**
|
||||
* Unit tests for TwigReference class.
|
||||
*/
|
||||
class TwigReferenceUnitTest extends UnitTestBase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Theme Twig References',
|
||||
'description' => 'Tests TwigReference functions',
|
||||
'group' => 'Theme',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
$this->variables = array(
|
||||
'foo' => 'bar',
|
||||
'baz' => array(
|
||||
'foo' => '42',
|
||||
'bar' => '23',
|
||||
),
|
||||
'node' => new TwigReferenceObjectTest(
|
||||
42,
|
||||
'test node'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test function for TwigReference class
|
||||
*/
|
||||
function testTwigReference() {
|
||||
// Create a new TwigReference wrapper
|
||||
$wrapper = new TwigReference();
|
||||
$wrapper->setReference($this->variables);
|
||||
|
||||
// Check that strings are returned as strings
|
||||
$foo = $wrapper['foo'];
|
||||
$this->assertEqual($foo, $this->variables['foo'], 'String returned from TwigReference is the same');
|
||||
$this->assertTrue(is_string($foo), 'String returned from TwigReference is of type string');
|
||||
|
||||
// Check that arrays are wrapped again as TwigReference objects
|
||||
$baz = $wrapper['baz'];
|
||||
$this->assertTrue(is_object($baz), 'Array returned from TwigReference is of type object');
|
||||
$this->assertTrue($baz instanceof TwigReference, 'Array returned from TwigReference is instance of TwigReference');
|
||||
|
||||
// Check that getReference is giving back a reference to the original array
|
||||
|
||||
$ref = &$baz->getReference();
|
||||
$this->assertTrue(is_array($ref), 'getReference returns an array');
|
||||
|
||||
// Now modify $ref
|
||||
$ref['#hidden'] = TRUE;
|
||||
$this->assertEqual($ref['#hidden'], $this->variables['baz']['#hidden'], 'Property set on reference is passed to original array.');
|
||||
$this->assertEqual($ref['#hidden'], $baz['#hidden'], 'Property set on reference is passed to wrapper.');
|
||||
|
||||
// Now modify $baz
|
||||
$baz['hi'] = 'hello';
|
||||
|
||||
$this->assertEqual($baz['hi'], $this->variables['baz']['hi'], 'Property set on TwigReference object is passed to original array.');
|
||||
$this->assertEqual($baz['hi'], $ref['hi'], 'Property set on TwigReference object is passed to reference.');
|
||||
|
||||
// Check that an object is passed through directly
|
||||
$node = $wrapper['node'];
|
||||
$this->assertTrue(is_object($node), 'Object returned from TwigReference is of type object');
|
||||
$this->assertTrue($node instanceof TwigReferenceObjectTest, 'Object returned from TwigReference is instance of TwigReferenceObjectTest');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test function for TwigReferenceFunctions class
|
||||
*/
|
||||
function testTwigReferenceFunctions() {
|
||||
|
||||
// Create wrapper
|
||||
$content = &$this->variables;
|
||||
|
||||
// Use twig nomenclature
|
||||
$context['content'] = $content;
|
||||
|
||||
// Twig converts {{ hide(content.baz) }} to the following code
|
||||
|
||||
// This will have failed, because getAttribute returns a value and not a reference
|
||||
|
||||
try {
|
||||
if (isset($context["content"])) {
|
||||
$_content_ = $context["content"];
|
||||
}
|
||||
else {
|
||||
$_content_ = NULL;
|
||||
}
|
||||
TwigReferenceFunctions::hide($this->getAttribute($_content_, "baz"));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
// Catch the critical warning that a value was passed by reference
|
||||
}
|
||||
$this->assertFalse(isset($content['baz']['#printed']), 'baz is not hidden in content after hide() via value');
|
||||
|
||||
// Now lets do the same with some TwigReference magic!
|
||||
|
||||
$content_wrapper = new TwigReference();
|
||||
$content_wrapper->setReference($content);
|
||||
$context['content'] = $content_wrapper;
|
||||
|
||||
// Twig converts {{ hide(content.baz) }} to the following code
|
||||
|
||||
// This will succeed, because getAttribute returns a value, but it is an object
|
||||
|
||||
if (isset($context["content"])) {
|
||||
$_content_ = $context["content"];
|
||||
}
|
||||
else {
|
||||
$_content_ = NULL;
|
||||
}
|
||||
TwigReferenceFunctions::hide($this->getAttribute($_content_, "baz"));
|
||||
|
||||
$this->assertTrue(isset($content['baz']['#printed']), 'baz is hidden in content after hide() via TwigReference object');
|
||||
|
||||
$type = TwigReferenceFunctions::gettype($this->getAttribute($_content_, "baz"));
|
||||
$this->assertEqual($type, 'array', 'Type returned via TwigReferenceFunctions:: is an array.');
|
||||
|
||||
$type = gettype($this->getAttribute($_content_, "baz"));
|
||||
$this->assertEqual($type, 'object', 'Type returned without TwigReferenceFunctions:: is an object.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to somehow simulate Twigs getAttribute function
|
||||
*/
|
||||
public function getAttribute($array, $offset) {
|
||||
if (isset($array[$offset])) {
|
||||
return $array[$offset];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@
|
|||
namespace Drupal\twig_extension_test\TwigExtension;
|
||||
|
||||
use Drupal\Core\Template\TwigExtension;
|
||||
use Drupal\Core\Template\TwigReferenceFunction;
|
||||
|
||||
/**
|
||||
* A test Twig extension that adds a custom function and a custom filter.
|
||||
|
|
|
@ -28,4 +28,19 @@ class TwigThemeTestController {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu callback for filters in a Twig template.
|
||||
*/
|
||||
public function testFilterRender() {
|
||||
return array(
|
||||
'#theme' => 'twig_theme_test_filter',
|
||||
'#quote' => array(
|
||||
'content' => array('#markup' => 'You can only find truth with logic if you have already found truth without it.'),
|
||||
'author' => array('#markup' => 'Gilbert Keith Chesterton'),
|
||||
'date' => array('#markup' => '1874-1936'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<div><strong>No author:</strong> {{ quote|without('author') }}.</div>
|
||||
<div><strong>Complete quote after without:</strong> {{ quote }}.</div>
|
||||
<div><strong>Only author:</strong> {{ quote.author }}.</div>
|
||||
<div><strong>No author or date:</strong> {{ quote|without('date', 'author') }}.</div>
|
||||
<div><strong>Only date:</strong> {{ quote.date }}.</div>
|
||||
<div><strong>Complete quote again for good measure:</strong> {{ quote }}.</div>
|
||||
<div><strong>Marked-up:</strong>
|
||||
<blockquote>
|
||||
<p>{{ quote.content }}</p>
|
||||
<footer>
|
||||
– <cite><a href="#">{{ quote.author }}</a> <em>({{ quote.date }})</em></cite>
|
||||
</footer>
|
||||
</blockquote>
|
||||
</div>
|
|
@ -4,6 +4,10 @@
|
|||
* Implements hook_theme().
|
||||
*/
|
||||
function twig_theme_test_theme($existing, $type, $theme, $path) {
|
||||
$items['twig_theme_test_filter'] = array(
|
||||
'variables' => array('quote' => array()),
|
||||
'template' => 'twig_theme_test.filter',
|
||||
);
|
||||
$items['twig_theme_test_php_variables'] = array(
|
||||
'template' => 'twig_theme_test.php_variables',
|
||||
);
|
||||
|
|
|
@ -11,3 +11,10 @@ twig_theme_test.trans:
|
|||
_content: '\Drupal\twig_theme_test\TwigThemeTestController::transBlockRender'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
twig_theme_test.filter:
|
||||
path: '/twig-theme-test/filter'
|
||||
defaults:
|
||||
_content: '\Drupal\twig_theme_test\TwigThemeTestController::testFilterRender'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
* - name: Name of the current term.
|
||||
* - content: Items for the content of the term (fields and description).
|
||||
* Use 'content' to print them all, or print a subset such as
|
||||
* 'content.description'. Use the following code to temporarily suppress the
|
||||
* printing of a given element:
|
||||
* 'content.description'. Use the following code to exclude the
|
||||
* printing of a given child element:
|
||||
* @code
|
||||
* {% hide(content.description) %}
|
||||
* {{ content|without('description') }}
|
||||
* @endcode
|
||||
* - attributes: HTML attributes for the wrapper. The 'class' attribute
|
||||
* contains the following classes by default:
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
* - author: Comment author. Can be a link or plain text.
|
||||
* - content: The content-related items for the comment display. Use
|
||||
* {{ content }} to print them all, or print a subset such as
|
||||
* {{ content.field_example }}. Use hide(content.field_example) to temporarily
|
||||
* suppress the printing of a given element.
|
||||
* {{ content.field_example }}. Use the following code to temporarily suppress
|
||||
* the printing of a given child element:
|
||||
* @code
|
||||
* {{ content|without('field_example') }}
|
||||
* @endcode
|
||||
* - created: Formatted date and time for when the comment was created.
|
||||
* Preprocess functions can reformat it by calling format_date() with the
|
||||
* desired parameters on the 'comment.created' variable.
|
||||
|
@ -106,9 +109,7 @@
|
|||
{{ title_suffix }}
|
||||
|
||||
<div{{ content_attributes }}>
|
||||
{# We hide the links now so that we can render them later. #}
|
||||
{% hide(content.links) %}
|
||||
{{ content }}
|
||||
{{ content|without('links') }}
|
||||
</div> <!-- /.content -->
|
||||
|
||||
<footer class="comment-footer">
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
* - label: The title of the node.
|
||||
* - content: All node items. Use {{ content }} to print them all,
|
||||
* or print a subset such as {{ content.field_example }}. Use
|
||||
* {% hide(content.field_example) %} to temporarily suppress the printing
|
||||
* of a given element.
|
||||
* {{ content|without('field_example') }} to exclude the printing of a
|
||||
* given child element.
|
||||
* - user_picture: The node author's picture from user-picture.html.twig.
|
||||
* - date: Formatted creation date. Preprocess functions can reformat it by
|
||||
* calling format_date() with the desired parameters on
|
||||
|
@ -91,9 +91,7 @@
|
|||
</header>
|
||||
|
||||
<div class="content clearfix"{{ content_attributes }}>
|
||||
{# We hide links now so that we can render them later. #}
|
||||
{% hide(content.links) %}
|
||||
{{ content }}
|
||||
{{ content|without('links') }}
|
||||
</div>
|
||||
|
||||
{% if content.links %}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* @file
|
||||
* Handles integration of Twig templates with the Drupal theme system.
|
||||
*/
|
||||
use Drupal\Core\Template\TwigReference;
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
|
@ -119,10 +118,6 @@ function twig_render_template($template_file, $variables) {
|
|||
* @see TwigNodeVisitor
|
||||
*/
|
||||
function twig_render_var($arg) {
|
||||
if ($arg instanceof TwigReference) {
|
||||
$arg = &$arg->getReference();
|
||||
}
|
||||
|
||||
// Check for numeric zero.
|
||||
if ($arg === 0) {
|
||||
return 0;
|
||||
|
@ -154,27 +149,29 @@ function twig_render_var($arg) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wrapper around hide() that does not return the content.
|
||||
* Removes child elements from a copy of the original array.
|
||||
*
|
||||
* @see hide
|
||||
*/
|
||||
function twig_hide($element) {
|
||||
if ($element instanceof TwigReference) {
|
||||
$element = &$element->getReference();
|
||||
hide($element);
|
||||
}
|
||||
// @todo Add warning in else case
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around show() that does not return the content.
|
||||
* Creates a copy of the renderable array and removes child elements by key
|
||||
* specified throught filter's arguments. The copy can be printed without these
|
||||
* elements. The original renderable array is still available and can be used
|
||||
* to print child elements in their entirety in the twig template.
|
||||
*
|
||||
* @see show
|
||||
* @param array $element
|
||||
* The parent renderable array to exclude the child items.
|
||||
* @param string[] $args, ...
|
||||
* The string keys of $element to prevent printing.
|
||||
*
|
||||
* @return array
|
||||
* The filtered renderable array.
|
||||
*/
|
||||
function twig_show($element) {
|
||||
if ($element instanceof TwigReference) {
|
||||
$element = &$element->getReference();
|
||||
show($element);
|
||||
function twig_without($element) {
|
||||
$filtered_element = $element;
|
||||
$args = func_get_args();
|
||||
unset($args[0]);
|
||||
foreach ($args as $arg) {
|
||||
if (isset($filtered_element[$arg])) {
|
||||
unset($filtered_element[$arg]);
|
||||
}
|
||||
}
|
||||
// @todo Add warning in else case
|
||||
return $filtered_element;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue