TYPO3  7.6
AbstractConditionMatcher.php
Go to the documentation of this file.
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 
18 
26 {
32  protected $pageId;
33 
39  protected $rootline;
40 
47  protected $simulateMatchResult = false;
48 
55  protected $simulateMatchConditions = array();
56 
63  public function setPageId($pageId)
64  {
65  if (is_integer($pageId) && $pageId > 0) {
66  $this->pageId = $pageId;
67  }
68  }
69 
75  public function getPageId()
76  {
77  return $this->pageId;
78  }
79 
86  public function setRootline(array $rootline)
87  {
88  if (!empty($rootline)) {
89  $this->rootline = $rootline;
90  }
91  }
92 
98  public function getRootline()
99  {
100  return $this->rootline;
101  }
102 
110  {
111  if (is_bool($simulateMatchResult)) {
112  $this->simulateMatchResult = $simulateMatchResult;
113  }
114  }
115 
123  {
124  $this->simulateMatchConditions = $simulateMatchConditions;
125  }
126 
135  protected function normalizeExpression($expression)
136  {
137  $normalizedExpression = preg_replace(array(
138  '/\\]\\s*(OR|\\|\\|)?\\s*\\[/i',
139  '/\\]\\s*(AND|&&)\\s*\\[/i'
140  ), array(
141  ']||[',
142  ']&&['
143  ), trim($expression));
144  return $normalizedExpression;
145  }
146 
153  public function match($expression)
154  {
155  // Return directly if result should be simulated:
156  if ($this->simulateMatchResult) {
158  }
159  // Return directly if matching for specific condition is simulated only:
160  if (!empty($this->simulateMatchConditions)) {
161  return in_array($expression, $this->simulateMatchConditions);
162  }
163  // Sets the current pageId if not defined yet:
164  if (!isset($this->pageId)) {
165  $this->pageId = $this->determinePageId();
166  }
167  // Sets the rootline if not defined yet:
168  if (!isset($this->rootline)) {
169  $this->rootline = $this->determineRootline();
170  }
171  $result = false;
172  $normalizedExpression = $this->normalizeExpression($expression);
173  // First and last character must be square brackets (e.g. "[A]&&[B]":
174  if ($normalizedExpression[0] === '[' && substr($normalizedExpression, -1) === ']') {
175  $innerExpression = substr($normalizedExpression, 1, -1);
176  $orParts = explode(']||[', $innerExpression);
177  foreach ($orParts as $orPart) {
178  $andParts = explode(']&&[', $orPart);
179  foreach ($andParts as $andPart) {
180  $result = $this->evaluateCondition($andPart);
181  // If condition in AND context fails, the whole block is FALSE:
182  if ($result === false) {
183  break;
184  }
185  }
186  // If condition in OR context succeeds, the whole expression is TRUE:
187  if ($result === true) {
188  break;
189  }
190  }
191  }
192  return $result;
193  }
194 
202  protected function evaluateConditionCommon($key, $value)
203  {
204  if (GeneralUtility::inList('browser,device,version,system,useragent', strtolower($key))) {
206  'Usage of client related conditions (browser, device, version, system, useragent) is deprecated since 7.0.'
207  );
208  $browserInfo = $this->getBrowserInfo(GeneralUtility::getIndpEnv('HTTP_USER_AGENT'));
209  }
210  $keyParts = GeneralUtility::trimExplode('|', $key);
211  switch ($keyParts[0]) {
212  case 'applicationContext':
213  $values = GeneralUtility::trimExplode(',', $value, true);
214  $currentApplicationContext = GeneralUtility::getApplicationContext();
215  foreach ($values as $applicationContext) {
216  if ($this->searchStringWildcard($currentApplicationContext, $applicationContext)) {
217  return true;
218  }
219  }
220  return false;
221  break;
222  case 'browser':
223  $values = GeneralUtility::trimExplode(',', $value, true);
224  // take all identified browsers into account, eg chrome deliver
225  // webkit=>532.5, chrome=>4.1, safari=>532.5
226  // so comparing string will be
227  // "webkit532.5 chrome4.1 safari532.5"
228  $all = '';
229  foreach ($browserInfo['all'] as $key => $value) {
230  $all .= $key . $value . ' ';
231  }
232  foreach ($values as $test) {
233  if (stripos($all, $test) !== false) {
234  return true;
235  }
236  }
237  return false;
238  break;
239  case 'version':
240  $values = GeneralUtility::trimExplode(',', $value, true);
241  foreach ($values as $test) {
242  if (strcspn($test, '=<>') == 0) {
243  switch ($test[0]) {
244  case '=':
245  if (doubleval(substr($test, 1)) == $browserInfo['version']) {
246  return true;
247  }
248  break;
249  case '<':
250  if (doubleval(substr($test, 1)) > $browserInfo['version']) {
251  return true;
252  }
253  break;
254  case '>':
255  if (doubleval(substr($test, 1)) < $browserInfo['version']) {
256  return true;
257  }
258  break;
259  }
260  } elseif (strpos(' ' . $browserInfo['version'], $test) == 1) {
261  return true;
262  }
263  }
264  return false;
265  break;
266  case 'system':
267  $values = GeneralUtility::trimExplode(',', $value, true);
268  // Take all identified systems into account, e.g. mac for iOS, Linux
269  // for android and Windows NT for Windows XP
270  $allSystems = ' ' . implode(' ', $browserInfo['all_systems']);
271  foreach ($values as $test) {
272  if (stripos($allSystems, $test) !== false) {
273  return true;
274  }
275  }
276  return false;
277  break;
278  case 'device':
279  if (!isset($this->deviceInfo)) {
280  $this->deviceInfo = $this->getDeviceType(GeneralUtility::getIndpEnv('HTTP_USER_AGENT'));
281  }
282  $values = GeneralUtility::trimExplode(',', $value, true);
283  foreach ($values as $test) {
284  if ($this->deviceInfo == $test) {
285  return true;
286  }
287  }
288  return false;
289  break;
290  case 'useragent':
291  $test = trim($value);
292  if ($test !== '') {
293  return $this->searchStringWildcard((string)$browserInfo['useragent'], $test);
294  } else {
295  return false;
296  }
297  break;
298  case 'language':
299  if (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') === $value) {
300  return true;
301  }
302  $values = GeneralUtility::trimExplode(',', $value, true);
303  foreach ($values as $test) {
304  if (preg_match('/^\\*.+\\*$/', $test)) {
305  $allLanguages = preg_split('/[,;]/', GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
306  if (in_array(substr($test, 1, -1), $allLanguages)) {
307  return true;
308  }
309  } elseif (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') == $test) {
310  return true;
311  }
312  }
313  return false;
314  break;
315  case 'IP':
316  if ($value === 'devIP') {
317  $value = trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
318  }
319 
320  return (bool)GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value);
321  break;
322  case 'hostname':
323  return (bool)GeneralUtility::cmpFQDN(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value);
324  break;
325  case 'hour':
326  case 'minute':
327  case 'month':
328  case 'year':
329  case 'dayofweek':
330  case 'dayofmonth':
331  case 'dayofyear':
332  // In order to simulate time properly in templates.
333  $theEvalTime = $GLOBALS['SIM_EXEC_TIME'];
334  switch ($key) {
335  case 'hour':
336  $theTestValue = date('H', $theEvalTime);
337  break;
338  case 'minute':
339  $theTestValue = date('i', $theEvalTime);
340  break;
341  case 'month':
342  $theTestValue = date('m', $theEvalTime);
343  break;
344  case 'year':
345  $theTestValue = date('Y', $theEvalTime);
346  break;
347  case 'dayofweek':
348  $theTestValue = date('w', $theEvalTime);
349  break;
350  case 'dayofmonth':
351  $theTestValue = date('d', $theEvalTime);
352  break;
353  case 'dayofyear':
354  $theTestValue = date('z', $theEvalTime);
355  break;
356  }
357  $theTestValue = (int)$theTestValue;
358  // comp
359  $values = GeneralUtility::trimExplode(',', $value, true);
360  foreach ($values as $test) {
361  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($test)) {
362  $test = '=' . $test;
363  }
364  if ($this->compareNumber($test, $theTestValue)) {
365  return true;
366  }
367  }
368  return false;
369  break;
370  case 'compatVersion':
371  return GeneralUtility::compat_version($value);
372  break;
373  case 'loginUser':
374  if ($this->isUserLoggedIn()) {
375  $values = GeneralUtility::trimExplode(',', $value, true);
376  foreach ($values as $test) {
377  if ($test == '*' || (string)$this->getUserId() === (string)$test) {
378  return true;
379  }
380  }
381  } elseif ($value === '') {
382  return true;
383  }
384  return false;
385  break;
386  case 'page':
387  if ($keyParts[1]) {
388  $page = $this->getPage();
389  $property = $keyParts[1];
390  if (!empty($page) && isset($page[$property]) && (string)$page[$property] === (string)$value) {
391  return true;
392  }
393  }
394  return false;
395  break;
396  case 'globalVar':
397  $values = GeneralUtility::trimExplode(',', $value, true);
398  foreach ($values as $test) {
399  $point = strcspn($test, '!=<>');
400  $theVarName = substr($test, 0, $point);
401  $nv = $this->getVariable(trim($theVarName));
402  $testValue = substr($test, $point);
403  if ($this->compareNumber($testValue, $nv)) {
404  return true;
405  }
406  }
407  return false;
408  break;
409  case 'globalString':
410  $values = GeneralUtility::trimExplode(',', $value, true);
411  foreach ($values as $test) {
412  $point = strcspn($test, '=');
413  $theVarName = substr($test, 0, $point);
414  $nv = (string)$this->getVariable(trim($theVarName));
415  $testValue = substr($test, $point + 1);
416  if ($this->searchStringWildcard($nv, trim($testValue))) {
417  return true;
418  }
419  }
420  return false;
421  break;
422  case 'userFunc':
423  $matches = array();
424  preg_match_all('/^\s*([^\(\s]+)\s*(?:\((.*)\))?\s*$/', $value, $matches);
425  $funcName = $matches[1][0];
426  $funcValues = $matches[2][0] ? $this->parseUserFuncArguments($matches[2][0]) : array();
427  if (is_callable($funcName) && call_user_func_array($funcName, $funcValues)) {
428  return true;
429  }
430  return false;
431  break;
432  }
433  return null;
434  }
435 
444  protected function evaluateCustomDefinedCondition($condition)
445  {
446  $conditionResult = null;
447 
448  list($conditionClassName, $conditionParameters) = GeneralUtility::trimExplode(' ', $condition, false, 2);
449 
450  // Check if the condition class name is a valid class
451  // This is necessary to not stop here for the conditions ELSE and GLOBAL
452  if (class_exists($conditionClassName)) {
453  // Use like this: [MyCompany\MyPackage\ConditionMatcher\MyOwnConditionMatcher = myvalue]
455  $conditionObject = GeneralUtility::makeInstance($conditionClassName);
456  if (($conditionObject instanceof \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractCondition) === false) {
457  throw new \TYPO3\CMS\Core\Configuration\TypoScript\Exception\InvalidTypoScriptConditionException(
458  '"' . $conditionClassName . '" is not a valid TypoScript Condition object.',
459  1410286153
460  );
461  }
462 
463  $conditionParameters = $this->parseUserFuncArguments($conditionParameters);
464  $conditionObject->setConditionMatcherInstance($this);
465  $conditionResult = $conditionObject->matchCondition($conditionParameters);
466  }
467 
468  return $conditionResult;
469  }
470 
477  protected function parseUserFuncArguments($arguments)
478  {
479  $result = array();
480  $arguments = trim($arguments);
481  while ($arguments !== '') {
482  if ($arguments[0] === ',') {
483  $result[] = '';
484  $arguments = substr($arguments, 1);
485  } else {
486  $pos = strcspn($arguments, ',\'"');
487  if ($pos == 0) {
488  // We hit a quote of some kind
489  $quote = $arguments[0];
490  $segment = preg_replace('/^(.*?[^\\\])' . $quote . '.*$/', '\1', substr($arguments, 1));
491  $segment = str_replace('\\' . $quote, $quote, $segment);
492  $result[] = $segment;
493  $offset = strpos($arguments, ',', strlen($segment) + 2);
494  if ($offset === false) {
495  $offset = strlen($arguments);
496  }
497  $arguments = substr($arguments, $offset);
498  } else {
499  $result[] = trim(substr($arguments, 0, $pos));
500  $arguments = substr($arguments, $pos + 1);
501  }
502  }
503  $arguments = trim($arguments);
504  };
505  return $result;
506  }
507 
514  protected function getVariableCommon(array $vars)
515  {
516  $value = null;
517  if (count($vars) === 1) {
518  $value = $this->getGlobal($vars[0]);
519  } else {
520  $splitAgain = explode('|', $vars[1], 2);
521  $k = trim($splitAgain[0]);
522  if ($k) {
523  switch ((string)trim($vars[0])) {
524  case 'GP':
525  $value = GeneralUtility::_GP($k);
526  break;
527  case 'GPmerged':
528  $value = GeneralUtility::_GPmerged($k);
529  break;
530  case 'ENV':
531  $value = getenv($k);
532  break;
533  case 'IENV':
534  $value = GeneralUtility::getIndpEnv($k);
535  break;
536  case 'LIT':
537  return trim($vars[1]);
538  break;
539  default:
540  return null;
541  }
542  // If array:
543  if (count($splitAgain) > 1) {
544  if (is_array($value) && trim($splitAgain[1]) !== '') {
545  $value = $this->getGlobal($splitAgain[1], $value);
546  } else {
547  $value = '';
548  }
549  }
550  }
551  }
552  return $value;
553  }
554 
562  protected function compareNumber($test, $leftValue)
563  {
564  if (preg_match('/^(!?=+|<=?|>=?)\\s*([^\\s]*)\\s*$/', $test, $matches)) {
565  $operator = $matches[1];
566  $rightValue = $matches[2];
567  switch ($operator) {
568  case '>=':
569  return $leftValue >= doubleval($rightValue);
570  break;
571  case '<=':
572  return $leftValue <= doubleval($rightValue);
573  break;
574  case '!=':
575  // multiple values may be split with '|'
576  // see if none matches ("not in list")
577  $found = false;
578  $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
579  foreach ($rightValueParts as $rightValueSingle) {
580  if ($leftValue == doubleval($rightValueSingle)) {
581  $found = true;
582  break;
583  }
584  }
585  return $found === false;
586  break;
587  case '<':
588  return $leftValue < doubleval($rightValue);
589  break;
590  case '>':
591  return $leftValue > doubleval($rightValue);
592  break;
593  default:
594  // nothing valid found except '=', use '='
595  // multiple values may be split with '|'
596  // see if one matches ("in list")
597  $found = false;
598  $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
599  foreach ($rightValueParts as $rightValueSingle) {
600  if ($leftValue == $rightValueSingle) {
601  $found = true;
602  break;
603  }
604  }
605  return $found;
606  }
607  }
608  return false;
609  }
610 
618  protected function searchStringWildcard($haystack, $needle)
619  {
620  $result = false;
621  if ($haystack === $needle) {
622  $result = true;
623  } elseif ($needle) {
624  if (preg_match('/^\\/.+\\/$/', $needle)) {
625  // Regular expression, only "//" is allowed as delimiter
626  $regex = $needle;
627  } else {
628  $needle = str_replace(array('*', '?'), array('###MANY###', '###ONE###'), $needle);
629  $regex = '/^' . preg_quote($needle, '/') . '$/';
630  // Replace the marker with .* to match anything (wildcard)
631  $regex = str_replace(array('###MANY###', '###ONE###'), array('.*', '.'), $regex);
632  }
633  $result = (bool)preg_match($regex, $haystack);
634  }
635  return $result;
636  }
637 
644  protected function getBrowserInfo($userAgent)
645  {
646  return \TYPO3\CMS\Core\Utility\ClientUtility::getBrowserInfo($userAgent);
647  }
648 
656  protected function getDeviceType($userAgent)
657  {
658  return \TYPO3\CMS\Core\Utility\ClientUtility::getDeviceType($userAgent);
659  }
660 
669  protected function getGlobal($var, $source = null)
670  {
671  $vars = explode('|', $var);
672  $c = count($vars);
673  $k = trim($vars[0]);
674  $theVar = isset($source) ? $source[$k] : $GLOBALS[$k];
675  for ($a = 1; $a < $c; $a++) {
676  if (!isset($theVar)) {
677  break;
678  }
679  $key = trim($vars[$a]);
680  if (is_object($theVar)) {
681  $theVar = $theVar->{$key};
682  } elseif (is_array($theVar)) {
683  $theVar = $theVar[$key];
684  } else {
685  return '';
686  }
687  }
688  if (!is_array($theVar) && !is_object($theVar)) {
689  return $theVar;
690  } else {
691  return '';
692  }
693  }
694 
702  abstract protected function evaluateCondition($string);
703 
716  abstract protected function getVariable($name);
717 
723  abstract protected function getGroupList();
724 
730  abstract protected function determinePageId();
731 
737  abstract protected function getPage();
738 
744  abstract protected function determineRootline();
745 
751  abstract protected function getUserId();
752 
758  abstract protected function isUserLoggedIn();
759 
766  abstract protected function log($message);
767 }