a176f0ea0ff1b4cb5ac892f7636084e534d141c0
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Configuration / TypoScript / ConditionMatching / AbstractConditionMatcherTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Tests\Unit\Configuration\TypoScript\ConditionMatching;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher;
19 use TYPO3\CMS\Core\Context\Context;
20 use TYPO3\CMS\Core\Context\DateTimeAspect;
21 use TYPO3\CMS\Core\Core\ApplicationContext;
22 use TYPO3\CMS\Core\ExpressionLanguage\TypoScriptConditionProvider;
23 use TYPO3\CMS\Core\Http\ServerRequest;
24 use TYPO3\CMS\Core\Log\Logger;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
27
28 /**
29 * Test cases
30 */
31 class AbstractConditionMatcherTest extends UnitTestCase
32 {
33 /**
34 * @var ApplicationContext
35 */
36 protected $backupApplicationContext;
37
38 /**
39 * @var AbstractConditionMatcher|\PHPUnit_Framework_MockObject_MockObject
40 */
41 protected $conditionMatcher;
42
43 /**
44 * @var \ReflectionMethod
45 */
46 protected $evaluateConditionCommonMethod;
47
48 /**
49 * @var \ReflectionMethod
50 */
51 protected $evaluateExpressionMethod;
52
53 /**
54 * Set up
55 */
56 protected function setUp(): void
57 {
58 require_once 'Fixtures/ConditionMatcherUserFuncs.php';
59
60 $this->resetSingletonInstances = true;
61 $GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
62
63 $this->initConditionMatcher();
64 $this->backupApplicationContext = GeneralUtility::getApplicationContext();
65 }
66
67 protected function initConditionMatcher()
68 {
69 $typoScriptConditionProvider = GeneralUtility::makeInstance(TypoScriptConditionProvider::class);
70 $this->conditionMatcher = $this->getMockForAbstractClass(AbstractConditionMatcher::class, [$typoScriptConditionProvider]);
71 $this->evaluateConditionCommonMethod = new \ReflectionMethod(AbstractConditionMatcher::class, 'evaluateConditionCommon');
72 $this->evaluateConditionCommonMethod->setAccessible(true);
73 $this->evaluateExpressionMethod = new \ReflectionMethod(AbstractConditionMatcher::class, 'evaluateExpression');
74 $this->evaluateExpressionMethod->setAccessible(true);
75 $loggerProphecy = $this->prophesize(Logger::class);
76 $this->conditionMatcher->setLogger($loggerProphecy->reveal());
77 }
78
79 /**
80 * Tear down
81 */
82 protected function tearDown(): void
83 {
84 Fixtures\GeneralUtilityFixture::setApplicationContext($this->backupApplicationContext);
85 parent::tearDown();
86 }
87
88 /**
89 * @return array
90 */
91 public function datesConditionDataProvider(): array
92 {
93 return [
94 '[dayofmonth = 17]' => ['dayofmonth', 17, true],
95 '[dayofweek = 3]' => ['dayofweek', 3, true],
96 '[dayofyear = 16]' => ['dayofyear', 16, true],
97 '[hour = 11]' => ['hour', 11, true],
98 '[minute = 4]' => ['minute', 4, true],
99 '[month = 1]' => ['month', 1, true],
100 '[year = 1945]' => ['year', 1945, true],
101 ];
102 }
103
104 /**
105 * @test
106 * @dataProvider datesConditionDataProvider
107 * @param string $expressionMethod
108 * @param int $expressionValue
109 * @param bool $expected
110 */
111 public function checkConditionMatcherForDates(string $expressionMethod, int $expressionValue, bool $expected): void
112 {
113 $GLOBALS['SIM_EXEC_TIME'] = mktime(11, 4, 0, 1, 17, 1945);
114 $this->assertSame($expected, $this->evaluateConditionCommonMethod->invokeArgs(
115 $this->conditionMatcher,
116 [$expressionMethod, $expressionValue]
117 ));
118 }
119
120 /**
121 * @return array
122 */
123 public function datesFunctionDataProvider(): array
124 {
125 return [
126 '[dayofmonth = 17]' => ['j', 17, true],
127 '[dayofweek = 3]' => ['w', 3, true],
128 '[dayofyear = 16]' => ['z', 16, true],
129 '[hour = 11]' => ['G', 11, true],
130 '[minute = 4]' => ['i', 4, true],
131 '[month = 1]' => ['n', 1, true],
132 '[year = 1945]' => ['Y', 1945, true],
133 ];
134 }
135
136 /**
137 * @test
138 * @dataProvider datesFunctionDataProvider
139 * @param string $format
140 * @param int $expressionValue
141 * @param bool $expected
142 */
143 public function checkConditionMatcherForDateFunction(string $format, int $expressionValue, bool $expected): void
144 {
145 $GLOBALS['SIM_EXEC_TIME'] = gmmktime(11, 4, 0, 1, 17, 1945);
146 GeneralUtility::makeInstance(Context::class)
147 ->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['SIM_EXEC_TIME'])));
148 $this->assertSame(
149 $expected,
150 $this->evaluateExpressionMethod->invokeArgs($this->conditionMatcher, ['date("' . $format . '") == ' . $expressionValue])
151 );
152 }
153
154 /**
155 * @return array
156 */
157 public function hostnameDataProvider(): array
158 {
159 return [
160 '[hostname = localhost]' => ['hostname', 'localhost', true],
161 '[hostname = localhost, foo.local]' => ['hostname', 'localhost, foo.local', true],
162 '[hostname = bar.local, foo.local]' => ['hostname', 'bar.local, foo.local', false],
163 ];
164 }
165
166 /**
167 * @test
168 * @dataProvider hostnameDataProvider
169 * @param string $expressionMethod
170 * @param string $expressionValue
171 * @param bool $expected
172 */
173 public function checkConditionMatcherForHostname(string $expressionMethod, string $expressionValue, bool $expected): void
174 {
175 $GLOBALS['_SERVER']['REMOTE_ADDR'] = '127.0.0.1';
176 $this->assertSame($expected, $this->evaluateConditionCommonMethod->invokeArgs(
177 $this->conditionMatcher,
178 [$expressionMethod, $expressionValue]
179 ));
180 }
181
182 /**
183 * Data provider with matching applicationContext conditions.
184 *
185 * @return array
186 */
187 public function matchingApplicationContextConditionsDataProvider(): array
188 {
189 return [
190 ['Production*'],
191 ['Production/Staging/*'],
192 ['Production/Staging/Server2'],
193 ['/^Production.*$/'],
194 ['/^Production\\/.+\\/Server\\d+$/'],
195 ];
196 }
197
198 /**
199 * @test
200 * @dataProvider matchingApplicationContextConditionsDataProvider
201 */
202 public function evaluateConditionCommonReturnsTrueForMatchingContexts($matchingContextCondition): void
203 {
204 /** @var ApplicationContext $applicationContext */
205 $applicationContext = new ApplicationContext('Production/Staging/Server2');
206 Fixtures\GeneralUtilityFixture::setApplicationContext($applicationContext);
207
208 $this->assertTrue(
209 $this->evaluateConditionCommonMethod->invokeArgs($this->conditionMatcher, ['applicationContext', $matchingContextCondition])
210 );
211 // Test expression language
212 $this->assertTrue(
213 $this->evaluateExpressionMethod->invokeArgs($this->conditionMatcher, ['like("' . $applicationContext . '", "' . preg_quote($matchingContextCondition, '/') . '")'])
214 );
215 }
216
217 /**
218 * Data provider with not matching applicationContext conditions.
219 *
220 * @return array
221 */
222 public function notMatchingApplicationContextConditionsDataProvider(): array
223 {
224 return [
225 ['Production'],
226 ['Testing*'],
227 ['Development/Profiling, Testing/Unit'],
228 ['Testing/Staging/Server2'],
229 ['/^Testing.*$/'],
230 ['/^Production\\/.+\\/Host\\d+$/'],
231 ];
232 }
233
234 /**
235 * @test
236 * @dataProvider notMatchingApplicationContextConditionsDataProvider
237 */
238 public function evaluateConditionCommonReturnsNullForNotMatchingApplicationContexts($notMatchingApplicationContextCondition): void
239 {
240 /** @var ApplicationContext $applicationContext */
241 $applicationContext = new ApplicationContext('Production/Staging/Server2');
242 Fixtures\GeneralUtilityFixture::setApplicationContext($applicationContext);
243
244 $this->assertFalse(
245 $this->evaluateConditionCommonMethod->invokeArgs($this->conditionMatcher, ['applicationContext', $notMatchingApplicationContextCondition])
246 );
247 // Test expression language
248 $this->assertFalse(
249 $this->evaluateExpressionMethod->invokeArgs($this->conditionMatcher, ['like("' . $applicationContext . '", "' . preg_quote($notMatchingApplicationContextCondition, '/') . '")'])
250 );
251 }
252
253 /**
254 * Data provider for evaluateConditionCommonEvaluatesIpAddressesCorrectly
255 *
256 * @return array
257 */
258 public function evaluateConditionCommonDevIpMaskDataProvider(): array
259 {
260 return [
261 // [0] $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
262 // [1] Actual IP
263 // [2] Expected condition result
264 'IP matches' => [
265 '127.0.0.1',
266 '127.0.0.1',
267 true,
268 ],
269 'ipv4 wildcard subnet' => [
270 '127.0.0.1/24',
271 '127.0.0.2',
272 true,
273 ],
274 'ipv6 wildcard subnet' => [
275 '0:0::1/128',
276 '::1',
277 true,
278 ],
279 'List of addresses matches' => [
280 '1.2.3.4, 5.6.7.8',
281 '5.6.7.8',
282 true,
283 ],
284 'IP does not match' => [
285 '127.0.0.1',
286 '127.0.0.2',
287 false,
288 ],
289 'ipv4 subnet does not match' => [
290 '127.0.0.1/8',
291 '126.0.0.1',
292 false,
293 ],
294 'ipv6 subnet does not match' => [
295 '::1/127',
296 '::2',
297 false
298 ],
299 'List of addresses does not match' => [
300 '127.0.0.1, ::1',
301 '::2',
302 false,
303 ],
304 ];
305 }
306
307 /**
308 * @test
309 * @dataProvider evaluateConditionCommonDevIpMaskDataProvider
310 */
311 public function evaluateConditionCommonEvaluatesIpAddressesCorrectly($devIpMask, $actualIp, $expectedResult): void
312 {
313 // Do not trigger proxy stuff of GeneralUtility::getIndPEnv
314 unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP']);
315
316 GeneralUtility::setIndpEnv('REMOTE_ADDR', $actualIp);
317 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = $devIpMask;
318 $this->initConditionMatcher();
319 $this->assertSame($expectedResult, $this->evaluateExpressionMethod->invokeArgs($this->conditionMatcher, ['ip("devIP")']));
320 }
321
322 /**
323 * @test
324 */
325 public function testUserFuncIsCalled(): void
326 {
327 $this->assertTrue(
328 $this->evaluateConditionCommonMethod->invokeArgs(
329 $this->conditionMatcher,
330 ['userFunc', 'user_testFunction']
331 )
332 );
333 }
334
335 /**
336 * @test
337 */
338 public function testUserFuncWithSingleArgument(): void
339 {
340 $this->assertTrue(
341 $this->evaluateConditionCommonMethod->invokeArgs(
342 $this->conditionMatcher,
343 ['userFunc', 'user_testFunctionWithSingleArgument(x)']
344 )
345 );
346 }
347
348 /**
349 * @test
350 */
351 public function testUserFuncWithIntegerZeroArgument(): void
352 {
353 $this->assertTrue(
354 $this->evaluateConditionCommonMethod->invokeArgs(
355 $this->conditionMatcher,
356 ['userFunc', 'user_testFunctionWithSingleArgument(0)']
357 )
358 );
359 }
360
361 /**
362 * @test
363 */
364 public function testUserFuncWithWhitespaceArgument(): void
365 {
366 $this->assertTrue(
367 $this->evaluateConditionCommonMethod->invokeArgs(
368 $this->conditionMatcher,
369 ['userFunc', 'user_testFunctionWithNoArgument( )']
370 )
371 );
372 }
373
374 /**
375 * @test
376 */
377 public function testUserFuncWithMultipleArguments(): void
378 {
379 $this->assertTrue(
380 $this->evaluateConditionCommonMethod->invokeArgs(
381 $this->conditionMatcher,
382 ['userFunc', 'user_testFunctionWithThreeArguments(1,2,3)']
383 )
384 );
385 }
386
387 /**
388 * @test
389 */
390 public function testUserFuncWithMultipleDifferentArgumentsNullBoolString(): void
391 {
392 $this->assertTrue(
393 $this->evaluateConditionCommonMethod->invokeArgs(
394 $this->conditionMatcher,
395 ['userFunc', 'user_testFunctionWithThreeArguments(0,true,"foo")']
396 )
397 );
398 }
399
400 /**
401 * @test
402 */
403 public function testUserFuncWithMultipleDifferentArgumentsNullStringBool(): void
404 {
405 $this->assertTrue(
406 $this->evaluateConditionCommonMethod->invokeArgs(
407 $this->conditionMatcher,
408 ['userFunc', 'user_testFunctionWithThreeArguments(0,"foo",true)']
409 )
410 );
411 }
412
413 /**
414 * @test
415 */
416 public function testUserFuncWithMultipleDifferentArgumentsStringBoolNull(): void
417 {
418 $this->assertTrue(
419 $this->evaluateConditionCommonMethod->invokeArgs(
420 $this->conditionMatcher,
421 ['userFunc', 'user_testFunctionWithThreeArguments("foo",true,0)']
422 )
423 );
424 }
425
426 /**
427 * @test
428 */
429 public function testUserFuncWithMultipleDifferentArgumentsStringNullBool(): void
430 {
431 $this->assertTrue(
432 $this->evaluateConditionCommonMethod->invokeArgs(
433 $this->conditionMatcher,
434 ['userFunc', 'user_testFunctionWithThreeArguments("foo",0,true)']
435 )
436 );
437 }
438
439 /**
440 * @test
441 */
442 public function testUserFuncWithMultipleDifferentArgumentsBoolNullString(): void
443 {
444 $this->assertTrue(
445 $this->evaluateConditionCommonMethod->invokeArgs(
446 $this->conditionMatcher,
447 ['userFunc', 'user_testFunctionWithThreeArguments(true,0,"foo")']
448 )
449 );
450 }
451
452 /**
453 * @test
454 */
455 public function testUserFuncWithMultipleDifferentArgumentsBoolStringNull(): void
456 {
457 $this->assertTrue(
458 $this->evaluateConditionCommonMethod->invokeArgs(
459 $this->conditionMatcher,
460 ['userFunc', 'user_testFunctionWithThreeArguments(true,"foo",0)']
461 )
462 );
463 }
464
465 /**
466 * @test
467 */
468 public function testUserFuncWithMultipleDifferentArgumentsNullBoolStringSingleQuotes(): void
469 {
470 $this->assertTrue(
471 $this->evaluateConditionCommonMethod->invokeArgs(
472 $this->conditionMatcher,
473 ['userFunc', "user_testFunctionWithThreeArguments(0,true,'foo')"]
474 )
475 );
476 }
477
478 /**
479 * @test
480 */
481 public function testUserFuncWithMultipleDifferentArgumentsNullStringBoolSingleQuotes(): void
482 {
483 $this->assertTrue(
484 $this->evaluateConditionCommonMethod->invokeArgs(
485 $this->conditionMatcher,
486 ['userFunc', "user_testFunctionWithThreeArguments(0,'foo',true)"]
487 )
488 );
489 }
490
491 /**
492 * @test
493 */
494 public function testUserFuncWithMultipleDifferentArgumentsStringBoolNullSingleQuotes(): void
495 {
496 $this->assertTrue(
497 $this->evaluateConditionCommonMethod->invokeArgs(
498 $this->conditionMatcher,
499 ['userFunc', "user_testFunctionWithThreeArguments('foo',true,0)"]
500 )
501 );
502 }
503
504 /**
505 * @test
506 */
507 public function testUserFuncWithMultipleDifferentArgumentsStringNullBoolSingleQuotes(): void
508 {
509 $this->assertTrue(
510 $this->evaluateConditionCommonMethod->invokeArgs(
511 $this->conditionMatcher,
512 ['userFunc', "user_testFunctionWithThreeArguments('foo',0,true)"]
513 )
514 );
515 }
516
517 /**
518 * @test
519 */
520 public function testUserFuncWithMultipleDifferentArgumentsBoolNullStringSingleQuotes(): void
521 {
522 $this->assertTrue(
523 $this->evaluateConditionCommonMethod->invokeArgs(
524 $this->conditionMatcher,
525 ['userFunc', "user_testFunctionWithThreeArguments(true,0,'foo')"]
526 )
527 );
528 }
529
530 /**
531 * @test
532 */
533 public function testUserFuncWithMultipleDifferentArgumentsBoolStringNullSingleQuotes(): void
534 {
535 $this->assertTrue(
536 $this->evaluateConditionCommonMethod->invokeArgs(
537 $this->conditionMatcher,
538 ['userFunc', "user_testFunctionWithThreeArguments(true,'foo',0)"]
539 )
540 );
541 }
542
543 /**
544 * @test
545 */
546 public function testUserFuncWithMultipleSingleQuotedArguments(): void
547 {
548 $this->assertTrue(
549 $this->evaluateConditionCommonMethod->invokeArgs(
550 $this->conditionMatcher,
551 ['userFunc', "user_testFunctionWithThreeArguments('foo','bar', 'baz')"]
552 )
553 );
554 }
555
556 /**
557 * @test
558 */
559 public function testUserFuncWithMultipleSoubleQuotedArguments(): void
560 {
561 $this->assertTrue(
562 $this->evaluateConditionCommonMethod->invokeArgs(
563 $this->conditionMatcher,
564 ['userFunc', 'user_testFunctionWithThreeArguments("foo","bar","baz")']
565 )
566 );
567 }
568
569 /**
570 * @test
571 */
572 public function testUserFuncReturnsFalse(): void
573 {
574 $this->assertFalse(
575 $this->evaluateConditionCommonMethod->invokeArgs(
576 $this->conditionMatcher,
577 ['userFunc', 'user_testFunctionFalse']
578 )
579 );
580 }
581
582 /**
583 * @test
584 */
585 public function testUserFuncWithMultipleArgumentsAndQuotes(): void
586 {
587 $this->assertTrue(
588 $this->evaluateConditionCommonMethod->invokeArgs(
589 $this->conditionMatcher,
590 ['userFunc', 'user_testFunctionWithThreeArguments(1,2,"3,4,5,6")']
591 )
592 );
593 }
594
595 /**
596 * @test
597 */
598 public function testUserFuncWithMultipleArgumentsAndQuotesAndSpaces(): void
599 {
600 $this->assertTrue(
601 $this->evaluateConditionCommonMethod->invokeArgs(
602 $this->conditionMatcher,
603 ['userFunc', 'user_testFunctionWithThreeArguments ( 1 , 2, "3, 4, 5, 6" )']
604 )
605 );
606 }
607
608 /**
609 * @test
610 */
611 public function testUserFuncWithMultipleArgumentsAndQuotesAndSpacesStripped(): void
612 {
613 $this->assertTrue(
614 $this->evaluateConditionCommonMethod->invokeArgs(
615 $this->conditionMatcher,
616 ['userFunc', 'user_testFunctionWithThreeArgumentsSpaces ( 1 , 2, "3, 4, 5, 6" )']
617 )
618 );
619 }
620
621 /**
622 * @test
623 */
624 public function testUserFuncWithSpacesInQuotes(): void
625 {
626 $this->assertTrue(
627 $this->evaluateConditionCommonMethod->invokeArgs(
628 $this->conditionMatcher,
629 ['userFunc', 'user_testFunctionWithSpaces(" 3, 4, 5, 6 ")']
630 )
631 );
632 }
633
634 /**
635 * @test
636 */
637 public function testUserFuncWithMultipleArgumentsAndQuotesAndSpacesStrippedAndEscapes(): void
638 {
639 $this->assertTrue(
640 $this->evaluateConditionCommonMethod->invokeArgs(
641 $this->conditionMatcher,
642 ['userFunc', 'user_testFunctionWithThreeArgumentsSpaces ( 1 , 2, "3, \"4, 5\", 6" )']
643 )
644 );
645 }
646
647 /**
648 * @test
649 */
650 public function testUserFuncWithQuoteMissing(): void
651 {
652 $this->assertTrue(
653 $this->evaluateConditionCommonMethod->invokeArgs(
654 $this->conditionMatcher,
655 ['userFunc', 'user_testFunctionWithQuoteMissing ("value \")']
656 )
657 );
658 }
659
660 /**
661 * @test
662 */
663 public function testUserFuncWithQuotesInside(): void
664 {
665 $this->assertTrue(
666 $this->evaluateConditionCommonMethod->invokeArgs(
667 $this->conditionMatcher,
668 ['userFunc', 'user_testQuotes("1 \" 2")']
669 )
670 );
671 }
672
673 /**
674 * @test
675 */
676 public function testUserFuncWithClassMethodCall(): void
677 {
678 $this->assertTrue(
679 $this->evaluateConditionCommonMethod->invokeArgs(
680 $this->conditionMatcher,
681 ['userFunc', 'ConditionMatcherUserFunctions::isTrue(1)']
682 )
683 );
684 }
685 }