[TASK] Streamline expressionLanguage usage in core
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Unit / Configuration / TypoScript / ConditionMatching / ConditionMatcherTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\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 Prophecy\Argument;
19 use TYPO3\CMS\Core\Cache\CacheManager;
20 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
21 use TYPO3\CMS\Core\Context\Context;
22 use TYPO3\CMS\Core\Context\UserAspect;
23 use TYPO3\CMS\Core\Http\ServerRequest;
24 use TYPO3\CMS\Core\Log\Logger;
25 use TYPO3\CMS\Core\Package\PackageInterface;
26 use TYPO3\CMS\Core\Package\PackageManager;
27 use TYPO3\CMS\Core\Site\Entity\Site;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
30 use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
31 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
32
33 /**
34 * Test case
35 */
36 class ConditionMatcherTest extends UnitTestCase
37 {
38 /**
39 * @var ConditionMatcher
40 */
41 protected $subject;
42
43 /**
44 * @var string
45 */
46 protected $testGlobalNamespace;
47
48 /**
49 * @var bool Reset singletons
50 */
51 protected $resetSingletonInstances = true;
52
53 protected function setUp(): void
54 {
55 $GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
56 $cacheFrontendProphecy = $this->prophesize(FrontendInterface::class);
57 $cacheFrontendProphecy->has(Argument::any())->willReturn(false);
58 $cacheFrontendProphecy->set(Argument::any(), Argument::any())->willReturn(null);
59 $cacheManagerProphecy = $this->prophesize(CacheManager::class);
60 $cacheManagerProphecy->getCache('cache_core')->willReturn($cacheFrontendProphecy->reveal());
61 GeneralUtility::setSingletonInstance(CacheManager::class, $cacheManagerProphecy->reveal());
62
63 $packageManagerProphecy = $this->prophesize(PackageManager::class);
64 $corePackageProphecy = $this->prophesize(PackageInterface::class);
65 $corePackageProphecy->getPackagePath()->willReturn(__DIR__ . '/../../../../../../../sysext/core/');
66 $packageManagerProphecy->getActivePackages()->willReturn([
67 $corePackageProphecy->reveal()
68 ]);
69 GeneralUtility::setSingletonInstance(PackageManager::class, $packageManagerProphecy->reveal());
70
71 $this->testGlobalNamespace = $this->getUniqueId('TEST');
72 $GLOBALS[$this->testGlobalNamespace] = [];
73 $GLOBALS['TSFE'] = new \stdClass();
74 $GLOBALS['TSFE']->page = [];
75 $GLOBALS['TSFE']->tmpl = new \stdClass();
76 $GLOBALS['TSFE']->tmpl->rootLine = [
77 2 => ['uid' => 121, 'pid' => 111],
78 1 => ['uid' => 111, 'pid' => 101],
79 0 => ['uid' => 101, 'pid' => 0]
80 ];
81
82 $frontedUserAuthentication = $this->getMockBuilder(FrontendUserAuthentication::class)
83 ->setMethods(['dummy'])
84 ->getMock();
85
86 $frontedUserAuthentication->user['uid'] = 13;
87 $frontedUserAuthentication->groupData['uid'] = [14];
88 $GLOBALS['TSFE']->fe_user = $frontedUserAuthentication;
89 $this->getFreshConditionMatcher();
90 }
91
92 protected function getFreshConditionMatcher()
93 {
94 $this->subject = new ConditionMatcher(new Context([
95 'frontend.user' => new UserAspect($GLOBALS['TSFE']->fe_user)
96 ]));
97 $this->subject->setLogger($this->prophesize(Logger::class)->reveal());
98 }
99
100 /**
101 * Tests whether usergroup comparison matches.
102 *
103 * @test
104 */
105 public function usergroupConditionMatchesSingleGroupId(): void
106 {
107 $subject = new ConditionMatcher(new Context([
108 'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [13, 14, 15])
109 ]));
110 $loggerProphecy = $this->prophesize(Logger::class);
111 $subject->setLogger($loggerProphecy->reveal());
112 $this->assertTrue($subject->match('[usergroup(13)]'));
113 $this->assertTrue($subject->match('[usergroup("13")]'));
114 $this->assertTrue($subject->match('[usergroup(\'13\')]'));
115 }
116
117 /**
118 * Tests whether usergroup comparison matches.
119 *
120 * @test
121 */
122 public function usergroupConditionMatchesMultipleUserGroupId(): void
123 {
124 $subject = new ConditionMatcher(new Context([
125 'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [13, 14, 15])
126 ]));
127 $loggerProphecy = $this->prophesize(Logger::class);
128 $subject->setLogger($loggerProphecy->reveal());
129 $this->assertFalse($subject->match('[usergroup(999,15,14,13)]'));
130 $this->assertTrue($subject->match('[usergroup("999,15,14,13")]'));
131 $this->assertTrue($subject->match('[usergroup(\'999,15,14,13\')]'));
132 }
133
134 /**
135 * Tests whether usergroup comparison matches.
136 *
137 * @test
138 */
139 public function usergroupConditionDoesNotMatchDefaulUserGroupIds(): void
140 {
141 $subject = new ConditionMatcher(new Context([
142 'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [0, -1])
143 ]));
144 $loggerProphecy = $this->prophesize(Logger::class);
145 $subject->setLogger($loggerProphecy->reveal());
146 $this->assertFalse($subject->match('[usergroup("0,-1")]'));
147 $this->assertFalse($subject->match('[usergroup(\'0,-1\')]'));
148 }
149
150 /**
151 * Tests whether user comparison matches.
152 *
153 * @test
154 */
155 public function loginUserConditionMatchesAnyLoggedInUser(): void
156 {
157 $this->getFreshConditionMatcher();
158 $this->assertTrue($this->subject->match('[loginUser("*")]'));
159 $this->assertTrue($this->subject->match('[loginUser(\'*\')]'));
160 }
161
162 /**
163 * Tests whether user comparison matches.
164 *
165 * @test
166 */
167 public function loginUserConditionMatchesSingleLoggedInUser(): void
168 {
169 $this->getFreshConditionMatcher();
170 $this->assertTrue($this->subject->match('[loginUser(13)]'));
171 $this->assertTrue($this->subject->match('[loginUser("13")]'));
172 $this->assertTrue($this->subject->match('[loginUser(\'13\')]'));
173 }
174
175 /**
176 * Tests whether user comparison matches.
177 *
178 * @test
179 */
180 public function loginUserConditionMatchesMultipleLoggedInUsers(): void
181 {
182 $this->getFreshConditionMatcher();
183 $this->assertTrue($this->subject->match('[loginUser("999,13")]'));
184 $this->assertTrue($this->subject->match('[loginUser(\'999,13\')]'));
185 }
186
187 /**
188 * Tests whether user comparison matches.
189 *
190 * @test
191 */
192 public function loginUserConditionDoesNotMatchIfNotUserIsLoggedId(): void
193 {
194 $user = new FrontendUserAuthentication();
195 $user->user['uid'] = 13;
196 $subject = new ConditionMatcher(new Context([
197 'frontend.user' => new UserAspect($user)
198 ]));
199 $loggerProphecy = $this->prophesize(Logger::class);
200 $subject->setLogger($loggerProphecy->reveal());
201 $this->assertFalse($subject->match('[loginUser("*")]'));
202 $this->assertTrue($subject->match('[loginUser("*") == false]'));
203 $this->assertFalse($subject->match('[loginUser("13")]'));
204 $this->assertFalse($subject->match('[loginUser(\'*\')]'));
205 $this->assertFalse($subject->match('[loginUser(\'13\')]'));
206 }
207
208 /**
209 * Tests whether user is not logged in
210 *
211 * @test
212 */
213 public function loginUserConditionMatchIfUserIsNotLoggedIn(): void
214 {
215 $user = new FrontendUserAuthentication();
216 $subject = new ConditionMatcher(new Context([
217 'frontend.user' => new UserAspect($user)
218 ]));
219 $loggerProphecy = $this->prophesize(Logger::class);
220 $subject->setLogger($loggerProphecy->reveal());
221 $this->assertTrue($subject->match('[loginUser(\'*\') == false]'));
222 $this->assertTrue($subject->match('[loginUser("*") == false]'));
223 }
224
225 /**
226 * Tests whether treeLevel comparison matches.
227 *
228 * @test
229 */
230 public function treeLevelConditionMatchesSingleValue(): void
231 {
232 $this->assertTrue($this->subject->match('[tree.level == 2]'));
233 }
234
235 /**
236 * Tests whether treeLevel comparison matches.
237 *
238 * @test
239 */
240 public function treeLevelConditionMatchesMultipleValues(): void
241 {
242 $this->assertTrue($this->subject->match('[tree.level in [999,998,2]]'));
243 }
244
245 /**
246 * Tests whether treeLevel comparison matches.
247 *
248 * @test
249 */
250 public function treeLevelConditionDoesNotMatchFaultyValue(): void
251 {
252 $this->assertFalse($this->subject->match('[tree.level == 999]'));
253 }
254
255 /**
256 * Tests whether a page Id is found in the previous rootline entries.
257 *
258 * @test
259 */
260 public function PIDupinRootlineConditionMatchesSinglePageIdInRootline(): void
261 {
262 $GLOBALS['TSFE']->id = 121;
263 $this->getFreshConditionMatcher();
264 $this->assertTrue($this->subject->match('[111 in tree.rootLineIds]'));
265 $this->assertTrue($this->subject->match('["111" in tree.rootLineIds]'));
266 $this->assertTrue($this->subject->match('[\'111\' in tree.rootLineIds]'));
267 }
268
269 /**
270 * Tests whether a page Id is found in the previous rootline entries.
271 *
272 * @test
273 */
274 public function PIDupinRootlineConditionDoesNotMatchPageIdNotInRootline(): void
275 {
276 $GLOBALS['TSFE']->id = 121;
277 $this->getFreshConditionMatcher();
278 $this->assertFalse($this->subject->match('[999 in tree.rootLineIds]'));
279 }
280
281 /**
282 * Tests whether a page Id is found in all rootline entries.
283 *
284 * @test
285 */
286 public function PIDinRootlineConditionMatchesSinglePageIdInRootline(): void
287 {
288 $GLOBALS['TSFE']->id = 121;
289 $this->getFreshConditionMatcher();
290 $this->assertTrue($this->subject->match('[111 in tree.rootLineIds]'));
291 }
292
293 /**
294 * Tests whether a page Id is found in all rootline entries.
295 *
296 * @test
297 */
298 public function PIDinRootlineConditionMatchesLastPageIdInRootline(): void
299 {
300 $GLOBALS['TSFE']->id = 121;
301 $this->getFreshConditionMatcher();
302 $this->assertTrue($this->subject->match('[121 in tree.rootLineIds]'));
303 }
304
305 /**
306 * Tests whether a page Id is found in all rootline entries.
307 *
308 * @test
309 */
310 public function PIDinRootlineConditionDoesNotMatchPageIdNotInRootline(): void
311 {
312 $GLOBALS['TSFE']->id = 121;
313 $this->assertFalse($this->subject->match('[999 in tree.rootLineIds]'));
314 }
315
316 /**
317 * Tests whether the compatibility version can be evaluated.
318 * (e.g. 7.9 is compatible to 7.0 but not to 15.0)
319 *
320 * @test
321 */
322 public function compatVersionConditionMatchesOlderRelease(): void
323 {
324 $this->assertTrue($this->subject->match('[compatVersion(7.0)]'));
325 $this->assertTrue($this->subject->match('[compatVersion("7.0")]'));
326 $this->assertTrue($this->subject->match('[compatVersion(\'7.0\')]'));
327 }
328
329 /**
330 * Tests whether the compatibility version can be evaluated.
331 * (e.g. 7.9 is compatible to 7.0 but not to 15.0)
332 *
333 * @test
334 */
335 public function compatVersionConditionMatchesSameRelease(): void
336 {
337 $this->assertTrue($this->subject->match('[compatVersion(' . TYPO3_branch . ')]'));
338 }
339
340 /**
341 * Tests whether the compatibility version can be evaluated.
342 * (e.g. 7.9 is compatible to 7.0 but not to 15.0)
343 *
344 * @test
345 */
346 public function compatVersionConditionDoesNotMatchNewerRelease(): void
347 {
348 $this->assertFalse($this->subject->match('[compatVersion(15.0)]'));
349 $this->assertFalse($this->subject->match('[compatVersion("15.0")]'));
350 $this->assertFalse($this->subject->match('[compatVersion(\'15.0\')]'));
351 }
352
353 /**
354 * Tests whether the generic fetching of variables works with the namespace 'TSFE'.
355 *
356 * @test
357 */
358 public function genericGetVariablesSucceedsWithNamespaceTSFE(): void
359 {
360 $GLOBALS['TSFE']->id = 1234567;
361 $GLOBALS['TSFE']->testSimpleObject = new \stdClass();
362 $GLOBALS['TSFE']->testSimpleObject->testSimpleVariable = 'testValue';
363
364 $this->getFreshConditionMatcher();
365 $this->assertTrue($this->subject->match('[getTSFE().id == 1234567]'));
366 $this->assertTrue($this->subject->match('[getTSFE().testSimpleObject.testSimpleVariable == "testValue"]'));
367 }
368
369 /**
370 * Tests whether the generic fetching of variables works with the namespace 'session'.
371 *
372 * @test
373 */
374 public function genericGetVariablesSucceedsWithNamespaceSession(): void
375 {
376 $prophecy = $this->prophesize(FrontendUserAuthentication::class);
377 $prophecy->getSessionData(Argument::exact('foo'))->willReturn(['bar' => 1234567]);
378 $GLOBALS['TSFE']->fe_user = $prophecy->reveal();
379
380 $this->getFreshConditionMatcher();
381 $this->assertTrue($this->subject->match('[session("foo|bar") == 1234567]'));
382 }
383
384 /**
385 * Tests whether the generic fetching of variables works with the namespace 'ENV'.
386 *
387 * @test
388 */
389 public function genericGetVariablesSucceedsWithNamespaceENV(): void
390 {
391 $testKey = $this->getUniqueId('test');
392 putenv($testKey . '=testValue');
393 $this->getFreshConditionMatcher();
394 $this->assertTrue($this->subject->match('[getenv("' . $testKey . '") == "testValue"]'));
395 }
396
397 /**
398 * Tests whether any property of a site language matches the request
399 *
400 * @test
401 */
402 public function siteLanguageMatchesCondition(): void
403 {
404 $site = new Site('angelo', 13, [
405 'languages' => [
406 [
407 'languageId' => 0,
408 'title' => 'United States',
409 'locale' => 'en_US.UTF-8',
410 ],
411 [
412 'languageId' => 2,
413 'title' => 'UK',
414 'locale' => 'en_UK.UTF-8',
415 ]
416 ]
417 ]);
418 $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('language', $site->getLanguageById(0));
419 $this->getFreshConditionMatcher();
420 $this->assertTrue($this->subject->match('[siteLanguage("locale") == "en_US.UTF-8"]'));
421 $this->assertTrue($this->subject->match('[siteLanguage("locale") in ["de_DE", "en_US.UTF-8"]]'));
422 }
423
424 /**
425 * Tests whether any property of a site language does NOT match the request
426 *
427 * @test
428 */
429 public function siteLanguageDoesNotMatchCondition(): void
430 {
431 $site = new Site('angelo', 13, [
432 'languages' => [
433 [
434 'languageId' => 0,
435 'title' => 'United States',
436 'locale' => 'en_US.UTF-8',
437 ],
438 [
439 'languageId' => 2,
440 'title' => 'UK',
441 'locale' => 'en_UK.UTF-8',
442 ]
443 ]
444 ]);
445 $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('language', $site->getLanguageById(0));
446 $this->getFreshConditionMatcher();
447 $this->assertFalse($this->subject->match('[siteLanguage("locale") == "en_UK.UTF-8"]'));
448 $this->assertFalse($this->subject->match('[siteLanguage("locale") == "de_DE" && siteLanguage("title") == "UK"]'));
449 }
450
451 /**
452 * Tests whether any property of a site matches the request
453 *
454 * @test
455 */
456 public function siteMatchesCondition(): void
457 {
458 $site = new Site('angelo', 13, ['languages' => [], 'base' => 'https://typo3.org/']);
459 $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('site', $site);
460 $this->getFreshConditionMatcher();
461 $this->assertTrue($this->subject->match('[site("identifier") == "angelo"]'));
462 $this->assertTrue($this->subject->match('[site("rootPageId") == 13]'));
463 $this->assertTrue($this->subject->match('[site("base") == "https://typo3.org/"]'));
464 }
465
466 /**
467 * Tests whether any property of a site that does NOT match the request
468 *
469 * @test
470 */
471 public function siteDoesNotMatchCondition(): void
472 {
473 $site = new Site('angelo', 13, [
474 'languages' => [
475 [
476 'languageId' => 0,
477 'title' => 'United States',
478 'locale' => 'en_US.UTF-8',
479 ],
480 [
481 'languageId' => 2,
482 'title' => 'UK',
483 'locale' => 'en_UK.UTF-8',
484 ]
485 ]
486 ]);
487 $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('site', $site);
488 $this->getFreshConditionMatcher();
489 $this->assertFalse($this->subject->match('[site("identifier") == "berta"]'));
490 $this->assertFalse($this->subject->match('[site("rootPageId") == 14 && site("rootPageId") == 23]'));
491 }
492 }