Cleanup: Updated copyright comments
[Packages/TYPO3.CMS.git] / t3lib / matchcondition / class.t3lib_matchcondition_abstract.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009-2011 Oliver Hader <oliver@typo3.org>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Matching TypoScript conditions
29 *
30 * $Id$
31 *
32 * Used with the TypoScript parser.
33 * Matches browserinfo, IPnumbers for use with templates
34 *
35 * @author Oliver Hader <oliver@typo3.org>
36 * @package TYPO3
37 * @subpackage t3lib
38 */
39 abstract class t3lib_matchCondition_abstract {
40 /**
41 * Id of the current page.
42 * @var integer
43 */
44 protected $pageId;
45 /**
46 * The rootline for the current page.
47 * @var array
48 */
49 protected $rootline;
50
51 /**
52 * Whether to simulate the behaviour and match all conditions
53 * (used in TypoScript object browser).
54 * @var boolean
55 */
56 protected $simulateMatchResult = FALSE;
57 /**
58 * Whether to simulat the behaviour and match specific conditions
59 * (used in TypoScript object browser).
60 * @var array
61 */
62 protected $simulateMatchConditions = array();
63
64 /**
65 * Sets the id of the page to evaluate conditions for.
66 *
67 * @param integer $pageId: Id of the page (must be positive)
68 * @return void
69 */
70 public function setPageId($pageId) {
71 if (is_integer($pageId) && $pageId > 0) {
72 $this->pageId = $pageId;
73 }
74 }
75
76 /**
77 * Gets the id of the page to evaluate conditions for.
78 *
79 * @return integer Id of the page
80 */
81 public function getPageId() {
82 return $this->pageId;
83 }
84
85 /**
86 * Sets the rootline.
87 *
88 * @param array $rootline: The rootline to be used for matching (must have elements)
89 * @return void
90 */
91 public function setRootline(array $rootline) {
92 if (count($rootline)) {
93 $this->rootline = $rootline;
94 }
95 }
96
97 /**
98 * Gets the rootline.
99 *
100 * @return array The rootline to be used for matching
101 */
102 public function getRootline() {
103 return $this->rootline;
104 }
105
106 /**
107 * Sets whether to simulate the behaviour and match all conditions.
108 *
109 * @param boolean $simulateMatchResult: Whether to simulate positive matches
110 * @return void
111 */
112 public function setSimulateMatchResult($simulateMatchResult) {
113 if (is_bool($simulateMatchResult)) {
114 $this->simulateMatchResult = $simulateMatchResult;
115 }
116 }
117
118 /**
119 * Sets whether to simulate the behaviour and match specific conditions.
120 *
121 * @param array $simulateMatchConditions: Conditions to simulate a match for
122 * @return void
123 */
124 public function setSimulateMatchConditions(array $simulateMatchConditions) {
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 $normalizedExpression = preg_replace(
138 array(
139 '/\]\s*(OR|\|\|)?\s*\[/i',
140 '/\]\s*(AND|&&)\s*\[/i',
141 ),
142 array(
143 ']||[',
144 ']&&[',
145 ),
146 trim($expression)
147 );
148
149 return $normalizedExpression;
150 }
151
152 /**
153 * Matches a TypoScript condition expression.
154 *
155 * @param string $expression: The expression to match
156 * @return boolean Whether the expression matched
157 */
158 public function match($expression) {
159 // Return directly if result should be simulated:
160 if ($this->simulateMatchResult) {
161 return $this->simulateMatchResult;
162 }
163 // Return directly if matching for specific condition is simulated only:
164 if (count($this->simulateMatchConditions)) {
165 return in_array($expression, $this->simulateMatchConditions);
166 }
167 // Sets the current pageId if not defined yet:
168 if (!isset($this->pageId)) {
169 $this->pageId = $this->determinePageId();
170 }
171 // Sets the rootline if not defined yet:
172 if (!isset($this->rootline)) {
173 $this->rootline = $this->determineRootline();
174 }
175
176 $result = FALSE;
177 $normalizedExpression = $this->normalizeExpression($expression);
178
179 // First and last character must be square brackets (e.g. "[A]&&[B]":
180 if (substr($normalizedExpression, 0, 1) === '[' && substr($normalizedExpression, -1) === ']') {
181 $innerExpression = substr($normalizedExpression, 1, -1);
182
183 $orParts = explode(']||[', $innerExpression);
184 foreach ($orParts as $orPart) {
185 $andParts = explode(']&&[', $orPart);
186 foreach ($andParts as $andPart) {
187 $result = $this->evaluateCondition($andPart);
188 // If condition in AND context fails, the whole block is false:
189 if ($result === FALSE) {
190 break;
191 }
192 }
193 // If condition in OR context succeeds, the whole expression is true:
194 if ($result === TRUE) {
195 break;
196 }
197 }
198 }
199
200 return $result;
201 }
202
203 /**
204 * Evaluates a TypoScript condition given as input, eg. "[browser=net][...(other conditions)...]"
205 *
206 * @param string The condition to match against its criterias.
207 * @return mixed Returns true or false based on the evaluation
208 */
209 protected function evaluateConditionCommon($key, $value) {
210 if (t3lib_div::inList('browser,version,system,useragent', strtolower($key))) {
211 $browserInfo = $this->getBrowserInfo(t3lib_div::getIndpEnv('HTTP_USER_AGENT'));
212 }
213 $keyParts = t3lib_div::trimExplode('|', $key);
214
215 switch ($keyParts[0]) {
216 case 'browser':
217 $values = t3lib_div::trimExplode(',', $value, TRUE);
218 // take all identified browsers into account, eg chrome deliver
219 // webkit=>532.5, chrome=>4.1, safari=>532.5
220 // so comparing string will be
221 // "webkit532.5 chrome4.1 safari532.5"
222 $all = '';
223 foreach ($browserInfo['all'] as $key => $value) {
224 $all .= $key . $value . ' ';
225 }
226 foreach ($values as $test) {
227 if (stripos($all, $test) !== FALSE) {
228 return TRUE;
229 }
230 }
231 break;
232 case 'version':
233 $values = t3lib_div::trimExplode(',', $value, TRUE);
234 foreach ($values as $test) {
235 if (strcspn($test, '=<>') == 0) {
236 switch (substr($test, 0, 1)) {
237 case '=':
238 if (doubleval(substr($test, 1)) == $browserInfo['version']) {
239 return TRUE;
240 }
241 break;
242 case '<':
243 if (doubleval(substr($test, 1)) > $browserInfo['version']) {
244 return TRUE;
245 }
246 break;
247 case '>':
248 if (doubleval(substr($test, 1)) < $browserInfo['version']) {
249 return TRUE;
250 }
251 break;
252 }
253 } elseif (strpos(' ' . $browserInfo['version'], $test) == 1) {
254 return TRUE;
255 }
256 }
257 break;
258 case 'system':
259 $values = t3lib_div::trimExplode(',', $value, TRUE);
260 // Take all identified systems into account, e.g. mac for iOS, Linux
261 // for android and Windows NT for Windows XP
262 $allSystems = $browserInfo['system'];
263 if (!empty($browserInfo['all_systems'])) {
264 $allSystems .= ' ' . implode(' ', $browserInfo['all_systems']);
265 }
266 foreach ($values as $test) {
267 if (stripos($allSystems, $test) !== FALSE) {
268 return TRUE;
269 }
270 }
271 break;
272 case 'device':
273 if (!isset($this->deviceInfo)) {
274 $this->deviceInfo = $this->getDeviceType(t3lib_div::getIndpEnv('HTTP_USER_AGENT'));
275 }
276 $values = t3lib_div::trimExplode(',', $value, TRUE);
277 foreach ($values as $test) {
278 if ($this->deviceInfo == $test) {
279 return TRUE;
280 }
281 }
282 break;
283 case 'useragent':
284 $test = trim($value);
285 if (strlen($test)) {
286 return $this->searchStringWildcard($browserInfo['useragent'], $test);
287 }
288 break;
289 case 'language':
290 $values = t3lib_div::trimExplode(',', $value, TRUE);
291 foreach ($values as $test) {
292 if (preg_match('/^\*.+\*$/', $test)) {
293 $allLanguages = preg_split('/[,;]/', t3lib_div::getIndpEnv('HTTP_ACCEPT_LANGUAGE'));
294 if (in_array(substr($test, 1, -1), $allLanguages)) {
295 return TRUE;
296 }
297 } elseif (t3lib_div::getIndpEnv('HTTP_ACCEPT_LANGUAGE') == $test) {
298 return TRUE;
299 }
300 }
301 break;
302 case 'IP':
303 if (t3lib_div::cmpIP(t3lib_div::getIndpEnv('REMOTE_ADDR'), $value)) {
304 return TRUE;
305 }
306 break;
307 case 'hostname':
308 if (t3lib_div::cmpFQDN(t3lib_div::getIndpEnv('REMOTE_ADDR'), $value)) {
309 return TRUE;
310 }
311 break;
312 // hour, minute, dayofweek, dayofmonth, month, year, julianday
313 case 'hour':
314 case 'minute':
315 case 'month':
316 case 'year':
317 case 'dayofweek':
318 case 'dayofmonth':
319 case 'dayofyear':
320 $theEvalTime = $GLOBALS['SIM_EXEC_TIME']; // In order to simulate time properly in templates.
321 switch ($key) {
322 case 'hour':
323 $theTestValue = date('H', $theEvalTime);
324 break;
325 case 'minute':
326 $theTestValue = date('i', $theEvalTime);
327 break;
328 case 'month':
329 $theTestValue = date('m', $theEvalTime);
330 break;
331 case 'year':
332 $theTestValue = date('Y', $theEvalTime);
333 break;
334 case 'dayofweek':
335 $theTestValue = date('w', $theEvalTime);
336 break;
337 case 'dayofmonth':
338 $theTestValue = date('d', $theEvalTime);
339 break;
340 case 'dayofyear':
341 $theTestValue = date('z', $theEvalTime);
342 break;
343 }
344 $theTestValue = intval($theTestValue);
345 // comp
346 $values = t3lib_div::trimExplode(',', $value, TRUE);
347 foreach ($values as $test) {
348 if (t3lib_div::testInt($test)) {
349 $test = '=' . $test;
350 }
351 if ($this->compareNumber($test, $theTestValue)) {
352 return TRUE;
353 }
354 }
355 break;
356 case 'compatVersion':
357 return t3lib_div::compat_version($value);
358 break;
359 case 'loginUser':
360 if ($this->isUserLoggedIn()) {
361 $values = t3lib_div::trimExplode(',', $value, TRUE);
362 foreach ($values as $test) {
363 if ($test == '*' || !strcmp($this->getUserId(), $test)) {
364 return TRUE;
365 }
366 }
367 } elseif ($value === '') {
368 return TRUE;
369 }
370 break;
371 case 'page':
372 if ($keyParts[1]) {
373 $page = $this->getPage();
374 $property = $keyParts[1];
375 if (!empty($page) && isset($page[$property])) {
376 if (strcmp($page[$property], $value) === 0) {
377 return TRUE;
378 }
379 }
380 }
381 break;
382 case 'globalVar':
383 $values = t3lib_div::trimExplode(',', $value, TRUE);
384 foreach ($values as $test) {
385 $point = strcspn($test, '!=<>');
386 $theVarName = substr($test, 0, $point);
387 $nv = $this->getVariable(trim($theVarName));
388 $testValue = substr($test, $point);
389
390 if ($this->compareNumber($testValue, $nv)) {
391 return TRUE;
392 }
393 }
394 break;
395 case 'globalString':
396 $values = t3lib_div::trimExplode(',', $value, TRUE);
397 foreach ($values as $test) {
398 $point = strcspn($test, '=');
399 $theVarName = substr($test, 0, $point);
400 $nv = $this->getVariable(trim($theVarName));
401 $testValue = substr($test, $point + 1);
402
403 if ($this->searchStringWildcard($nv, trim($testValue))) {
404 return TRUE;
405 }
406 }
407 break;
408 case 'userFunc':
409 $values = preg_split('/\(|\)/', $value);
410 $funcName = trim($values[0]);
411 $funcValue = t3lib_div::trimExplode(',', $values[1]);
412 $prefix = $this->getUserFuncClassPrefix();
413 if ($prefix &&
414 !t3lib_div::isFirstPartOfStr(trim($funcName), $prefix) &&
415 !t3lib_div::isFirstPartOfStr(trim($funcName), 'tx_')
416 ) {
417 $this->log('Match condition: Function "' . $funcName . '" was not prepended with "' . $prefix . '"');
418 return FALSE;
419 }
420 if (function_exists($funcName) && call_user_func($funcName, $funcValue[0])) {
421 return TRUE;
422 }
423 break;
424 }
425
426 return NULL;
427 }
428
429 protected function getVariableCommon(array $vars) {
430 $value = NULL;
431
432 if (count($vars) == 1) {
433 $value = $this->getGlobal($vars[0]);
434 } else {
435 $splitAgain = explode('|', $vars[1], 2);
436 $k = trim($splitAgain[0]);
437
438 if ($k) {
439 switch ((string) trim($vars[0])) {
440 case 'GP':
441 $value = t3lib_div::_GP($k);
442 break;
443 case 'ENV':
444 $value = getenv($k);
445 break;
446 case 'IENV':
447 $value = t3lib_div::getIndpEnv($k);
448 break;
449 // return litteral value:
450 case 'LIT':
451 return trim($vars[1]);
452 break;
453 default:
454 return NULL;
455 }
456 // If array:
457 if (count($splitAgain) > 1) {
458 if (is_array($value) && trim($splitAgain[1])) {
459 $value = $this->getGlobal($splitAgain[1], $value);
460 } else {
461 $value = '';
462 }
463 }
464 }
465 }
466
467 return $value;
468 }
469
470 /**
471 * Evaluates a $leftValue based on an operator: "<", ">", "<=", ">=", "!=" or "="
472 *
473 * @param string $test: The value to compare with on the form [operator][number]. Eg. "< 123"
474 * @param integer $leftValue: The value on the left side
475 * @return boolean If $value is "50" and $test is "< 123" then it will return true.
476 */
477 protected function compareNumber($test, $leftValue) {
478 if (preg_match('/^(!?=+|<=?|>=?)\s*([^\s]*)\s*$/', $test, $matches)) {
479 $operator = $matches[1];
480 $rightValue = $matches[2];
481
482 switch ($operator) {
483 case '>=':
484 return ($leftValue >= doubleval($rightValue));
485 break;
486 case '<=':
487 return ($leftValue <= doubleval($rightValue));
488 break;
489 case '!=':
490 return ($leftValue != doubleval($rightValue));
491 break;
492 case '<':
493 return ($leftValue < doubleval($rightValue));
494 break;
495 case '>':
496 return ($leftValue > doubleval($rightValue));
497 break;
498 default:
499 // nothing valid found except '=', use '='
500 return ($leftValue == trim($rightValue));
501 break;
502 }
503 }
504
505 return FALSE;
506 }
507
508 /**
509 * Matching two strings against each other, supporting a "*" wildcard or (if wrapped in "/") PCRE regular expressions
510 *
511 * @param string The string in which to find $needle.
512 * @param string The string to find in $haystack
513 * @return boolean 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.
514 */
515 protected function searchStringWildcard($haystack, $needle) {
516 $result = FALSE;
517
518 if ($needle) {
519 if (preg_match('/^\/.+\/$/', $needle)) {
520 // Regular expression, only "//" is allowed as delimiter
521 $regex = $needle;
522 } else {
523 $needle = str_replace(array('*', '?'), array('###MANY###', '###ONE###'), $needle);
524 $regex = '/^' . preg_quote($needle, '/') . '$/';
525 // Replace the marker with .* to match anything (wildcard)
526 $regex = str_replace(array('###MANY###', '###ONE###'), array('.*', '.'), $regex);
527 }
528
529 $result = (boolean) preg_match($regex, (string) $haystack);
530 }
531
532 return $result;
533 }
534
535 /**
536 * Generates an array with abstracted browser information
537 *
538 * @param string $userAgent: The useragent string, t3lib_div::getIndpEnv('HTTP_USER_AGENT')
539 * @return array Contains keys "browser", "version", "system"
540 */
541 protected function getBrowserInfo($userAgent) {
542 return t3lib_utility_Client::getBrowserInfo($userAgent);
543 }
544
545 /**
546 * Gets a code for a browsing device based on the input useragent string.
547 *
548 * @param string $userAgent: The useragent string, t3lib_div::getIndpEnv('HTTP_USER_AGENT')
549 * @return string Code for the specific device type
550 */
551 protected function getDeviceType($userAgent) {
552 return t3lib_utility_Client::getDeviceType($userAgent);
553 }
554
555 /**
556 * Return global variable where the input string $var defines array keys separated by "|"
557 * Example: $var = "HTTP_SERVER_VARS | something" will return the value $GLOBALS['HTTP_SERVER_VARS']['something'] value
558 *
559 * @param string Global var key, eg. "HTTP_GET_VAR" or "HTTP_GET_VARS|id" to get the GET parameter "id" back.
560 * @param array Alternative array than $GLOBAL to get variables from.
561 * @return mixed Whatever value. If none, then blank string.
562 */
563 protected function getGlobal($var, $source = NULL) {
564 $vars = explode('|', $var);
565 $c = count($vars);
566 $k = trim($vars[0]);
567 $theVar = isset($source) ? $source[$k] : $GLOBALS[$k];
568
569 for ($a = 1; $a < $c; $a++) {
570 if (!isset($theVar)) {
571 break;
572 }
573
574 $key = trim($vars[$a]);
575 if (is_object($theVar)) {
576 $theVar = $theVar->$key;
577 } elseif (is_array($theVar)) {
578 $theVar = $theVar[$key];
579 } else {
580 return '';
581 }
582 }
583
584 if (!is_array($theVar) && !is_object($theVar)) {
585 return $theVar;
586 } else {
587 return '';
588 }
589 }
590
591
592 /**
593 * Evaluates a TypoScript condition given as input, eg. "[browser=net][...(other conditions)...]"
594 *
595 * @param string $string: The condition to match against its criterias.
596 * @return boolean Whether the condition matched
597 * @see t3lib_tsparser::parse()
598 */
599 abstract protected function evaluateCondition($string);
600
601 /**
602 * Gets the value of a variable.
603 *
604 * Examples of names:
605 * + TSFE:id
606 * + GP:firstLevel|secondLevel
607 * + _GET|firstLevel|secondLevel
608 * + LIT:someLiteralValue
609 *
610 * @param string $name: The name of the variable to fetch the value from
611 * @return mixed The value of the given variable (string) or NULL if variable did not exist
612 */
613 abstract protected function getVariable($name);
614
615 /**
616 * Gets the usergroup list of the current user.
617 *
618 * @return string The usergroup list of the current user
619 */
620 abstract protected function getGroupList();
621
622 /**
623 * Determines the current page Id.
624 *
625 * @return integer The current page Id
626 */
627 abstract protected function determinePageId();
628
629 /**
630 * Gets the properties for the current page.
631 *
632 * @return array The properties for the current page.
633 */
634 abstract protected function getPage();
635
636 /**
637 * Determines the rootline for the current page.
638 *
639 * @return array The rootline for the current page.
640 */
641 abstract protected function determineRootline();
642
643 /**
644 * Gets prefix for user functions (normally 'user_').
645 *
646 * @return string The prefix for user functions (normally 'user_').
647 */
648 abstract protected function getUserFuncClassPrefix();
649
650 /**
651 * Gets the id of the current user.
652 *
653 * @return integer The id of the current user
654 */
655 abstract protected function getUserId();
656
657 /**
658 * Determines if a user is logged in.
659 *
660 * @return boolean Determines if a user is logged in
661 */
662 abstract protected function isUserLoggedIn();
663
664 /**
665 * Sets a log message.
666 *
667 * @param string $message: The log message to set/write
668 * @return void
669 */
670 abstract protected function log($message);
671 }
672
673 ?>