[TASK] Re-work/simplify copyright header in PHP files - Part 2
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Configuration / TypoScript / ConditionMatching / AbstractConditionMatcher.php
1 <?php
2 namespace TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching;
3
4 /**
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Matching TypoScript conditions
21 *
22 * Used with the TypoScript parser.
23 * Matches browserinfo, IPnumbers for use with templates
24 *
25 * @author Oliver Hader <oliver@typo3.org>
26 */
27 abstract class AbstractConditionMatcher {
28
29 /**
30 * Id of the current page.
31 *
32 * @var integer
33 */
34 protected $pageId;
35
36 /**
37 * The rootline for the current page.
38 *
39 * @var array
40 */
41 protected $rootline;
42
43 /**
44 * Whether to simulate the behaviour and match all conditions
45 * (used in TypoScript object browser).
46 *
47 * @var boolean
48 */
49 protected $simulateMatchResult = FALSE;
50
51 /**
52 * Whether to simulat the behaviour and match specific conditions
53 * (used in TypoScript object browser).
54 *
55 * @var array
56 */
57 protected $simulateMatchConditions = array();
58
59 /**
60 * Sets the id of the page to evaluate conditions for.
61 *
62 * @param integer $pageId Id of the page (must be positive)
63 * @return void
64 */
65 public function setPageId($pageId) {
66 if (is_integer($pageId) && $pageId > 0) {
67 $this->pageId = $pageId;
68 }
69 }
70
71 /**
72 * Gets the id of the page to evaluate conditions for.
73 *
74 * @return integer Id of the page
75 */
76 public function getPageId() {
77 return $this->pageId;
78 }
79
80 /**
81 * Sets the rootline.
82 *
83 * @param array $rootline The rootline to be used for matching (must have elements)
84 * @return void
85 */
86 public function setRootline(array $rootline) {
87 if (count($rootline)) {
88 $this->rootline = $rootline;
89 }
90 }
91
92 /**
93 * Gets the rootline.
94 *
95 * @return array The rootline to be used for matching
96 */
97 public function getRootline() {
98 return $this->rootline;
99 }
100
101 /**
102 * Sets whether to simulate the behaviour and match all conditions.
103 *
104 * @param boolean $simulateMatchResult Whether to simulate positive matches
105 * @return void
106 */
107 public function setSimulateMatchResult($simulateMatchResult) {
108 if (is_bool($simulateMatchResult)) {
109 $this->simulateMatchResult = $simulateMatchResult;
110 }
111 }
112
113 /**
114 * Sets whether to simulate the behaviour and match specific conditions.
115 *
116 * @param array $simulateMatchConditions Conditions to simulate a match for
117 * @return void
118 */
119 public function setSimulateMatchConditions(array $simulateMatchConditions) {
120 $this->simulateMatchConditions = $simulateMatchConditions;
121 }
122
123 /**
124 * Normalizes an expression and removes the first and last square bracket.
125 * + OR normalization: "...]OR[...", "...]||[...", "...][..." --> "...]||[..."
126 * + AND normalization: "...]AND[...", "...]&&[..." --> "...]&&[..."
127 *
128 * @param string $expression The expression to be normalized (e.g. "[A] && [B] OR [C]")
129 * @return string The normalized expression (e.g. "[A]&&[B]||[C]")
130 */
131 protected function normalizeExpression($expression) {
132 $normalizedExpression = preg_replace(array(
133 '/\\]\\s*(OR|\\|\\|)?\\s*\\[/i',
134 '/\\]\\s*(AND|&&)\\s*\\[/i'
135 ), array(
136 ']||[',
137 ']&&['
138 ), trim($expression));
139 return $normalizedExpression;
140 }
141
142 /**
143 * Matches a TypoScript condition expression.
144 *
145 * @param string $expression The expression to match
146 * @return boolean Whether the expression matched
147 */
148 public function match($expression) {
149 // Return directly if result should be simulated:
150 if ($this->simulateMatchResult) {
151 return $this->simulateMatchResult;
152 }
153 // Return directly if matching for specific condition is simulated only:
154 if (count($this->simulateMatchConditions)) {
155 return in_array($expression, $this->simulateMatchConditions);
156 }
157 // Sets the current pageId if not defined yet:
158 if (!isset($this->pageId)) {
159 $this->pageId = $this->determinePageId();
160 }
161 // Sets the rootline if not defined yet:
162 if (!isset($this->rootline)) {
163 $this->rootline = $this->determineRootline();
164 }
165 $result = FALSE;
166 $normalizedExpression = $this->normalizeExpression($expression);
167 // First and last character must be square brackets (e.g. "[A]&&[B]":
168 if ($normalizedExpression[0] === '[' && substr($normalizedExpression, -1) === ']') {
169 $innerExpression = substr($normalizedExpression, 1, -1);
170 $orParts = explode(']||[', $innerExpression);
171 foreach ($orParts as $orPart) {
172 $andParts = explode(']&&[', $orPart);
173 foreach ($andParts as $andPart) {
174 $result = $this->evaluateCondition($andPart);
175 // If condition in AND context fails, the whole block is FALSE:
176 if ($result === FALSE) {
177 break;
178 }
179 }
180 // If condition in OR context succeeds, the whole expression is TRUE:
181 if ($result === TRUE) {
182 break;
183 }
184 }
185 }
186 return $result;
187 }
188
189 /**
190 * Evaluates a TypoScript condition given as input, eg. "[browser=net][...(other conditions)...]"
191 *
192 * @param string $key The condition to match against its criterias.
193 * @param string $value
194 * @return NULL|boolean Result of the evaluation; NULL if condition could not be evaluated
195 */
196 protected function evaluateConditionCommon($key, $value) {
197 if (GeneralUtility::inList('browser,version,system,useragent', strtolower($key))) {
198 $browserInfo = $this->getBrowserInfo(GeneralUtility::getIndpEnv('HTTP_USER_AGENT'));
199 }
200 $keyParts = GeneralUtility::trimExplode('|', $key);
201 switch ($keyParts[0]) {
202 case 'applicationContext':
203 $values = GeneralUtility::trimExplode(',', $value, TRUE);
204 $currentApplicationContext = GeneralUtility::getApplicationContext();
205 foreach ($values as $applicationContext) {
206 if ($this->searchStringWildcard($currentApplicationContext, $applicationContext)) {
207 return TRUE;
208 }
209 }
210 break;
211 case 'browser':
212 $values = GeneralUtility::trimExplode(',', $value, TRUE);
213 // take all identified browsers into account, eg chrome deliver
214 // webkit=>532.5, chrome=>4.1, safari=>532.5
215 // so comparing string will be
216 // "webkit532.5 chrome4.1 safari532.5"
217 $all = '';
218 foreach ($browserInfo['all'] as $key => $value) {
219 $all .= $key . $value . ' ';
220 }
221 foreach ($values as $test) {
222 if (stripos($all, $test) !== FALSE) {
223 return TRUE;
224 }
225 }
226 break;
227 case 'version':
228 $values = GeneralUtility::trimExplode(',', $value, TRUE);
229 foreach ($values as $test) {
230 if (strcspn($test, '=<>') == 0) {
231 switch ($test[0]) {
232 case '=':
233 if (doubleval(substr($test, 1)) == $browserInfo['version']) {
234 return TRUE;
235 }
236 break;
237 case '<':
238 if (doubleval(substr($test, 1)) > $browserInfo['version']) {
239 return TRUE;
240 }
241 break;
242 case '>':
243 if (doubleval(substr($test, 1)) < $browserInfo['version']) {
244 return TRUE;
245 }
246 break;
247 }
248 } elseif (strpos(' ' . $browserInfo['version'], $test) == 1) {
249 return TRUE;
250 }
251 }
252 break;
253 case 'system':
254 $values = GeneralUtility::trimExplode(',', $value, TRUE);
255 // Take all identified systems into account, e.g. mac for iOS, Linux
256 // for android and Windows NT for Windows XP
257 $allSystems = ' ' . implode(' ', $browserInfo['all_systems']);
258 foreach ($values as $test) {
259 if (stripos($allSystems, $test) !== FALSE) {
260 return TRUE;
261 }
262 }
263 break;
264 case 'device':
265 if (!isset($this->deviceInfo)) {
266 $this->deviceInfo = $this->getDeviceType(GeneralUtility::getIndpEnv('HTTP_USER_AGENT'));
267 }
268 $values = GeneralUtility::trimExplode(',', $value, TRUE);
269 foreach ($values as $test) {
270 if ($this->deviceInfo == $test) {
271 return TRUE;
272 }
273 }
274 break;
275 case 'useragent':
276 $test = trim($value);
277 if ($test !== '') {
278 return $this->searchStringWildcard((string)$browserInfo['useragent'], $test);
279 }
280 break;
281 case 'language':
282 if (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') === $value) {
283 return TRUE;
284 }
285 $values = GeneralUtility::trimExplode(',', $value, TRUE);
286 foreach ($values as $test) {
287 if (preg_match('/^\\*.+\\*$/', $test)) {
288 $allLanguages = preg_split('/[,;]/', GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
289 if (in_array(substr($test, 1, -1), $allLanguages)) {
290 return TRUE;
291 }
292 } elseif (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') == $test) {
293 return TRUE;
294 }
295 }
296 break;
297 case 'IP':
298 if ($value === 'devIP') {
299 $value = trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
300 }
301
302 if (GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value)) {
303 return TRUE;
304 }
305 break;
306 case 'hostname':
307 if (GeneralUtility::cmpFQDN(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value)) {
308 return TRUE;
309 }
310 break;
311 case 'hour':
312
313 case 'minute':
314
315 case 'month':
316
317 case 'year':
318
319 case 'dayofweek':
320
321 case 'dayofmonth':
322
323 case 'dayofyear':
324 // In order to simulate time properly in templates.
325 $theEvalTime = $GLOBALS['SIM_EXEC_TIME'];
326 switch ($key) {
327 case 'hour':
328 $theTestValue = date('H', $theEvalTime);
329 break;
330 case 'minute':
331 $theTestValue = date('i', $theEvalTime);
332 break;
333 case 'month':
334 $theTestValue = date('m', $theEvalTime);
335 break;
336 case 'year':
337 $theTestValue = date('Y', $theEvalTime);
338 break;
339 case 'dayofweek':
340 $theTestValue = date('w', $theEvalTime);
341 break;
342 case 'dayofmonth':
343 $theTestValue = date('d', $theEvalTime);
344 break;
345 case 'dayofyear':
346 $theTestValue = date('z', $theEvalTime);
347 break;
348 }
349 $theTestValue = (int)$theTestValue;
350 // comp
351 $values = GeneralUtility::trimExplode(',', $value, TRUE);
352 foreach ($values as $test) {
353 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($test)) {
354 $test = '=' . $test;
355 }
356 if ($this->compareNumber($test, $theTestValue)) {
357 return TRUE;
358 }
359 }
360 break;
361 case 'compatVersion':
362 return GeneralUtility::compat_version($value);
363 break;
364 case 'loginUser':
365 if ($this->isUserLoggedIn()) {
366 $values = GeneralUtility::trimExplode(',', $value, TRUE);
367 foreach ($values as $test) {
368 if ($test == '*' || (string)$this->getUserId() === (string)$test) {
369 return TRUE;
370 }
371 }
372 } elseif ($value === '') {
373 return TRUE;
374 }
375 break;
376 case 'page':
377 if ($keyParts[1]) {
378 $page = $this->getPage();
379 $property = $keyParts[1];
380 if (!empty($page) && isset($page[$property]) && (string)$page[$property] === (string)$value) {
381 return TRUE;
382 }
383 }
384 break;
385 case 'globalVar':
386 $values = GeneralUtility::trimExplode(',', $value, TRUE);
387 foreach ($values as $test) {
388 $point = strcspn($test, '!=<>');
389 $theVarName = substr($test, 0, $point);
390 $nv = $this->getVariable(trim($theVarName));
391 $testValue = substr($test, $point);
392 if ($this->compareNumber($testValue, $nv)) {
393 return TRUE;
394 }
395 }
396 break;
397 case 'globalString':
398 $values = GeneralUtility::trimExplode(',', $value, TRUE);
399 foreach ($values as $test) {
400 $point = strcspn($test, '=');
401 $theVarName = substr($test, 0, $point);
402 $nv = (string)$this->getVariable(trim($theVarName));
403 $testValue = substr($test, $point + 1);
404 if ($this->searchStringWildcard($nv, trim($testValue))) {
405 return TRUE;
406 }
407 }
408 break;
409 case 'userFunc':
410 $matches = array();
411 preg_match_all('/^\s*([^\(\s]+)\s*(?:\((.*)\))?\s*$/', $value, $matches);
412 $funcName = $matches[1][0];
413 $funcValues = $matches[2][0] ? $this->parseUserFuncArguments($matches[2][0]) : array();
414 if (function_exists($funcName) && call_user_func_array($funcName, $funcValues)) {
415 return TRUE;
416 }
417 break;
418 }
419 return NULL;
420 }
421
422 /**
423 * Parses arguments to the userFunc.
424 *
425 * @param string $arguments
426 * @return array
427 */
428 protected function parseUserFuncArguments($arguments) {
429 $result = array();
430 $arguments = trim($arguments);
431 while ($arguments) {
432 if ($arguments[0] === ',') {
433 $result[] = '';
434 $arguments = substr($arguments, 1);
435 } else {
436 $pos = strcspn($arguments, ',\'"');
437 if ($pos == 0) {
438 // We hit a quote of some kind
439 $quote = $arguments[0];
440 $segment = preg_replace('/^(.*?[^\\\])' . $quote . '.*$/', '\1', substr($arguments, 1));
441 $segment = str_replace('\\' . $quote, $quote, $segment);
442 $result[] = $segment;
443 $offset = strpos($arguments, ',', strlen($segment) + 2);
444 if ($offset === FALSE) {
445 $offset = strlen($arguments);
446 }
447 $arguments = substr($arguments, $offset);
448 } else {
449 $result[] = trim(substr($arguments, 0, $pos));
450 $arguments = substr($arguments, $pos + 1);
451 }
452 }
453 $arguments = trim($arguments);
454 };
455 return $result;
456 }
457
458 /**
459 * Get variable common
460 *
461 * @param array $vars
462 * @return mixed Whatever value. If none, then NULL.
463 */
464 protected function getVariableCommon(array $vars) {
465 $value = NULL;
466 if (count($vars) == 1) {
467 $value = $this->getGlobal($vars[0]);
468 } else {
469 $splitAgain = explode('|', $vars[1], 2);
470 $k = trim($splitAgain[0]);
471 if ($k) {
472 switch ((string) trim($vars[0])) {
473 case 'GP':
474 $value = GeneralUtility::_GP($k);
475 break;
476 case 'ENV':
477 $value = getenv($k);
478 break;
479 case 'IENV':
480 $value = GeneralUtility::getIndpEnv($k);
481 break;
482 case 'LIT':
483 return trim($vars[1]);
484 break;
485 default:
486 return NULL;
487 }
488 // If array:
489 if (count($splitAgain) > 1) {
490 if (is_array($value) && trim($splitAgain[1])) {
491 $value = $this->getGlobal($splitAgain[1], $value);
492 } else {
493 $value = '';
494 }
495 }
496 }
497 }
498 return $value;
499 }
500
501 /**
502 * Evaluates a $leftValue based on an operator: "<", ">", "<=", ">=", "!=" or "="
503 *
504 * @param string $test The value to compare with on the form [operator][number]. Eg. "< 123
505 * @param float $leftValue The value on the left side
506 * @return boolean If $value is "50" and $test is "< 123" then it will return TRUE.
507 */
508 protected function compareNumber($test, $leftValue) {
509 if (preg_match('/^(!?=+|<=?|>=?)\\s*([^\\s]*)\\s*$/', $test, $matches)) {
510 $operator = $matches[1];
511 $rightValue = $matches[2];
512 switch ($operator) {
513 case '>=':
514 return $leftValue >= doubleval($rightValue);
515 break;
516 case '<=':
517 return $leftValue <= doubleval($rightValue);
518 break;
519 case '!=':
520 // multiple values may be split with '|'
521 // see if none matches ("not in list")
522 $found = FALSE;
523 $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
524 foreach ($rightValueParts as $rightValueSingle) {
525 if ($leftValue == doubleval($rightValueSingle)) {
526 $found = TRUE;
527 break;
528 }
529 }
530 return $found === FALSE;
531 break;
532 case '<':
533 return $leftValue < doubleval($rightValue);
534 break;
535 case '>':
536 return $leftValue > doubleval($rightValue);
537 break;
538 default:
539 // nothing valid found except '=', use '='
540 // multiple values may be split with '|'
541 // see if one matches ("in list")
542 $found = FALSE;
543 $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
544 foreach ($rightValueParts as $rightValueSingle) {
545 if ($leftValue == $rightValueSingle) {
546 $found = TRUE;
547 break;
548 }
549 }
550 return $found;
551 }
552 }
553 return FALSE;
554 }
555
556 /**
557 * Matching two strings against each other, supporting a "*" wildcard or (if wrapped in "/") PCRE regular expressions
558 *
559 * @param string $haystack The string in which to find $needle.
560 * @param string $needle The string to find in $haystack
561 * @return boolean Returns TRUE if $needle matches or is found in (according to wildcards) in $haystack. Eg. if $haystack is "Netscape 6.5" and $needle is "Net*" or "Net*ape" then it returns TRUE.
562 */
563 protected function searchStringWildcard($haystack, $needle) {
564 $result = FALSE;
565 if ($haystack === $needle) {
566 $result = TRUE;
567 } elseif ($needle) {
568 if (preg_match('/^\\/.+\\/$/', $needle)) {
569 // Regular expression, only "//" is allowed as delimiter
570 $regex = $needle;
571 } else {
572 $needle = str_replace(array('*', '?'), array('###MANY###', '###ONE###'), $needle);
573 $regex = '/^' . preg_quote($needle, '/') . '$/';
574 // Replace the marker with .* to match anything (wildcard)
575 $regex = str_replace(array('###MANY###', '###ONE###'), array('.*', '.'), $regex);
576 }
577 $result = (bool)preg_match($regex, $haystack);
578 }
579 return $result;
580 }
581
582 /**
583 * Generates an array with abstracted browser information
584 *
585 * @param string $userAgent The useragent string, \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('HTTP_USER_AGENT')
586 * @return array Contains keys "browser", "version", "system
587 */
588 protected function getBrowserInfo($userAgent) {
589 return \TYPO3\CMS\Core\Utility\ClientUtility::getBrowserInfo($userAgent);
590 }
591
592 /**
593 * Gets a code for a browsing device based on the input useragent string.
594 *
595 * @param string $userAgent The useragent string, \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('HTTP_USER_AGENT')
596 * @return string Code for the specific device type
597 */
598 protected function getDeviceType($userAgent) {
599 return \TYPO3\CMS\Core\Utility\ClientUtility::getDeviceType($userAgent);
600 }
601
602 /**
603 * Return global variable where the input string $var defines array keys separated by "|"
604 * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value
605 *
606 * @param string $var Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back.
607 * @param array $source Alternative array than $GLOBAL to get variables from.
608 * @return mixed Whatever value. If none, then blank string.
609 */
610 protected function getGlobal($var, $source = NULL) {
611 $vars = explode('|', $var);
612 $c = count($vars);
613 $k = trim($vars[0]);
614 $theVar = isset($source) ? $source[$k] : $GLOBALS[$k];
615 for ($a = 1; $a < $c; $a++) {
616 if (!isset($theVar)) {
617 break;
618 }
619 $key = trim($vars[$a]);
620 if (is_object($theVar)) {
621 $theVar = $theVar->{$key};
622 } elseif (is_array($theVar)) {
623 $theVar = $theVar[$key];
624 } else {
625 return '';
626 }
627 }
628 if (!is_array($theVar) && !is_object($theVar)) {
629 return $theVar;
630 } else {
631 return '';
632 }
633 }
634
635 /**
636 * Evaluates a TypoScript condition given as input, eg. "[browser=net][...(other conditions)...]"
637 *
638 * @param string $string The condition to match against its criterias.
639 * @return boolean Whether the condition matched
640 * @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::parse()
641 */
642 abstract protected function evaluateCondition($string);
643
644 /**
645 * Gets the value of a variable.
646 *
647 * Examples of names:
648 * + TSFE:id
649 * + GP:firstLevel|secondLevel
650 * + _GET|firstLevel|secondLevel
651 * + LIT:someLiteralValue
652 *
653 * @param string $name The name of the variable to fetch the value from
654 * @return mixed The value of the given variable (string) or NULL if variable did not exist
655 */
656 abstract protected function getVariable($name);
657
658 /**
659 * Gets the usergroup list of the current user.
660 *
661 * @return string The usergroup list of the current user
662 */
663 abstract protected function getGroupList();
664
665 /**
666 * Determines the current page Id.
667 *
668 * @return integer The current page Id
669 */
670 abstract protected function determinePageId();
671
672 /**
673 * Gets the properties for the current page.
674 *
675 * @return array The properties for the current page.
676 */
677 abstract protected function getPage();
678
679 /**
680 * Determines the rootline for the current page.
681 *
682 * @return array The rootline for the current page.
683 */
684 abstract protected function determineRootline();
685
686 /**
687 * Gets the id of the current user.
688 *
689 * @return integer The id of the current user
690 */
691 abstract protected function getUserId();
692
693 /**
694 * Determines if a user is logged in.
695 *
696 * @return boolean Determines if a user is logged in
697 */
698 abstract protected function isUserLoggedIn();
699
700 /**
701 * Sets a log message.
702 *
703 * @param string $message The log message to set/write
704 * @return void
705 */
706 abstract protected function log($message);
707
708 }