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