TYPO3 CMS  TYPO3_7-6
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 
18 
26 {
32  protected $pageId;
33 
39  protected $rootline;
40 
47  protected $simulateMatchResult = false;
48 
55  protected $simulateMatchConditions = [];
56 
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 
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([
138  '/\\]\\s*(OR|\\|\\|)?\\s*\\[/i',
139  '/\\]\\s*(AND|&&)\\s*\\[/i'
140  ], [
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  $lowerKey = strtolower($key);
205  if ($lowerKey === 'browser' || $lowerKey === 'device' || $lowerKey === 'version' || $lowerKey === 'system' || $lowerKey === 'useragent') {
207  'Usage of client related conditions (browser, device, version, system, useragent) is deprecated since 7.0.'
208  );
209  $browserInfo = $this->getBrowserInfo(GeneralUtility::getIndpEnv('HTTP_USER_AGENT'));
210  }
211  $keyParts = GeneralUtility::trimExplode('|', $key);
212  switch ($keyParts[0]) {
213  case 'applicationContext':
214  $values = GeneralUtility::trimExplode(',', $value, true);
215  $currentApplicationContext = GeneralUtility::getApplicationContext();
216  foreach ($values as $applicationContext) {
217  if ($this->searchStringWildcard($currentApplicationContext, $applicationContext)) {
218  return true;
219  }
220  }
221  return false;
222  break;
223  case 'browser':
224  $values = GeneralUtility::trimExplode(',', $value, true);
225  // take all identified browsers into account, eg chrome deliver
226  // webkit=>532.5, chrome=>4.1, safari=>532.5
227  // so comparing string will be
228  // "webkit532.5 chrome4.1 safari532.5"
229  $all = '';
230  foreach ($browserInfo['all'] as $key => $value) {
231  $all .= $key . $value . ' ';
232  }
233  foreach ($values as $test) {
234  if (stripos($all, $test) !== false) {
235  return true;
236  }
237  }
238  return false;
239  break;
240  case 'version':
241  $values = GeneralUtility::trimExplode(',', $value, true);
242  foreach ($values as $test) {
243  if (strcspn($test, '=<>') == 0) {
244  switch ($test[0]) {
245  case '=':
246  if (floatval(substr($test, 1)) == $browserInfo['version']) {
247  return true;
248  }
249  break;
250  case '<':
251  if (floatval(substr($test, 1)) > $browserInfo['version']) {
252  return true;
253  }
254  break;
255  case '>':
256  if (floatval(substr($test, 1)) < $browserInfo['version']) {
257  return true;
258  }
259  break;
260  }
261  } elseif (strpos(' ' . $browserInfo['version'], $test) == 1) {
262  return true;
263  }
264  }
265  return false;
266  break;
267  case 'system':
268  $values = GeneralUtility::trimExplode(',', $value, true);
269  // Take all identified systems into account, e.g. mac for iOS, Linux
270  // for android and Windows NT for Windows XP
271  $allSystems = ' ' . implode(' ', $browserInfo['all_systems']);
272  foreach ($values as $test) {
273  if (stripos($allSystems, $test) !== false) {
274  return true;
275  }
276  }
277  return false;
278  break;
279  case 'device':
280  if (!isset($this->deviceInfo)) {
281  $this->deviceInfo = $this->getDeviceType(GeneralUtility::getIndpEnv('HTTP_USER_AGENT'));
282  }
283  $values = GeneralUtility::trimExplode(',', $value, true);
284  foreach ($values as $test) {
285  if ($this->deviceInfo == $test) {
286  return true;
287  }
288  }
289  return false;
290  break;
291  case 'useragent':
292  $test = trim($value);
293  if ($test !== '') {
294  return $this->searchStringWildcard((string)$browserInfo['useragent'], $test);
295  } else {
296  return false;
297  }
298  break;
299  case 'language':
300  if (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') === $value) {
301  return true;
302  }
303  $values = GeneralUtility::trimExplode(',', $value, true);
304  foreach ($values as $test) {
305  if (preg_match('/^\\*.+\\*$/', $test)) {
306  $allLanguages = preg_split('/[,;]/', GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
307  if (in_array(substr($test, 1, -1), $allLanguages)) {
308  return true;
309  }
310  } elseif (GeneralUtility::getIndpEnv('HTTP_ACCEPT_LANGUAGE') == $test) {
311  return true;
312  }
313  }
314  return false;
315  break;
316  case 'IP':
317  if ($value === 'devIP') {
318  $value = trim($GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
319  }
320 
321  return (bool)GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value);
322  break;
323  case 'hostname':
324  return (bool)GeneralUtility::cmpFQDN(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $value);
325  break;
326  case 'hour':
327  case 'minute':
328  case 'month':
329  case 'year':
330  case 'dayofweek':
331  case 'dayofmonth':
332  case 'dayofyear':
333  // In order to simulate time properly in templates.
334  $theEvalTime = $GLOBALS['SIM_EXEC_TIME'];
335  switch ($key) {
336  case 'hour':
337  $theTestValue = date('H', $theEvalTime);
338  break;
339  case 'minute':
340  $theTestValue = date('i', $theEvalTime);
341  break;
342  case 'month':
343  $theTestValue = date('m', $theEvalTime);
344  break;
345  case 'year':
346  $theTestValue = date('Y', $theEvalTime);
347  break;
348  case 'dayofweek':
349  $theTestValue = date('w', $theEvalTime);
350  break;
351  case 'dayofmonth':
352  $theTestValue = date('d', $theEvalTime);
353  break;
354  case 'dayofyear':
355  $theTestValue = date('z', $theEvalTime);
356  break;
357  }
358  $theTestValue = (int)$theTestValue;
359  // comp
360  $values = GeneralUtility::trimExplode(',', $value, true);
361  foreach ($values as $test) {
362  if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($test)) {
363  $test = '=' . $test;
364  }
365  if ($this->compareNumber($test, $theTestValue)) {
366  return true;
367  }
368  }
369  return false;
370  break;
371  case 'compatVersion':
372  return GeneralUtility::compat_version($value);
373  break;
374  case 'loginUser':
375  if ($this->isUserLoggedIn()) {
376  $values = GeneralUtility::trimExplode(',', $value, true);
377  foreach ($values as $test) {
378  if ($test == '*' || (string)$this->getUserId() === (string)$test) {
379  return true;
380  }
381  }
382  } elseif ($value === '') {
383  return true;
384  }
385  return false;
386  break;
387  case 'page':
388  if ($keyParts[1]) {
389  $page = $this->getPage();
390  $property = $keyParts[1];
391  if (!empty($page) && isset($page[$property]) && (string)$page[$property] === (string)$value) {
392  return true;
393  }
394  }
395  return false;
396  break;
397  case 'globalVar':
398  $values = GeneralUtility::trimExplode(',', $value, true);
399  foreach ($values as $test) {
400  $point = strcspn($test, '!=<>');
401  $theVarName = substr($test, 0, $point);
402  $nv = $this->getVariable(trim($theVarName));
403  $testValue = substr($test, $point);
404  if ($this->compareNumber($testValue, $nv)) {
405  return true;
406  }
407  }
408  return false;
409  break;
410  case 'globalString':
411  $values = GeneralUtility::trimExplode(',', $value, true);
412  foreach ($values as $test) {
413  $point = strcspn($test, '=');
414  $theVarName = substr($test, 0, $point);
415  $nv = (string)$this->getVariable(trim($theVarName));
416  $testValue = substr($test, $point + 1);
417  if ($this->searchStringWildcard($nv, trim($testValue))) {
418  return true;
419  }
420  }
421  return false;
422  break;
423  case 'userFunc':
424  $matches = [];
425  preg_match_all('/^\s*([^\(\s]+)\s*(?:\((.*)\))?\s*$/', $value, $matches);
426  $funcName = $matches[1][0];
427  $funcValues = trim($matches[2][0]) !== '' ? $this->parseUserFuncArguments($matches[2][0]) : [];
428  if (is_callable($funcName) && call_user_func_array($funcName, $funcValues)) {
429  return true;
430  }
431  return false;
432  break;
433  }
434  return null;
435  }
436 
445  protected function evaluateCustomDefinedCondition($condition)
446  {
447  $conditionResult = null;
448 
449  list($conditionClassName, $conditionParameters) = GeneralUtility::trimExplode(' ', $condition, false, 2);
450 
451  // Check if the condition class name is a valid class
452  // This is necessary to not stop here for the conditions ELSE and GLOBAL
453  if (class_exists($conditionClassName)) {
454  // Use like this: [MyCompany\MyPackage\ConditionMatcher\MyOwnConditionMatcher = myvalue]
456  $conditionObject = GeneralUtility::makeInstance($conditionClassName);
457  if (($conditionObject instanceof \TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractCondition) === false) {
458  throw new \TYPO3\CMS\Core\Configuration\TypoScript\Exception\InvalidTypoScriptConditionException(
459  '"' . $conditionClassName . '" is not a valid TypoScript Condition object.',
460  1410286153
461  );
462  }
463 
464  $conditionParameters = $this->parseUserFuncArguments($conditionParameters);
465  $conditionObject->setConditionMatcherInstance($this);
466  $conditionResult = $conditionObject->matchCondition($conditionParameters);
467  }
468 
469  return $conditionResult;
470  }
471 
478  protected function parseUserFuncArguments($arguments)
479  {
480  $result = [];
481  $arguments = trim($arguments);
482  while ($arguments !== '') {
483  if ($arguments[0] === ',') {
484  $result[] = '';
485  $arguments = substr($arguments, 1);
486  } else {
487  $pos = strcspn($arguments, ',\'"');
488  if ($pos == 0) {
489  // We hit a quote of some kind
490  $quote = $arguments[0];
491  $segment = preg_replace('/^(.*?[^\\\])' . $quote . '.*$/', '\1', substr($arguments, 1));
492  $segment = str_replace('\\' . $quote, $quote, $segment);
493  $result[] = $segment;
494  // shorten $arguments
495  $arguments = substr($arguments, strlen($segment) + 2);
496  $offset = strpos($arguments, ',');
497  if ($offset === false) {
498  $offset = strlen($arguments);
499  }
500  $arguments = substr($arguments, $offset + 1);
501  } else {
502  $result[] = trim(substr($arguments, 0, $pos));
503  $arguments = substr($arguments, $pos + 1);
504  }
505  }
506  $arguments = trim($arguments);
507  }
508  return $result;
509  }
510 
517  protected function getVariableCommon(array $vars)
518  {
519  $value = null;
520  if (count($vars) === 1) {
521  $value = $this->getGlobal($vars[0]);
522  } else {
523  $splitAgain = explode('|', $vars[1], 2);
524  $k = trim($splitAgain[0]);
525  if ($k) {
526  switch ((string)trim($vars[0])) {
527  case 'GP':
528  $value = GeneralUtility::_GP($k);
529  break;
530  case 'GPmerged':
531  $value = GeneralUtility::_GPmerged($k);
532  break;
533  case 'ENV':
534  $value = getenv($k);
535  break;
536  case 'IENV':
537  $value = GeneralUtility::getIndpEnv($k);
538  break;
539  case 'LIT':
540  return trim($vars[1]);
541  break;
542  default:
543  return null;
544  }
545  // If array:
546  if (count($splitAgain) > 1) {
547  if (is_array($value) && trim($splitAgain[1]) !== '') {
548  $value = $this->getGlobal($splitAgain[1], $value);
549  } else {
550  $value = '';
551  }
552  }
553  }
554  }
555  return $value;
556  }
557 
565  protected function compareNumber($test, $leftValue)
566  {
567  if (preg_match('/^(!?=+|<=?|>=?)\\s*([^\\s]*)\\s*$/', $test, $matches)) {
568  $operator = $matches[1];
569  $rightValue = $matches[2];
570  switch ($operator) {
571  case '>=':
572  return $leftValue >= floatval($rightValue);
573  break;
574  case '<=':
575  return $leftValue <= floatval($rightValue);
576  break;
577  case '!=':
578  // multiple values may be split with '|'
579  // see if none matches ("not in list")
580  $found = false;
581  $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
582  foreach ($rightValueParts as $rightValueSingle) {
583  if ($leftValue == floatval($rightValueSingle)) {
584  $found = true;
585  break;
586  }
587  }
588  return $found === false;
589  break;
590  case '<':
591  return $leftValue < floatval($rightValue);
592  break;
593  case '>':
594  return $leftValue > floatval($rightValue);
595  break;
596  default:
597  // nothing valid found except '=', use '='
598  // multiple values may be split with '|'
599  // see if one matches ("in list")
600  $found = false;
601  $rightValueParts = GeneralUtility::trimExplode('|', $rightValue);
602  foreach ($rightValueParts as $rightValueSingle) {
603  if ($leftValue == $rightValueSingle) {
604  $found = true;
605  break;
606  }
607  }
608  return $found;
609  }
610  }
611  return false;
612  }
613 
621  protected function searchStringWildcard($haystack, $needle)
622  {
623  $result = false;
624  if ($haystack === $needle) {
625  $result = true;
626  } elseif ($needle) {
627  if (preg_match('/^\\/.+\\/$/', $needle)) {
628  // Regular expression, only "//" is allowed as delimiter
629  $regex = $needle;
630  } else {
631  $needle = str_replace(['*', '?'], ['%%%MANY%%%', '%%%ONE%%%'], $needle);
632  $regex = '/^' . preg_quote($needle, '/') . '$/';
633  // Replace the marker with .* to match anything (wildcard)
634  $regex = str_replace(['%%%MANY%%%', '%%%ONE%%%'], ['.*', '.'], $regex);
635  }
636  $result = (bool)preg_match($regex, $haystack);
637  }
638  return $result;
639  }
640 
647  protected function getBrowserInfo($userAgent)
648  {
649  return \TYPO3\CMS\Core\Utility\ClientUtility::getBrowserInfo($userAgent);
650  }
651 
659  protected function getDeviceType($userAgent)
660  {
661  return \TYPO3\CMS\Core\Utility\ClientUtility::getDeviceType($userAgent);
662  }
663 
672  protected function getGlobal($var, $source = null)
673  {
674  $vars = explode('|', $var);
675  $c = count($vars);
676  $k = trim($vars[0]);
677  $theVar = isset($source) ? $source[$k] : $GLOBALS[$k];
678  for ($a = 1; $a < $c; $a++) {
679  if (!isset($theVar)) {
680  break;
681  }
682  $key = trim($vars[$a]);
683  if (is_object($theVar)) {
684  $theVar = $theVar->{$key};
685  } elseif (is_array($theVar)) {
686  $theVar = $theVar[$key];
687  } else {
688  return '';
689  }
690  }
691  if (!is_array($theVar) && !is_object($theVar)) {
692  return $theVar;
693  } else {
694  return '';
695  }
696  }
697 
705  abstract protected function evaluateCondition($string);
706 
719  abstract protected function getVariable($name);
720 
726  abstract protected function getGroupList();
727 
733  abstract protected function determinePageId();
734 
740  abstract protected function getPage();
741 
747  abstract protected function determineRootline();
748 
754  abstract protected function getUserId();
755 
761  abstract protected function isUserLoggedIn();
762 
769  abstract protected function log($message);
770 }
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']