[TASK] Sync CMS Fluid with Flow Fluid 1.1 (part1)
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / Core / Parser / SyntaxTree / BooleanNode.php
1 <?php
2 namespace TYPO3\CMS\Fluid\Core\Parser\SyntaxTree;
3
4 /* *
5 * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 /**
15 * A node which is used inside boolean arguments
16 */
17 class BooleanNode extends \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode {
18
19 /**
20 * List of comparators which are supported in the boolean expression language.
21 *
22 * Make sure that if one string is contained in one another, the longer
23 * string is listed BEFORE the shorter one.
24 * Example: put ">=" before ">"
25 *
26 * @var array
27 */
28 static protected $comparators = array('==', '!=', '%', '>=', '>', '<=', '<');
29
30 /**
31 * A regular expression which checks the text nodes of a boolean expression.
32 * Used to define how the regular expression language should look like.
33 *
34 * @var string
35 */
36 static protected $booleanExpressionTextNodeCheckerRegularExpression = '/
37 ^ # Start with first input symbol
38 (?: # start repeat
39 COMPARATORS # We allow all comparators
40 |\s* # Arbitary spaces
41 |-? # Numbers, possibly with the "minus" symbol in front.
42 [0-9]+ # some digits
43 (?: # and optionally a dot, followed by some more digits
44 \\.
45 [0-9]+
46 )?
47 |\'[^\'\\\\]* # single quoted string literals with possibly escaped single quotes
48 (?:
49 \\\\. # escaped character
50 [^\'\\\\]* # unrolled loop following Jeffrey E.F. Friedl
51 )*\'
52 |"[^"\\\\]* # double quoted string literals with possibly escaped double quotes
53 (?:
54 \\\\. # escaped character
55 [^"\\\\]* # unrolled loop following Jeffrey E.F. Friedl
56 )*"
57 )*
58 $/x';
59
60 /**
61 * Left side of the comparison
62 *
63 * @var \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode
64 */
65 protected $leftSide;
66
67 /**
68 * Right side of the comparison
69 *
70 * @var \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode
71 */
72 protected $rightSide;
73
74 /**
75 * The comparator. One element of self::$comparators. If NULL,
76 * no comparator was found, and self::$syntaxTreeNode should
77 * instead be evaluated.
78 *
79 * @var string
80 */
81 protected $comparator;
82
83 /**
84 * If no comparator was found, the syntax tree node should be
85 * converted to boolean.
86 *
87 * @var \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode
88 */
89 protected $syntaxTreeNode;
90
91 /**
92 * Constructor. Parses the syntax tree node and fills $this->leftSide, $this->rightSide,
93 * $this->comparator and $this->syntaxTreeNode.
94 *
95 * @param \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $syntaxTreeNode
96 * @throws \TYPO3\CMS\Fluid\Core\Parser\Exception
97 */
98 public function __construct(\TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode $syntaxTreeNode) {
99 $childNodes = $syntaxTreeNode->getChildNodes();
100 if (count($childNodes) > 3) {
101 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('A boolean expression has more than tree parts.', 1244201848);
102 } elseif (count($childNodes) === 0) {
103 // In this case, we do not have child nodes; i.e. the current SyntaxTreeNode
104 // is a text node with a literal comparison like "1 == 1"
105 $childNodes = array($syntaxTreeNode);
106 }
107
108 $this->leftSide = new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode();
109 $this->rightSide = new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\RootNode();
110 $this->comparator = NULL;
111 foreach ($childNodes as $childNode) {
112 if ($childNode instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode && !preg_match(str_replace('COMPARATORS', implode('|', self::$comparators), self::$booleanExpressionTextNodeCheckerRegularExpression), $childNode->getText())) {
113 // $childNode is text node, and no comparator found.
114 $this->comparator = NULL;
115 // skip loop and fall back to classical to boolean conversion.
116 break;
117 }
118
119 if ($this->comparator !== NULL) {
120 // comparator already set, we are evaluating the right side of the comparator
121 $this->rightSide->addChildNode($childNode);
122 } elseif ($childNode instanceof \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode
123 && ($this->comparator = $this->getComparatorFromString($childNode->getText()))) {
124 // comparator in current string segment
125 $explodedString = explode($this->comparator, $childNode->getText());
126 if (isset($explodedString[0]) && trim($explodedString[0]) !== '') {
127 $value = trim($explodedString[0]);
128 if (is_numeric($value)) {
129 $this->leftSide->addChildNode(new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode($value));
130 } else {
131 $this->leftSide->addChildNode(new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode(preg_replace('/(^[\'"]|[\'"]$)/', '', $value)));
132 }
133 }
134 if (isset($explodedString[1]) && trim($explodedString[1]) !== '') {
135 $value = trim($explodedString[1]);
136 if (is_numeric($value)) {
137 $this->rightSide->addChildNode(new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\NumericNode($value));
138 } else {
139 $this->rightSide->addChildNode(new \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\TextNode(preg_replace('/(^[\'"]|[\'"]$)/', '', $value)));
140 }
141 }
142 } else {
143 // comparator not found yet, on the left side of the comparator
144 $this->leftSide->addChildNode($childNode);
145 }
146 }
147
148 if ($this->comparator === NULL) {
149 // No Comparator found, we need to evaluate the given syntax tree node manually
150 $this->syntaxTreeNode = $syntaxTreeNode;
151 }
152 }
153
154 /**
155 * @return string
156 * @internal
157 */
158 public function getComparator() {
159 return $this->comparator;
160 }
161
162 /**
163 * @return \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode
164 * @internal
165 */
166 public function getSyntaxTreeNode() {
167 return $this->syntaxTreeNode;
168 }
169
170 /**
171 * @return \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode
172 * @internal
173 */
174 public function getLeftSide() {
175 return $this->leftSide;
176 }
177
178 /**
179 * @return \TYPO3\CMS\Fluid\Core\Parser\SyntaxTree\AbstractNode
180 * @internal
181 */
182 public function getRightSide() {
183 return $this->rightSide;
184 }
185
186 /**
187 * @param \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext
188 * @return boolean the boolean value
189 */
190 public function evaluate(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext) {
191 if ($this->comparator !== NULL) {
192 return self::evaluateComparator($this->comparator, $this->leftSide->evaluate($renderingContext), $this->rightSide->evaluate($renderingContext));
193 } else {
194 $value = $this->syntaxTreeNode->evaluate($renderingContext);
195 return self::convertToBoolean($value);
196 }
197 }
198
199 /**
200 * Do the actual comparison. Compares $leftSide and $rightSide with $comparator and emits a boolean value.
201 *
202 * Some special rules apply:
203 * - The == and != operators are comparing the Object Identity using === and !==, when one of the two
204 * operands are objects.
205 * - For arithmetic comparisons (%, >, >=, <, <=), some special rules apply:
206 * - arrays are only comparable with arrays, else the comparison yields FALSE
207 * - objects are only comparable with objects, else the comparison yields FALSE
208 * - the comparison is FALSE when two types are not comparable according to the table
209 * "Comparison with various types" on http://php.net/manual/en/language.operators.comparison.php
210 *
211 * This function must be static public, as it is also directly called from cached templates.
212 *
213 * @param string $comparator
214 * @param mixed $evaluatedLeftSide
215 * @param mixed $evaluatedRightSide
216 * @return boolean TRUE if comparison of left and right side using the comparator emit TRUE, false otherwise
217 * @throws \TYPO3\CMS\Fluid\Core\Parser\Exception
218 */
219 static public function evaluateComparator($comparator, $evaluatedLeftSide, $evaluatedRightSide) {
220 switch ($comparator) {
221 case '==':
222 if (is_object($evaluatedLeftSide) || is_object($evaluatedRightSide)) {
223 return ($evaluatedLeftSide === $evaluatedRightSide);
224 } else {
225 return ($evaluatedLeftSide == $evaluatedRightSide);
226 }
227 break;
228 case '!=':
229 if (is_object($evaluatedLeftSide) || is_object($evaluatedRightSide)) {
230 return ($evaluatedLeftSide !== $evaluatedRightSide);
231 } else {
232 return ($evaluatedLeftSide != $evaluatedRightSide);
233 }
234 break;
235 case '%':
236 if (!self::isComparable($evaluatedLeftSide, $evaluatedRightSide)) {
237 return FALSE;
238 }
239 return (boolean) ((integer) $evaluatedLeftSide % (integer) $evaluatedRightSide);
240 case '>':
241 if (!self::isComparable($evaluatedLeftSide, $evaluatedRightSide)) {
242 return FALSE;
243 }
244 return $evaluatedLeftSide > $evaluatedRightSide;
245 case '>=':
246 if (!self::isComparable($evaluatedLeftSide, $evaluatedRightSide)) {
247 return FALSE;
248 }
249 return $evaluatedLeftSide >= $evaluatedRightSide;
250 case '<':
251 if (!self::isComparable($evaluatedLeftSide, $evaluatedRightSide)) {
252 return FALSE;
253 }
254 return $evaluatedLeftSide < $evaluatedRightSide;
255 case '<=':
256 if (!self::isComparable($evaluatedLeftSide, $evaluatedRightSide)) {
257 return FALSE;
258 }
259 return $evaluatedLeftSide <= $evaluatedRightSide;
260 default:
261 throw new \TYPO3\CMS\Fluid\Core\Parser\Exception('Comparator "' . $comparator . '" is not implemented.', 1244234398);
262 }
263 }
264
265 /**
266 * Checks whether two operands are comparable (based on their types). This implements
267 * the "Comparison with various types" table from http://php.net/manual/en/language.operators.comparison.php,
268 * only leaving out "array" with "anything" and "object" with anything; as we specify
269 * that arrays and objects are incomparable with anything else than their type.
270 *
271 * @param mixed $evaluatedLeftSide
272 * @param mixed $evaluatedRightSide
273 * @return boolean TRUE if the operands can be compared using arithmetic operators, FALSE otherwise.
274 */
275 static protected function isComparable($evaluatedLeftSide, $evaluatedRightSide) {
276 if ((is_null($evaluatedLeftSide) || is_string($evaluatedLeftSide))
277 && is_string($evaluatedRightSide)) {
278 return TRUE;
279 }
280 if (is_bool($evaluatedLeftSide) || is_null($evaluatedLeftSide)) {
281 return TRUE;
282 }
283 if (is_object($evaluatedLeftSide) && is_object($evaluatedRightSide)) {
284 return TRUE;
285 }
286 if ((is_string($evaluatedLeftSide) || is_resource($evaluatedLeftSide) || is_numeric($evaluatedLeftSide))
287 && (is_string($evaluatedRightSide) || is_resource($evaluatedRightSide) || is_numeric($evaluatedRightSide))) {
288 return TRUE;
289 }
290 if (is_array($evaluatedLeftSide) && is_array($evaluatedRightSide)) {
291 return TRUE;
292 }
293 return FALSE;
294 }
295
296 /**
297 * Determine if there is a comparator inside $string, and if yes, returns it.
298 *
299 * @param string $string string to check for a comparator inside
300 * @return string The comparator or NULL if none found.
301 */
302 protected function getComparatorFromString($string) {
303 foreach (self::$comparators as $comparator) {
304 if (strpos($string, $comparator) !== FALSE) {
305 return $comparator;
306 }
307 }
308 return NULL;
309 }
310
311 /**
312 * Convert argument strings to their equivalents. Needed to handle strings with a boolean meaning.
313 *
314 * Must be public and static as it is used from inside cached templates.
315 *
316 * @param mixed $value Value to be converted to boolean
317 * @return boolean
318 */
319 static public function convertToBoolean($value) {
320 if (is_bool($value)) {
321 return $value;
322 }
323 if (is_numeric($value)) {
324 return $value > 0;
325 }
326 if (is_string($value)) {
327 return (!empty($value) && strtolower($value) !== 'false');
328 }
329 if (is_array($value) || (is_object($value) && $value instanceof \Countable)) {
330 return count($value) > 0;
331 }
332 if (is_object($value)) {
333 return TRUE;
334 }
335 return FALSE;
336 }
337 }
338
339 ?>