diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 898fdf8..a9ecec7 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -23,9 +23,11 @@ public function getFunctions() { // @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'), + 'hide' => new TwigReferenceFunction('twig_hide', array('needs_context' => TRUE)), +// 'change' => new TwigReferenceFunction('twig_change', array('needs_context' => TRUE)), + + 'render_var' => new TwigReferenceFunction('twig_render_var', array('needs_context' => TRUE)), + 'show' => new TwigReferenceFunction('twig_show', array('needs_context' => TRUE)), ); } @@ -39,7 +41,7 @@ public function getNodeVisitors() { // The node visitor is needed to wrap all variables with // render_var -> twig_render_var() function. return array( - new TwigNodeVisitor(), + new TwigNodeVisitor(), new TwigReferenceNodeVisitor() ); } diff --git a/core/lib/Drupal/Core/Template/TwigNodeExpressionGetAttrReference.php b/core/lib/Drupal/Core/Template/TwigNodeExpressionGetAttrReference.php new file mode 100644 index 0000000..b96c625 --- /dev/null +++ b/core/lib/Drupal/Core/Template/TwigNodeExpressionGetAttrReference.php @@ -0,0 +1,27 @@ +raw('array( "attribute", '); + $compiler->subcompile($this->getNode('attribute')); + + if (count($this->getNode('arguments')) || \Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + + if (\Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { + $compiler->raw(', ')->repr($this->getAttribute('type')); + } + + if ($this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) { + $compiler->raw(', '.($this->getAttribute('is_defined_test') ? 'true' : 'false')); + } + + if ($this->getAttribute('ignore_strict_check')) { + $compiler->raw(', '.($this->getAttribute('ignore_strict_check') ? 'true' : 'false')); + } + } + $compiler->raw(')'); + } +} diff --git a/core/lib/Drupal/Core/Template/TwigNodeExpressionNameReference.php b/core/lib/Drupal/Core/Template/TwigNodeExpressionNameReference.php index c66d73f..0273e60 100644 --- a/core/lib/Drupal/Core/Template/TwigNodeExpressionNameReference.php +++ b/core/lib/Drupal/Core/Template/TwigNodeExpressionNameReference.php @@ -20,10 +20,10 @@ class TwigNodeExpressionNameReference extends \Twig_Node_Expression_Name { */ public function compile(\Twig_Compiler $compiler) { $name = $this->getAttribute('name'); + $compiler->raw('$this, array("#twig_reference" => TRUE, "path" => array('); $compiler - ->raw('$this->getContextReference($context, ') + ->raw('array("name", ') ->string($name) ->raw(')'); } - } diff --git a/core/lib/Drupal/Core/Template/TwigNodeExpressionNameReferenceEnd.php b/core/lib/Drupal/Core/Template/TwigNodeExpressionNameReferenceEnd.php new file mode 100644 index 0000000..2aabe54 --- /dev/null +++ b/core/lib/Drupal/Core/Template/TwigNodeExpressionNameReferenceEnd.php @@ -0,0 +1,9 @@ +raw('))'); + } +} \ No newline at end of file diff --git a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php index 710faa0..2af494a 100644 --- a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php +++ b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php @@ -31,23 +31,23 @@ class TwigNodeVisitor implements \Twig_NodeVisitorInterface { * Implements Twig_NodeVisitorInterface::enterNode(). */ 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; - } +// 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,7 +62,7 @@ function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env) { */ function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) { if ($node instanceof \Twig_Node_Print) { - $this->isReference = FALSE; +// $this->isReference = FALSE; $class = get_class($node); return new $class( @@ -71,16 +71,16 @@ function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) { ); } - 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; - } - } +// 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; } @@ -90,6 +90,6 @@ function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) { */ function getPriority() { // We want to run before other NodeVisitors like Escape or Optimizer - return -1; + return -2; } } diff --git a/core/lib/Drupal/Core/Template/TwigReferenceNodeVisitor.php b/core/lib/Drupal/Core/Template/TwigReferenceNodeVisitor.php new file mode 100644 index 0000000..50f7874 --- /dev/null +++ b/core/lib/Drupal/Core/Template/TwigReferenceNodeVisitor.php @@ -0,0 +1,140 @@ +hasAttribute('expression_path')) { + array_unshift($this->isAttributePath, $node->getAttribute('expression_path') == 'attribute'?1:0); + } + + // Optimization: Do not support nested values. + $validPath = count($this->isAttributePath) > 0 ? array_sum($this->isAttributePath) === 0 : TRUE; + +// if ($this->isReference && $validPath && !($node instanceof \Twig_Node_Expression_GetAttr || $node instanceof \Twig_Node_Expression_Name || $node instanceof \Twig_Node_Expression_Constant || $node instanceof \Twig_Node_Expression_Array)) { +// $this->isReference = FALSE; +// } + + if ($node instanceof \Twig_Node_Expression_Function) { + $name = $node->getAttribute('name'); + $func = $env->getFunction($name); + + if ($func instanceof TwigReferenceFunction) { + // We need to create a TwigReference + $this->isReference = TRUE; + $node->setAttribute('twig_reference', TRUE); + array_unshift($this->dataAttributes, array()); + } + } +// if ($node instanceof \Twig_Node_Print) { +// // Our injected render_var needs arguments passed by reference -- in case of render array +// $this->isReference = TRUE; +// $node->setAttribute('twig_reference', TRUE); +// array_unshift($this->dataAttributes, array()); +// } + if ($node instanceof \Twig_Node_Expression_GetAttr) { + $nodeI = $node->getNode('node'); + $nodeI->setAttribute('expression_path', 'node'); + $attribute = $node->getNode('attribute'); + $attribute->setAttribute('expression_path', 'attribute'); +// $node->setAttribute('count', $this->attrCounter); +// if ($validPath) { +// $this->attrCounter++; +// } + } + + return $node; + } + + /** + * Implements Twig_NodeVisitorInterface::leaveNode(). + * + * We use this to inject a call to render_var -> twig_render_var() + * before anything is printed. + * + * @see twig_render + */ + function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) { + + if ($this->isReference && $node->hasAttribute('twig_reference')) { + $nodes = array_shift($this->dataAttributes); + if (count($nodes) > 0) { + $nodes[] = new TwigNodeExpressionNameReferenceEnd(); + $node->setNode('arguments', new \Twig_Node($nodes)); + } + $this->isReference = FALSE; + } + +// if ($node instanceof \Twig_Node_Print) { +// $this->isReference = FALSE; +// +// $class = get_class($node); +// return new $class( +// new \Twig_Node_Expression_Function('render', new \Twig_Node(array($node->getNode('expr'))), $node->getLine()), +// $node->getLine() +// ); +// } + $validPath = count($this->isAttributePath) > 0 ? array_sum($this->isAttributePath) === 0 : TRUE; + + if ($this->isReference && $validPath) { + if ($node instanceof \Twig_Node_Expression_Name) { + $name = $node->getAttribute('name'); + $newNode = new TwigNodeExpressionNameReference($name, $node->getLine()); + array_push($this->dataAttributes[0], $newNode); + return $node; + } + elseif ($node instanceof \Twig_Node_Expression_GetAttr) { + $nodeI = $node->getNode('node'); + $attribute = $node->getNode('attribute'); + $arguments = $node->getNode('arguments'); + $type = $node->getAttribute('type'); + + $newNode = new TwigNodeExpressionGetAttrReference($nodeI, $attribute, $arguments, $type, $node->getLine()); +// $newNode->setAttribute('count', $node->getAttribute('count')); +// $this->attrCounter--; + array_push($this->dataAttributes[0], $newNode); + return $node; + } + } + +// if ($node instanceof \Twig_Function_Function) { +// // Do something! +// $this->isReference = FALSE; +// } + + if ($node->hasAttribute('expression_path')) { + array_shift($this->isAttributePath); + } + +// if ($node instanceof \Twig_Node_Expression_GetAttr && $validPath) { +// $this->attrCounter--; +// } + + return $node; + } + + /** + * Implements Twig_NodeVisitorInterface::getPriority(). + */ + function getPriority() { + // This should come after TwigNodeVisitor. + return -1; + } +} diff --git a/core/lib/Drupal/Core/Template/TwigTemplate.php b/core/lib/Drupal/Core/Template/TwigTemplate.php index cb2d91d..ab7c733 100644 --- a/core/lib/Drupal/Core/Template/TwigTemplate.php +++ b/core/lib/Drupal/Core/Template/TwigTemplate.php @@ -87,4 +87,23 @@ return $ref; } + + public function &unwrapReference(&$context, $val) { + $path = $val['path']; + $return = &$context; + + foreach ($val['path'] as $p) { + $name = $p[1]; + if ($p[0] == 'name' && $name == '_context') { + continue; + } + if (is_array($return) && isset($return[$name])) { + $return = &$return[$name]; + } else { + $return = $this->getAttribute($return, $name, isset($p[2])?$p[2]:array(), isset($p[3])?$p[3]:'any'); // @todo Make tests work as well ... + } + } + + return $return; + } } diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine index 1ba60a5..c0287a3 100644 --- a/core/themes/engines/twig/twig.engine +++ b/core/themes/engines/twig/twig.engine @@ -93,39 +93,39 @@ function twig_render_template($template_file, $variables) { * @see render * @see TwigNodeVisitor */ -function twig_render_var($arg) { - if ($arg instanceof TwigReference) { - $arg = &$arg->getReference(); +function twig_render_var(&$context, $template, $val) { + if (isset($val['#twig_reference']) && $val['#twig_reference'] === TRUE) { + $val = &$template->unwrapReference($context, $val); } // Check for numeric zero. - if ($arg === 0) { + if ($val === 0) { return 0; } // == is true also for empty arrays - if ($arg == NULL) { + if ($val == NULL) { return NULL; } // Keep Twig_Markup objects intact to prepare for later autoescaping support - if ($arg instanceOf Twig_Markup) { - return $arg; + if ($val instanceOf Twig_Markup) { + return $val; } - if (is_scalar($arg)) { - return $arg; + if (is_scalar($val)) { + return $val; } - if (is_object($arg)) { - if (method_exists($arg, '__toString')) { - return (string) $arg; + if (is_object($val)) { + if (method_exists($val, '__toString')) { + return (string) $val; } throw new Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg)))); } // This is a normal render array. - return render($arg); + return render($val); } /** @@ -133,11 +133,12 @@ function twig_render_var($arg) { * * @see hide */ -function twig_hide($element) { - if ($element instanceof TwigReference) { - $element = &$element->getReference(); - hide($element); - } +function twig_hide(&$context, $template, $val) { + // Unwrap reference if twig_reference key is set + if (isset($val['#twig_reference']) && $val['#twig_reference'] === TRUE) { + $val = &$template->unwrapReference($context, $val); + } + hide($val); // @todo Add warning in else case } @@ -146,10 +147,11 @@ function twig_hide($element) { * * @see show */ -function twig_show($element) { - if ($element instanceof TwigReference) { - $element = &$element->getReference(); - show($element); +function twig_show(&$context, $template, $val) { + if (isset($val['#twig_reference']) && $val['#twig_reference'] === TRUE) { + $val = &$template->unwrapReference($context, $val); } + + show($element); // @todo Add warning in else case }