TYPO3 CMS  TYPO3_8-7
AbstractConditionMatcher.php
Go to the documentation of this file.
1 <?php
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 
19 
27 {
33  protected $pageId;
34 
40  protected $rootline;
41 
48  protected $simulateMatchResult = false;
49 
56  protected $simulateMatchConditions = [];
57 
63  public function setPageId($pageId)
64  {
65  if (is_int($pageId) && $pageId > 0) {
66  $this->pageId = $pageId;
67  }
68  }
69 
75  public function getPageId()
76  {
77  return $this->pageId;
78  }
79 
85  public function setRootline(array $rootline)
86  {
87  if (!empty($rootline)) {
88  $this->rootline = $rootline;
89  }
90  }
91 
97  public function getRootline()
98  {
99  return $this->rootline;
100  }
101 
108  {
109  if (is_bool($simulateMatchResult)) {
110  $this->simulateMatchResult = $simulateMatchResult;
111  }
112  }
113 
120  {
121  $this->simulateMatchConditions = $simulateMatchConditions;
122  }
123 
132  protected function normalizeExpression($expression)
133  {
134  $normalizedExpression = preg_replace([
135  '/\\]\\s*(OR|\\|\\|)?\\s*\\[/i',
136  '/\\]\\s*(AND|&&)\\s*\\[/i'
137  ], [
138  ']||[',
139  ']&&['
140  ], trim($expression));
141  return $normalizedExpression;
142  }
143 
150  public function match($expression)
151  {
152  // Return directly if result should be simulated:
153  if ($this->simulateMatchResult) {
155  }
156  // Return directly if matching for specific condition is simulated only:
157  if (!empty($this->simulateMatchConditions)) {
158  return in_array($expression, $this->simulateMatchConditions);
159  }
160  // Sets the current pageId if not defined yet:
161  if (!isset($this->pageId)) {
162  $this->pageId = $this->determinePageId();
163  }
164  // Sets the rootline if not defined yet:
165  if (!isset($this->rootline)) {
166  $this->rootline = $this->determineRootline();
167  }
168  $result = false;
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) {
180  break;
181  }
182  }
183  // If condition in OR context succeeds, the whole expression is TRUE:
184  if ($result === true) {
185  break;
186  }
187  }
188  }
189  return $result;
190  }
191 
199  protected function evaluateConditionCommon($key, $value)
200  {
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)) {
208  return true;
209  }
210  }
211  return false;
212  break;
213  case 'language':
214  if (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') === $value) {
215  return true;
216  }
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)) {
222  return true;
223  }
224  } elseif (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') == $test) {
225  return true;
226  }
227  }
228  return false;
229  break;
230  case 'IP':
231  if ($value === 'devIP') {
232  $value = trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
233  }
234 
235  return (bool)GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value);
236  break;
237  case 'hostname':
238  return (bool)GeneralUtility::cmpFQDN(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value);
239  break;
240  case 'hour':
241  case 'minute':
242  case 'month':
243  case 'year':
244  case 'dayofweek':
245  case 'dayofmonth':
246  case 'dayofyear':
247  // In order to simulate time properly in templates.
248  $theEvalTime = $GLOBALS['SIM_EXEC_TIME'];
249  switch ($key) {
250  case 'hour':
251  $theTestValue = date('H', $theEvalTime);
252  break;
253  case 'minute':
254  $theTestValue = date('i', $theEvalTime);
255  break;
256  case 'month':
257  $theTestValue = date('m', $theEvalTime);
258  break;
259  case 'year':
260  $theTestValue = date('Y', $theEvalTime);
261  break;
262  case 'dayofweek':
263  $theTestValue = date('w', $theEvalTime);
264  break;
265  case 'dayofmonth':
266  $theTestValue = date('d', $theEvalTime);
267  break;
268  case 'dayofyear':
269  $theTestValue = date('z', $theEvalTime);
270  break;
271  default:
272  $theTestValue = 0;
273  break;
274  }
275  $theTestValue = (int)$theTestValue;
276  // comp
277  $values = GeneralUtility::trimExplode(',', $value, true);
278  foreach ($values as $test) {
279  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($test)) {
280  $test = '=' . $test;
281  }
282  if ($this->compareNumber($test, $theTestValue)) {
283  return true;
284  }
285  }
286  return false;
287  break;
288  case 'compatVersion':
290  break;
291  case 'loginUser':
292  if ($this->isUserLoggedIn()) {
293  $values = GeneralUtility::trimExplode(',', $value, true);
294  foreach ($values as $test) {
295  if ($test === '*' || (string)$this->getUserId() === (string)$test) {
296  return true;
297  }
298  }
299  } elseif ($value === '') {
300  return true;
301  }
302  return false;
303  break;
304  case 'page':
305  if ($keyParts[1]) {
306  $page = $this->getPage();
307  $property = $keyParts[1];
308  if (!empty($page) && isset($page[$property]) && (string)$page[$property] === (string)$value) {
309  return true;
310  }
311  }
312  return false;
313  break;
314  case 'globalVar':
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)) {
322  return true;
323  }
324  }
325  return false;
326  break;
327  case 'globalString':
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))) {
335  return true;
336  }
337  }
338  return false;
339  break;
340  case 'userFunc':
341  $matches = [];
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)) {
346  return true;
347  }
348  return false;
349  break;
350  }
351  return null;
352  }
353 
362  protected function evaluateCustomDefinedCondition($condition)
363  {
364  $conditionResult = null;
365 
366  list($conditionClassName, $conditionParameters) = GeneralUtility::trimExplode(' ', $condition, false, 2);
367 
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]
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.',
377  1410286153
378  );
379  }
380 
381  $conditionParameters = $this->parseUserFuncArguments($conditionParameters);
382  $conditionObject->setConditionMatcherInstance($this);
383  $conditionResult = $conditionObject->matchCondition($conditionParameters);
384  }
385 
386  return $conditionResult;
387  }
388 
395  protected function parseUserFuncArguments($arguments)
396  {
397  $result = [];
398  $arguments = trim($arguments);
399  while ($arguments !== '') {
400  if ($arguments[0] === ',') {
401  $result[] = '';
402  $arguments = substr($arguments, 1);
403  } else {
404  $pos = strcspn($arguments, ',\'"');
405  if ($pos == 0) {
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);
416  }
417  $arguments = substr($arguments, $offset + 1);
418  } else {
419  $result[] = trim(substr($arguments, 0, $pos));
420  $arguments = substr($arguments, $pos + 1);
421  }
422  }
423  $arguments = trim($arguments);
424  }
425  return $result;
426  }
427 
434  protected function getVariableCommon(array $vars)
435  {
436  $value = null;
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]);
442  } else {
443  $splitAgain = explode('|', $vars[1], 2);
444  $k = trim($splitAgain[0]);
445  if ($k) {
446  switch ($namespace) {
447  case 'GP':
448  $value = GeneralUtility::_GP($k);
449  break;
450  case 'GPmerged':
451  $value = GeneralUtility::_GPmerged($k);
452  break;
453  case 'ENV':
454  $value = getenv($k);
455  break;
456  case 'IENV':
457  $value = GeneralUtility::getIndpEnv($k);
458  break;
459  default:
460  return null;
461  }
462  // If array:
463  if (count($splitAgain) > 1) {
464  if (is_array($value) && trim($splitAgain[1]) !== '') {
465  $value = $this->getGlobal($splitAgain[1], $value);
466  } else {
467  $value = '';
468  }
469  }
470  }
471  }
472  return $value;
473  }
474 
482  protected function compareNumber($test, $leftValue)
483  {
484  if (preg_match('/^(!?=+|<=?|>=?)\\s*([^\\s]*)\\s*$/', $test, $matches)) {
485  $operator = $matches[1];
486  $rightValue = $matches[2];
487  switch ($operator) {
488  case '>=':
489  return $leftValue >= (float)$rightValue;
490  break;
491  case '<=':
492  return $leftValue <= (float)$rightValue;
493  break;
494  case '!=':
495  // multiple values may be split with '|'
496  // see if none matches ("not in list")
497  $found = false;
498  $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
499  foreach ($rightValueParts as $rightValueSingle) {
500  if ($leftValue == (float)$rightValueSingle) {
501  $found = true;
502  break;
503  }
504  }
505  return $found === false;
506  break;
507  case '<':
508  return $leftValue < (float)$rightValue;
509  break;
510  case '>':
511  return $leftValue > (float)$rightValue;
512  break;
513  default:
514  // nothing valid found except '=', use '='
515  // multiple values may be split with '|'
516  // see if one matches ("in list")
517  $found = false;
518  $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
519  foreach ($rightValueParts as $rightValueSingle) {
520  if ($leftValue == $rightValueSingle) {
521  $found = true;
522  break;
523  }
524  }
525  return $found;
526  }
527  }
528  return false;
529  }
530 
538  protected function searchStringWildcard($haystack, $needle)
539  {
540  $result = false;
541  if ($haystack === $needle) {
542  $result = true;
543  } elseif ($needle) {
544  if (preg_match('/^\\/.+\\/$/', $needle)) {
545  // Regular expression, only "//" is allowed as delimiter
546  $regex = $needle;
547  } else {
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);
552  }
553  $result = (bool)preg_match($regex, $haystack);
554  }
555  return $result;
556  }
557 
566  protected function getGlobal($var, $source = null)
567  {
568  $vars = explode('|', $var);
569  $c = count($vars);
570  $k = trim($vars[0]);
571  $theVar = isset($source) ? $source[$k] : $GLOBALS[$k];
572  for ($a = 1; $a < $c; $a++) {
573  if (!isset($theVar)) {
574  break;
575  }
576  $key = trim($vars[$a]);
577  if (is_object($theVar)) {
578  $theVar = $theVar->{$key};
579  } elseif (is_array($theVar)) {
580  $theVar = $theVar[$key];
581  } else {
582  return '';
583  }
584  }
585  if (!is_array($theVar) && !is_object($theVar)) {
586  return $theVar;
587  }
588  return '';
589  }
590 
598  abstract protected function evaluateCondition($string);
599 
612  abstract protected function getVariable($name);
613 
619  abstract protected function getGroupList();
620 
626  abstract protected function determinePageId();
627 
633  abstract protected function getPage();
634 
640  abstract protected function determineRootline();
641 
647  abstract protected function getUserId();
648 
654  abstract protected function isUserLoggedIn();
655 
661  abstract protected function log($message);
662 }
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']