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