[+BUGFIX] (Core): Condition improvements
authorSebastian Kurfuerst <sebastian@typo3.org>
Sun, 19 Jun 2011 08:18:49 +0000 (10:18 +0200)
committerSebastian Kurfuerst <sebastian@typo3.org>
Mon, 20 Jun 2011 08:17:10 +0000 (10:17 +0200)
* now all complex comparators are fully specified, and behave according to
  the specification
* ConditionViewHelper now behaves predictably in all cases of then/else and
  inline notation.

Original commit: a0966281fbaa8781e6f7430caba39cd4a47315b7
Resolves: #27557

Change-Id: Ib0affd738b2c0ce1f10f24ea589c0ffad949406d

typo3/sysext/fluid/Classes/Core/Parser/SyntaxTree/ViewHelperNode.php
typo3/sysext/fluid/Classes/Core/ViewHelper/AbstractConditionViewHelper.php

index 08b8d8a..8f306c1 100644 (file)
@@ -58,14 +58,14 @@ class Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode extends Tx_Fluid_Core_Parse
         * Make sure that if one string is contained in one another, the longer
         * string is listed BEFORE the shorter one.
         * Example: put ">=" before ">"
-        * @var array of comparators
+        * @var array
         */
        static protected $comparators = array('==', '!=', '%', '>=', '>', '<=', '<');
 
        /**
         * A regular expression which checks the text nodes of a boolean expression.
         * Used to define how the regular expression language should look like.
-        * @var string Regular expression
+        * @var string
         */
        static protected $booleanExpressionTextNodeCheckerRegularExpression = '/
                ^                 # Start with first input symbol
@@ -227,6 +227,10 @@ class Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode extends Tx_Fluid_Core_Parse
                $childNodes = $syntaxTreeNode->getChildNodes();
                if (count($childNodes) > 3) {
                        throw new Tx_Fluid_Core_Parser_Exception('The expression "' . $syntaxTreeNode->evaluate($renderingContext) . '" has more than tree parts.', 1244201848);
+               } elseif (count($childNodes) === 0) {
+                               // In this case, we do not have child nodes; i.e. the current SyntaxTreeNode
+                               // is a text node with a literal comparison like "1 == 1"
+                       $childNodes = array($syntaxTreeNode);
                }
 
                $leftSide = NULL;
@@ -274,7 +278,16 @@ class Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode extends Tx_Fluid_Core_Parse
        }
 
        /**
-        * Do the actual comparison. Compares $leftSide and $rightSide with $comparator and emits a boolean value
+        * Do the actual comparison. Compares $leftSide and $rightSide with $comparator and emits a boolean value.
+        *
+        * Some special rules apply:
+        * - The == and != operators are comparing the Object Identity using === and !==, when one of the two
+        *   operands are objects.
+        * - For arithmetic comparisons (%, >, >=, <, <=), some special rules apply:
+        *   - arrays are only comparable with arrays, else the comparison yields FALSE
+        *   - objects are only comparable with objects, else the comparison yields FALSE
+        *   - the comparison is FALSE when two types are not comparable according to the table
+        *     "Comparison with various types" on http://php.net/manual/en/language.operators.comparison.php
         *
         * @param string $comparator One of self::$comparators
         * @param mixed $leftSide Left side to compare
@@ -286,26 +299,33 @@ class Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode extends Tx_Fluid_Core_Parse
        protected function evaluateComparator($comparator, $leftSide, $rightSide) {
                switch ($comparator) {
                        case '==':
-                               if (is_object($leftSide) && is_object($rightSide)) {
+                               if (is_object($leftSide) || is_object($rightSide)) {
                                        return ($leftSide === $rightSide);
+                               } else {
+                                       return ($leftSide == $rightSide);
                                }
-                               return ($leftSide == $rightSide);
                                break;
                        case '!=':
-                               if (is_object($leftSide) && is_object($rightSide)) {
+                               if (is_object($leftSide) || is_object($rightSide)) {
                                        return ($leftSide !== $rightSide);
+                               } else {
+                                       return ($leftSide != $rightSide);
                                }
-                               return ($leftSide != $rightSide);
                                break;
                        case '%':
+                               if (!$this->isComparable($leftSide, $rightSide)) return FALSE;
                                return (boolean)((int)$leftSide % (int)$rightSide);
                        case '>':
+                               if (!$this->isComparable($leftSide, $rightSide)) return FALSE;
                                return ($leftSide > $rightSide);
                        case '>=':
+                               if (!$this->isComparable($leftSide, $rightSide)) return FALSE;
                                return ($leftSide >= $rightSide);
                        case '<':
+                               if (!$this->isComparable($leftSide, $rightSide)) return FALSE;
                                return ($leftSide < $rightSide);
                        case '<=':
+                               if (!$this->isComparable($leftSide, $rightSide)) return FALSE;
                                return ($leftSide <= $rightSide);
                        default:
                                throw new Tx_Fluid_Core_Parser_Exception('Comparator "' . $comparator . '" is not implemented.', 1244234398);
@@ -313,6 +333,29 @@ class Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode extends Tx_Fluid_Core_Parse
        }
 
        /**
+        * Checks whether two operands are comparable (based on their types). This implements
+        * the "Comparison with various types" table from http://php.net/manual/en/language.operators.comparison.php,
+        * only leaving out "array" with "anything" and "object" with anything; as we specify
+        * that arrays and objects are incomparable with anything else than their type.
+        *
+        * @param mixed $operand1 the first operand
+        * @param mixed $operand2 the second operand
+        * @return boolean TRUE if the operands can be compared using arithmetic operators, FALSE otherwise.
+        */
+       protected function isComparable($operand1, $operand2) {
+               if ((is_null($operand1) || is_string($operand1))
+                       && is_string($operand2)) return TRUE;
+               if (is_bool($operand1) || is_null($operand1)) return TRUE;
+               if (is_object($operand1)
+                       && is_object($operand2)) return TRUE;
+               if ((is_string($operand1) || is_resource($operand1) || is_numeric($operand1))
+                       && (is_string($operand2) || is_resource($operand2) || is_numeric($operand2))) return TRUE;
+               if (is_array($operand1) && is_array($operand2)) return TRUE;
+
+               return FALSE;
+       }
+
+       /**
         * Determine if there is a comparator inside $string, and if yes, returns it.
         *
         * @param string $string string to check for a comparator inside
index 1c6897d..ae5976a 100644 (file)
@@ -73,7 +73,7 @@ abstract class Tx_Fluid_Core_ViewHelper_AbstractConditionViewHelper extends Tx_F
        /**
         * Returns value of "then" attribute.
         * If then attribute is not set, iterates through child nodes and renders ThenViewHelper.
-        * If then attribute is not set and no ThenViewHelper is found, all child nodes are rendered
+        * If then attribute is not set and no ThenViewHelper and no ElseViewHelper is found, all child nodes are rendered
         *
         * @return string rendered ThenViewHelper or contents of <f:if> if no ThenViewHelper was found
         * @author Sebastian Kurf├╝rst <sebastian@typo3.org>
@@ -84,14 +84,25 @@ abstract class Tx_Fluid_Core_ViewHelper_AbstractConditionViewHelper extends Tx_F
                if ($this->arguments->hasArgument('then')) {
                        return $this->arguments['then'];
                }
+
+               $elseViewHelperEncountered = FALSE;
                foreach ($this->childNodes as $childNode) {
                        if ($childNode instanceof Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode
                                && $childNode->getViewHelperClassName() === 'Tx_Fluid_ViewHelpers_ThenViewHelper') {
                                $data = $childNode->evaluate($this->getRenderingContext());
                                return $data;
                        }
+                       if ($childNode instanceof Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode
+                               && $childNode->getViewHelperClassName() === 'Tx_Fluid_ViewHelpers_ElseViewHelper') {
+                               $elseViewHelperEncountered = TRUE;
+                       }
+               }
+
+               if ($elseViewHelperEncountered) {
+                       return '';
+               } else {
+                       return $this->renderChildren();
                }
-               return $this->renderChildren();
        }
 
        /**
@@ -105,15 +116,16 @@ abstract class Tx_Fluid_Core_ViewHelper_AbstractConditionViewHelper extends Tx_F
         * @api
         */
        protected function renderElseChild() {
+               if ($this->arguments->hasArgument('else')) {
+                       return $this->arguments['else'];
+               }
+
                foreach ($this->childNodes as $childNode) {
                        if ($childNode instanceof Tx_Fluid_Core_Parser_SyntaxTree_ViewHelperNode
                                && $childNode->getViewHelperClassName() === 'Tx_Fluid_ViewHelpers_ElseViewHelper') {
                                return $childNode->evaluate($this->getRenderingContext());
                        }
                }
-               if ($this->arguments->hasArgument('else')) {
-                       return $this->arguments['else'];
-               }
                return '';
        }
 }