2 namespace TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\Utility\GeneralUtility
;
18 use TYPO3\CMS\Core\Utility\VersionNumberUtility
;
21 * Matching TypoScript conditions
23 * Used with the TypoScript parser.
24 * Matches IPnumbers etc. for use with templates
26 abstract class AbstractConditionMatcher
29 * Id of the current page.
36 * The rootline for the current page.
43 * Whether to simulate the behaviour and match all conditions
44 * (used in TypoScript object browser).
48 protected $simulateMatchResult = false
;
51 * Whether to simulat the behaviour and match specific conditions
52 * (used in TypoScript object browser).
56 protected $simulateMatchConditions = [];
59 * Sets the id of the page to evaluate conditions for.
61 * @param int $pageId Id of the page (must be positive)
63 public function setPageId($pageId)
65 if (is_int($pageId) && $pageId > 0) {
66 $this->pageId
= $pageId;
71 * Gets the id of the page to evaluate conditions for.
73 * @return int Id of the page
75 public function getPageId()
83 * @param array $rootline The rootline to be used for matching (must have elements)
85 public function setRootline(array $rootline)
87 if (!empty($rootline)) {
88 $this->rootline
= $rootline;
95 * @return array The rootline to be used for matching
97 public function getRootline()
99 return $this->rootline
;
103 * Sets whether to simulate the behaviour and match all conditions.
105 * @param bool $simulateMatchResult Whether to simulate positive matches
107 public function setSimulateMatchResult($simulateMatchResult)
109 if (is_bool($simulateMatchResult)) {
110 $this->simulateMatchResult
= $simulateMatchResult;
115 * Sets whether to simulate the behaviour and match specific conditions.
117 * @param array $simulateMatchConditions Conditions to simulate a match for
119 public function setSimulateMatchConditions(array $simulateMatchConditions)
121 $this->simulateMatchConditions
= $simulateMatchConditions;
125 * Normalizes an expression and removes the first and last square bracket.
126 * + OR normalization: "...]OR[...", "...]||[...", "...][..." --> "...]||[..."
127 * + AND normalization: "...]AND[...", "...]&&[..." --> "...]&&[..."
129 * @param string $expression The expression to be normalized (e.g. "[A] && [B] OR [C]")
130 * @return string The normalized expression (e.g. "[A]&&[B]||[C]")
132 protected function normalizeExpression($expression)
134 $normalizedExpression = preg_replace([
135 '/\\]\\s*(OR|\\|\\|)?\\s*\\[/i',
136 '/\\]\\s*(AND|&&)\\s*\\[/i'
140 ], trim($expression));
141 return $normalizedExpression;
145 * Matches a TypoScript condition expression.
147 * @param string $expression The expression to match
148 * @return bool Whether the expression matched
150 public function match($expression)
152 // Return directly if result should be simulated:
153 if ($this->simulateMatchResult
) {
154 return $this->simulateMatchResult
;
156 // Return directly if matching for specific condition is simulated only:
157 if (!empty($this->simulateMatchConditions
)) {
158 return in_array($expression, $this->simulateMatchConditions
);
160 // Sets the current pageId if not defined yet:
161 if (!isset($this->pageId
)) {
162 $this->pageId
= $this->determinePageId();
164 // Sets the rootline if not defined yet:
165 if (!isset($this->rootline
)) {
166 $this->rootline
= $this->determineRootline();
169 $normalizedExpression = $this->normalizeExpression($expression);
170 // First and last character must be square brackets (e.g. "[A]&&[B]":
171 if ($normalizedExpression[0] === '[' && substr($normalizedExpression, -1) === ']') {
172 $innerExpression = substr($normalizedExpression, 1, -1);
173 $orParts = explode(']||[', $innerExpression);
174 foreach ($orParts as $orPart) {
175 $andParts = explode(']&&[', $orPart);
176 foreach ($andParts as $andPart) {
177 $result = $this->evaluateCondition($andPart);
178 // If condition in AND context fails, the whole block is FALSE:
179 if ($result === false
) {
183 // If condition in OR context succeeds, the whole expression is TRUE:
184 if ($result === true
) {
193 * Evaluates a TypoScript condition given as input, eg. "[applicationContext = Production][...(other condition)...]"
195 * @param string $key The condition to match against its criteria.
196 * @param string $value
197 * @return bool|null Result of the evaluation; NULL if condition could not be evaluated
199 protected function evaluateConditionCommon($key, $value)
201 $keyParts = GeneralUtility
::trimExplode('|', $key);
202 switch ($keyParts[0]) {
203 case 'applicationContext':
204 $values = GeneralUtility
::trimExplode(',', $value, true
);
205 $currentApplicationContext = GeneralUtility
::getApplicationContext();
206 foreach ($values as $applicationContext) {
207 if ($this->searchStringWildcard($currentApplicationContext, $applicationContext)) {
214 if (GeneralUtility
::getIndpEnv('HTTP_ACCEPT_LANGUAGE') === $value) {
217 $values = GeneralUtility
::trimExplode(',', $value, true
);
218 foreach ($values as $test) {
219 if (preg_match('/^\\*.+\\*$/', $test)) {
220 $allLanguages = preg_split('/[,;]/', GeneralUtility
::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
221 if (in_array(substr($test, 1, -1), $allLanguages)) {
224 } elseif (GeneralUtility
::getIndpEnv('HTTP_ACCEPT_LANGUAGE') == $test) {
231 if ($value === 'devIP') {
232 $value = trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
235 return (bool
)GeneralUtility
::cmpIP(GeneralUtility
::getIndpEnv('REMOTE_ADDR'), $value);
238 return (bool
)GeneralUtility
::cmpFQDN(GeneralUtility
::getIndpEnv('REMOTE_ADDR'), $value);
247 // In order to simulate time properly in templates.
248 $theEvalTime = $GLOBALS['SIM_EXEC_TIME'];
251 $theTestValue = date('H', $theEvalTime);
254 $theTestValue = date('i', $theEvalTime);
257 $theTestValue = date('m', $theEvalTime);
260 $theTestValue = date('Y', $theEvalTime);
263 $theTestValue = date('w', $theEvalTime);
266 $theTestValue = date('d', $theEvalTime);
269 $theTestValue = date('z', $theEvalTime);
275 $theTestValue = (int)$theTestValue;
277 $values = GeneralUtility
::trimExplode(',', $value, true
);
278 foreach ($values as $test) {
279 if (\TYPO3\CMS\Core\Utility\MathUtility
::canBeInterpretedAsInteger($test)) {
282 if ($this->compareNumber($test, $theTestValue)) {
288 case 'compatVersion':
289 return VersionNumberUtility
::convertVersionNumberToInteger(TYPO3_branch
) >= VersionNumberUtility
::convertVersionNumberToInteger($value);
292 if ($this->isUserLoggedIn()) {
293 $values = GeneralUtility
::trimExplode(',', $value, true
);
294 foreach ($values as $test) {
295 if ($test === '*' ||
(string)$this->getUserId() === (string)$test) {
299 } elseif ($value === '') {
306 $page = $this->getPage();
307 $property = $keyParts[1];
308 if (!empty($page) && isset($page[$property]) && (string)$page[$property] === (string)$value) {
315 $values = GeneralUtility
::trimExplode(',', $value, true
);
316 foreach ($values as $test) {
317 $point = strcspn($test, '!=<>');
318 $theVarName = substr($test, 0, $point);
319 $nv = $this->getVariable(trim($theVarName));
320 $testValue = substr($test, $point);
321 if ($this->compareNumber($testValue, $nv)) {
328 $values = GeneralUtility
::trimExplode(',', $value, true
);
329 foreach ($values as $test) {
330 $point = strcspn($test, '=');
331 $theVarName = substr($test, 0, $point);
332 $nv = (string)$this->getVariable(trim($theVarName));
333 $testValue = substr($test, $point +
1);
334 if ($this->searchStringWildcard($nv, trim($testValue))) {
342 preg_match_all('/^\s*([^\(\s]+)\s*(?:\((.*)\))?\s*$/', $value, $matches);
343 $funcName = $matches[1][0];
344 $funcValues = trim($matches[2][0]) !== '' ?
$this->parseUserFuncArguments($matches[2][0]) : [];
345 if (is_callable($funcName) && call_user_func_array($funcName, $funcValues)) {
355 * Evaluates a TypoScript condition given as input with a custom class name,
356 * e.g. "[MyCompany\MyPackage\ConditionMatcher\MyOwnConditionMatcher = myvalue]"
358 * @param string $condition The condition to match
359 * @return bool|null Result of the evaluation; NULL if condition could not be evaluated
360 * @throws \TYPO3\CMS\Core\Configuration\TypoScript\Exception\InvalidTypoScriptConditionException
362 protected function evaluateCustomDefinedCondition($condition)
364 $conditionResult = null
;
366 list($conditionClassName, $conditionParameters) = GeneralUtility
::trimExplode(' ', $condition, false
, 2);
368 // Check if the condition class name is a valid class
369 // This is necessary to not stop here for the conditions ELSE and GLOBAL
370 if (class_exists($conditionClassName)) {
371 // Use like this: [MyCompany\MyPackage\ConditionMatcher\MyOwnConditionMatcher = myvalue]
372 /** @var \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractCondition $conditionObject */
373 $conditionObject = GeneralUtility
::makeInstance($conditionClassName);
374 if (($conditionObject instanceof \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractCondition
) === false
) {
375 throw new \TYPO3\CMS\Core\Configuration\TypoScript\Exception\
InvalidTypoScriptConditionException(
376 '"' . $conditionClassName . '" is not a valid TypoScript Condition object.',
381 $conditionParameters = $this->parseUserFuncArguments($conditionParameters);
382 $conditionObject->setConditionMatcherInstance($this);
383 $conditionResult = $conditionObject->matchCondition($conditionParameters);
386 return $conditionResult;
390 * Parses arguments to the userFunc.
392 * @param string $arguments
395 protected function parseUserFuncArguments($arguments)
398 $arguments = trim($arguments);
399 while ($arguments !== '') {
400 if ($arguments[0] === ',') {
402 $arguments = substr($arguments, 1);
404 $pos = strcspn($arguments, ',\'"');
406 // We hit a quote of some kind
407 $quote = $arguments[0];
408 $segment = preg_replace('/^(.*?[^\\\])' . $quote . '.*$/', '\1', substr($arguments, 1));
409 $segment = str_replace('\\' . $quote, $quote, $segment);
410 $result[] = $segment;
411 // shorten $arguments
412 $arguments = substr($arguments, strlen($segment) +
2);
413 $offset = strpos($arguments, ',');
414 if ($offset === false
) {
415 $offset = strlen($arguments);
417 $arguments = substr($arguments, $offset +
1);
419 $result[] = trim(substr($arguments, 0, $pos));
420 $arguments = substr($arguments, $pos +
1);
423 $arguments = trim($arguments);
429 * Get variable common
432 * @return mixed Whatever value. If none, then NULL.
434 protected function getVariableCommon(array $vars)
437 $namespace = trim($vars[0]);
438 if (count($vars) === 1) {
439 $value = $this->getGlobal($vars[0]);
440 } elseif ($namespace === 'LIT') {
441 $value = trim($vars[1]);
443 $splitAgain = explode('|', $vars[1], 2);
444 $k = trim($splitAgain[0]);
446 switch ($namespace) {
448 $value = GeneralUtility
::_GP($k);
451 $value = GeneralUtility
::_GPmerged($k);
457 $value = GeneralUtility
::getIndpEnv($k);
463 if (count($splitAgain) > 1) {
464 if (is_array($value) && trim($splitAgain[1]) !== '') {
465 $value = $this->getGlobal($splitAgain[1], $value);
476 * Evaluates a $leftValue based on an operator: "<", ">", "<=", ">=", "!=" or "="
478 * @param string $test The value to compare with on the form [operator][number]. Eg. "< 123
479 * @param float $leftValue The value on the left side
480 * @return bool If $value is "50" and $test is "< 123" then it will return TRUE.
482 protected function compareNumber($test, $leftValue)
484 if (preg_match('/^(!?=+|<=?|>=?)\\s*([^\\s]*)\\s*$/', $test, $matches)) {
485 $operator = $matches[1];
486 $rightValue = $matches[2];
489 return $leftValue >= (float)$rightValue;
492 return $leftValue <= (float)$rightValue;
495 // multiple values may be split with '|'
496 // see if none matches ("not in list")
498 $rightValueParts = GeneralUtility
::trimExplode('|', $rightValue);
499 foreach ($rightValueParts as $rightValueSingle) {
500 if ($leftValue == (float)$rightValueSingle) {
505 return $found === false
;
508 return $leftValue < (float)$rightValue;
511 return $leftValue > (float)$rightValue;
514 // nothing valid found except '=', use '='
515 // multiple values may be split with '|'
516 // see if one matches ("in list")
518 $rightValueParts = GeneralUtility
::trimExplode('|', $rightValue);
519 foreach ($rightValueParts as $rightValueSingle) {
520 if ($leftValue == $rightValueSingle) {
532 * Matching two strings against each other, supporting a "*" wildcard or (if wrapped in "/") PCRE regular expressions
534 * @param string $haystack The string in which to find $needle.
535 * @param string $needle The string to find in $haystack
536 * @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 protected function searchStringWildcard($haystack, $needle)
541 if ($haystack === $needle) {
544 if (preg_match('/^\\/.+\\/$/', $needle)) {
545 // Regular expression, only "//" is allowed as delimiter
548 $needle = str_replace(['*', '?'], ['%%%MANY%%%', '%%%ONE%%%'], $needle);
549 $regex = '/^' . preg_quote($needle, '/') . '$/';
550 // Replace the marker with .* to match anything (wildcard)
551 $regex = str_replace(['%%%MANY%%%', '%%%ONE%%%'], ['.*', '.'], $regex);
553 $result = (bool
)preg_match($regex, $haystack);
559 * Return global variable where the input string $var defines array keys separated by "|"
560 * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value
562 * @param string $var Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back.
563 * @param array $source Alternative array than $GLOBAL to get variables from.
564 * @return mixed Whatever value. If none, then blank string.
566 protected function getGlobal($var, $source = null
)
568 $vars = explode('|', $var);
571 $theVar = isset($source) ?
$source[$k] : $GLOBALS[$k];
572 for ($a = 1; $a < $c; $a++
) {
573 if (!isset($theVar)) {
576 $key = trim($vars[$a]);
577 if (is_object($theVar)) {
578 $theVar = $theVar->{$key};
579 } elseif (is_array($theVar)) {
580 $theVar = $theVar[$key];
585 if (!is_array($theVar) && !is_object($theVar)) {
592 * Evaluates a TypoScript condition given as input, eg. "[browser=net][...(other conditions)...]"
594 * @param string $string The condition to match against its criteria.
595 * @return bool Whether the condition matched
596 * @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser::parse()
598 abstract protected function evaluateCondition($string);
601 * Gets the value of a variable.
605 * + GP:firstLevel|secondLevel
606 * + _GET|firstLevel|secondLevel
607 * + LIT:someLiteralValue
609 * @param string $name The name of the variable to fetch the value from
610 * @return mixed The value of the given variable (string) or NULL if variable did not exist
612 abstract protected function getVariable($name);
615 * Gets the usergroup list of the current user.
617 * @return string The usergroup list of the current user
619 abstract protected function getGroupList();
622 * Determines the current page Id.
624 * @return int The current page Id
626 abstract protected function determinePageId();
629 * Gets the properties for the current page.
631 * @return array The properties for the current page.
633 abstract protected function getPage();
636 * Determines the rootline for the current page.
638 * @return array The rootline for the current page.
640 abstract protected function determineRootline();
643 * Gets the id of the current user.
645 * @return int The id of the current user
647 abstract protected function getUserId();
650 * Determines if a user is logged in.
652 * @return bool Determines if a user is logged in
654 abstract protected function isUserLoggedIn();
657 * Sets a log message.
659 * @param string $message The log message to set/write
661 abstract protected function log($message);