TYPO3 API  SVNRelease
ViewHelperNode.php
Go to the documentation of this file.
00001 <?php
00002 
00003 /*                                                                        *
00004  * This script belongs to the FLOW3 package "Fluid".                      *
00005  *                                                                        *
00006  * It is free software; you can redistribute it and/or modify it under    *
00007  * the terms of the GNU Lesser General Public License as published by the *
00008  * Free Software Foundation, either version 3 of the License, or (at your *
00009  * option) any later version.                                             *
00010  *                                                                        *
00011  * This script is distributed in the hope that it will be useful, but     *
00012  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *
00013  * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser       *
00014  * General Public License for more details.                               *
00015  *                                                                        *
00016  * You should have received a copy of the GNU Lesser General Public       *
00017  * License along with the script.                                         *
00018  * If not, see http://www.gnu.org/licenses/lgpl.html                      *
00019  *                                                                        *
00020  * The TYPO3 project - inspiring people to share!                         *
00021  *                                                                        */
00022 
00023 /**
00024  * Node which will call a ViewHelper associated with this node.
00025  *
00026  * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
00027  */
00028 class Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode extends Tx_Fluid_Core_Parser_SyntaxTree_AbstractNode {
00029 
00030     /**
00031      * Class name of view helper
00032      * @var string
00033      */
00034     protected $viewHelperClassName;
00035 
00036     /**
00037      * Arguments of view helper - References to RootNodes.
00038      * @var array
00039      */
00040     protected $arguments = array();
00041 
00042     /**
00043      * The ViewHelper associated with this node
00044      * @var Tx_Fluid_Core_ViewHelper_AbstractViewHelper
00045      */
00046     protected $uninitializedViewHelper = NULL;
00047 
00048     /**
00049      * A mapping RenderingContext -> ViewHelper to only re-initialize ViewHelpers
00050      * when a context change occurs.
00051      * @var Tx_Extbase_Persistence_ObjectStorage
00052      */
00053     protected $viewHelpersByContext = NULL;
00054 
00055     /**
00056      * List of comparators which are supported in the boolean expression language.
00057      *
00058      * Make sure that if one string is contained in one another, the longer
00059      * string is listed BEFORE the shorter one.
00060      * Example: put ">=" before ">"
00061      * @var array of comparators
00062      */
00063     static protected $comparators = array('==', '!=', '%', '>=', '>', '<=', '<');
00064 
00065     /**
00066      * A regular expression which checks the text nodes of a boolean expression.
00067      * Used to define how the regular expression language should look like.
00068      * @var string Regular expression
00069      */
00070     static protected $booleanExpressionTextNodeCheckerRegularExpression = '/
00071         ^                 # Start with first input symbol
00072         (?:               # start repeat
00073             COMPARATORS   # We allow all comparators
00074             |\s*          # Arbitary spaces
00075             |-?           # Numbers, possibly with the "minus" symbol in front.
00076                 [0-9]+    # some digits
00077                 (?:       # and optionally a dot, followed by some more digits
00078                     \\.
00079                     [0-9]+
00080                 )?
00081         )*
00082         $/x';
00083 
00084     /**
00085      * Constructor.
00086      *
00087      * @param Tx_Fluid_Core_ViewHelper_AbstractViewHelper $viewHelper The view helper
00088      * @param array $arguments Arguments of view helper - each value is a RootNode.
00089      * @author Sebastian Kurfürst <sebastian@typo3.org>
00090      * @author Karsten Dambekalns <karsten@typo3.org>
00091      */
00092     public function __construct(Tx_Fluid_Core_ViewHelper_AbstractViewHelper $viewHelper, array $arguments) {
00093         $this->uninitializedViewHelper = $viewHelper;
00094         $this->viewHelpersByContext = t3lib_div::makeInstance('Tx_Extbase_Persistence_ObjectStorage');
00095         $this->arguments = $arguments;
00096 
00097         if (FALSE /*FIXME*/) {
00098             $this->viewHelperClassName = $this->uninitializedViewHelper->FLOW3_AOP_Proxy_getProxyTargetClassName();
00099         } else {
00100             $this->viewHelperClassName = get_class($this->uninitializedViewHelper);
00101         }
00102     }
00103 
00104     /**
00105      * Returns the attached (but still uninitialized) ViewHelper for this ViewHelperNode.
00106      * We need this method because sometimes Interceptors need to ask some information from the ViewHelper.
00107      *
00108      * @return Tx_Fluid_Core_ViewHelper_AbstractViewHelper the attached ViewHelper, if it is initialized
00109      */
00110     public function getUninitializedViewHelper() {
00111         return $this->uninitializedViewHelper;
00112     }
00113 
00114     /**
00115      * Get class name of view helper
00116      *
00117      * @return string Class Name of associated view helper
00118      * @author Sebastian Kurfürst <sebastian@typo3.org>
00119      */
00120     public function getViewHelperClassName() {
00121         return $this->viewHelperClassName;
00122     }
00123 
00124     /**
00125      * Call the view helper associated with this object.
00126      *
00127      * First, it evaluates the arguments of the view helper.
00128      *
00129      * If the view helper implements Tx_Fluid_Core_ViewHelper_Facets_ChildNodeAccessInterface,
00130      * it calls setChildNodes(array childNodes) on the view helper.
00131      *
00132      * Afterwards, checks that the view helper did not leave a variable lying around.
00133      *
00134      * @param Tx_Fluid_Core_Rendering_RenderingContextInterface $renderingContext
00135      * @return object evaluated node after the view helper has been called.
00136      * @author Sebastian Kurfürst <sebastian@typo3.org>
00137      * @author Karsten Dambekalns <karsten@typo3.org>
00138      * @todo check recreation of viewhelper when revisiting caching
00139      */
00140     public function evaluate(Tx_Fluid_Core_Rendering_RenderingContextInterface $renderingContext) {
00141         $objectManager = $renderingContext->getObjectManager();
00142         $contextVariables = $renderingContext->getTemplateVariableContainer()->getAllIdentifiers();
00143 
00144         if ($this->viewHelpersByContext->contains($renderingContext)) {
00145             $viewHelper = $this->viewHelpersByContext[$renderingContext];
00146         } else {
00147             $viewHelper = clone $this->uninitializedViewHelper;
00148             $this->viewHelpersByContext->attach($renderingContext, $viewHelper);
00149         }
00150 
00151         $evaluatedArguments = array();
00152         $renderMethodParameters = array();
00153         if (count($viewHelper->prepareArguments())) {
00154             foreach ($viewHelper->prepareArguments() as $argumentName => $argumentDefinition) {
00155                 if (isset($this->arguments[$argumentName])) {
00156                     $argumentValue = $this->arguments[$argumentName];
00157                     $evaluatedArguments[$argumentName] = $this->convertArgumentValue($argumentValue, $argumentDefinition->getType(), $renderingContext);
00158                 } else {
00159                     $evaluatedArguments[$argumentName] = $argumentDefinition->getDefaultValue();
00160                 }
00161                 if ($argumentDefinition->isMethodParameter()) {
00162                     $renderMethodParameters[$argumentName] = $evaluatedArguments[$argumentName];
00163                 }
00164             }
00165         }
00166 
00167         $viewHelperArguments = $objectManager->create('Tx_Fluid_Core_ViewHelper_Arguments', $evaluatedArguments);
00168         $viewHelper->setArguments($viewHelperArguments);
00169         $viewHelper->setTemplateVariableContainer($renderingContext->getTemplateVariableContainer());
00170         if ($renderingContext->getControllerContext() !== NULL) {
00171             $viewHelper->setControllerContext($renderingContext->getControllerContext());
00172         }
00173         $viewHelper->setViewHelperVariableContainer($renderingContext->getViewHelperVariableContainer());
00174         $viewHelper->setViewHelperNode($this);
00175         $viewHelper->setRenderingContext($renderingContext);
00176 
00177         if ($viewHelper instanceof Tx_Fluid_Core_ViewHelper_Facets_ChildNodeAccessInterface) {
00178             $viewHelper->setChildNodes($this->childNodes);
00179         }
00180 
00181         $output = $viewHelper->initializeArgumentsAndRender($renderMethodParameters);
00182 
00183         return $output;
00184     }
00185 
00186     /**
00187      * Convert argument strings to their equivalents. Needed to handle strings with a boolean meaning.
00188      *
00189      * @param Tx_Fluid_Core_Parser_SyntaxTree_AbstractNode $syntaxTreeNode Value to be converted
00190      * @param string $type Target type
00191      * @return mixed New value
00192      * @author Sebastian Kurfürst <sebastian@typo3.org>
00193      * @author Bastian Waidelich <bastian@typo3.org>
00194      */
00195     protected function convertArgumentValue(Tx_Fluid_Core_Parser_SyntaxTree_AbstractNode $syntaxTreeNode, $type, Tx_Fluid_Core_Rendering_RenderingContextInterface $renderingContext) {
00196         if ($type === 'boolean') {
00197             return $this->evaluateBooleanExpression($syntaxTreeNode, $renderingContext);
00198         }
00199         return $syntaxTreeNode->evaluate($renderingContext);
00200     }
00201 
00202     /**
00203      * Convert boolean expression syntax tree to some meaningful value.
00204      * The expression is available as the SyntaxTree of the argument.
00205      *
00206      * We currently only support expressions of the form:
00207      * XX Comparator YY
00208      * Where XX and YY can be either:
00209      * - a number
00210      * - an Object accessor
00211      * - an array
00212      * - a ViewHelper
00213      *
00214      * and comparator must be one of the above.
00215      *
00216      * In case no comparator is found, the fallback of "convertToBoolean" is used.
00217      *
00218      *
00219      * Internal work:
00220      * First, we loop through the child syntaxtree nodes, to fill the left side of the comparator,
00221      * the right side of the comparator, and the comparator itself.
00222      * Then, we evaluate the obtained left and right side using the given comparator. This is done inside the evaluateComparator method.
00223      *
00224      * @param Tx_Fluid_Core_Parser_SyntaxTree_AbstractNode $syntaxTreeNode Value to be converted
00225      * @param Tx_Fluid_Core_Rendering_RenderingContextInterface $renderingContext
00226      * @return boolean Evaluated value
00227      * @throws Tx_Fluid_Core_Parser_Exception
00228      * @author Sebastian Kurfürst <sebastian@typo3.org>
00229      */
00230     protected function evaluateBooleanExpression(Tx_Fluid_Core_Parser_SyntaxTree_AbstractNode $syntaxTreeNode, Tx_Fluid_Core_Rendering_RenderingContextInterface $renderingContext) {
00231         $childNodes = $syntaxTreeNode->getChildNodes();
00232         if (count($childNodes) > 3) {
00233             throw new Tx_Fluid_Core_Parser_Exception('The expression "' . $syntaxTreeNode->evaluate($renderingContext) . '" has more than tree parts.', 1244201848);
00234         }
00235 
00236         $leftSide = NULL;
00237         $rightSide = NULL;
00238         $comparator = NULL;
00239         foreach ($childNodes as $childNode) {
00240             if ($childNode instanceof Tx_Fluid_Core_Parser_SyntaxTree_TextNode && !preg_match(str_replace('COMPARATORS', implode('|', self::$comparators), self::$booleanExpressionTextNodeCheckerRegularExpression), $childNode->evaluate($renderingContext))) {
00241                 $comparator = NULL;
00242                     // skip loop and fall back to classical to boolean conversion.
00243                 break;
00244             }
00245 
00246             if ($comparator !== NULL) {
00247                     // comparator already set, we are evaluating the right side of the comparator
00248                 if ($rightSide === NULL) {
00249                     $rightSide = $childNode->evaluate($renderingContext);
00250                 } else {
00251                     $rightSide .= $childNode->evaluate($renderingContext);
00252                 }
00253             } elseif ($childNode instanceof Tx_Fluid_Core_Parser_SyntaxTree_TextNode
00254                 && ($comparator = $this->getComparatorFromString($childNode->evaluate($renderingContext)))) {
00255                     // comparator in current string segment
00256                 $explodedString = explode($comparator, $childNode->evaluate($renderingContext));
00257                 if (isset($explodedString[0]) && trim($explodedString[0]) !== '') {
00258                     $leftSide .= trim($explodedString[0]);
00259                 }
00260                 if (isset($explodedString[1]) && trim($explodedString[1]) !== '') {
00261                     $rightSide .= trim($explodedString[1]);
00262                 }
00263             } else {
00264                     // comparator not found yet, on the left side of the comparator
00265                 if ($leftSide === NULL) {
00266                     $leftSide = $childNode->evaluate($renderingContext);
00267                 } else {
00268                     $leftSide .= $childNode->evaluate($renderingContext);
00269                 }
00270             }
00271         }
00272 
00273         if ($comparator !== NULL) {
00274             return $this->evaluateComparator($comparator, $leftSide, $rightSide);
00275         } else {
00276             return $this->convertToBoolean($syntaxTreeNode->evaluate($renderingContext));
00277         }
00278     }
00279 
00280     /**
00281      * Do the actual comparison. Compares $leftSide and $rightSide with $comparator and emits a boolean value
00282      *
00283      * @param string $comparator One of self::$comparators
00284      * @param mixed $leftSide Left side to compare
00285      * @param mixed $rightSide Right side to compare
00286      * @return boolean TRUE if comparison of left and right side using the comparator emit TRUE, false otherwise
00287      * @throws Tx_Fluid_Core_Parser_Exception
00288      * @author Sebastian Kurfürst <sebastian@typo3.org>
00289      */
00290     protected function evaluateComparator($comparator, $leftSide, $rightSide) {
00291         switch ($comparator) {
00292             case '==':
00293                 if (is_object($leftSide) && is_object($rightSide)) {
00294                     return ($leftSide === $rightSide);
00295                 }
00296                 return ($leftSide == $rightSide);
00297                 break;
00298             case '!=':
00299                 if (is_object($leftSide) && is_object($rightSide)) {
00300                     return ($leftSide !== $rightSide);
00301                 }
00302                 return ($leftSide != $rightSide);
00303                 break;
00304             case '%':
00305                 return (boolean)((int)$leftSide % (int)$rightSide);
00306             case '>':
00307                 return ($leftSide > $rightSide);
00308             case '>=':
00309                 return ($leftSide >= $rightSide);
00310             case '<':
00311                 return ($leftSide < $rightSide);
00312             case '<=':
00313                 return ($leftSide <= $rightSide);
00314             default:
00315                 throw new Tx_Fluid_Core_Parser_Exception('Comparator "' . $comparator . '" is not implemented.', 1244234398);
00316         }
00317     }
00318 
00319     /**
00320      * Determine if there is a comparator inside $string, and if yes, returns it.
00321      *
00322      * @param string $string string to check for a comparator inside
00323      * @return string The comparator or NULL if none found.
00324      * @author Sebastian Kurfürst <sebastian@typo3.org>
00325      * @author Bastian Waidelich <bastian@typo3.org>
00326      */
00327     protected function getComparatorFromString($string) {
00328         foreach (self::$comparators as $comparator) {
00329             if (strpos($string, $comparator) !== FALSE) {
00330                 return $comparator;
00331             }
00332         }
00333 
00334         return NULL;
00335     }
00336 
00337     /**
00338      * Convert argument strings to their equivalents. Needed to handle strings with a boolean meaning.
00339      *
00340      * @param mixed $value Value to be converted to boolean
00341      * @return boolean
00342      * @author Bastian Waidelich <bastian@typo3.org>
00343      * @todo this should be moved to another class
00344      */
00345     protected function convertToBoolean($value) {
00346         if (is_bool($value)) {
00347             return $value;
00348         }
00349         if (is_numeric($value)) {
00350             return $value > 0;
00351         }
00352         if (is_string($value)) {
00353             return (!empty($value) && strtolower($value) !== 'false');
00354         }
00355         if (is_array($value) || (is_object($value) && $value instanceof Countable)) {
00356             return count($value) > 0;
00357         }
00358         if (is_object($value)) {
00359             return TRUE;
00360         }
00361         return FALSE;
00362     }
00363 
00364     /**
00365      * Clean up for serializing.
00366      *
00367      * @return array
00368      * @author Sebastian Kurfürst <sebastian@typo3.org>
00369      */
00370     public function __sleep() {
00371         return array('viewHelperClassName', 'arguments', 'childNodes');
00372     }
00373 }
00374 
00375 ?>