7d62cfb9e846457bb220d57c662e5d9454d834d9
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Functional / SiteHandling / EnhancerSiteRequestTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
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\Core\Bootstrap;
19 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
20 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
21 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
22 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
23
24 /**
25 * Test case for frontend requests having site handling configured using enhancers.
26 */
27 class EnhancerSiteRequestTest extends AbstractTestCase
28 {
29 /**
30 * @var string
31 */
32 private $siteTitle = 'A Company that Manufactures Everything Inc';
33
34 /**
35 * @var InternalRequestContext
36 */
37 private $internalRequestContext;
38
39 public static function setUpBeforeClass(): void
40 {
41 parent::setUpBeforeClass();
42 static::initializeDatabaseSnapshot();
43 }
44
45 public static function tearDownAfterClass(): void
46 {
47 static::destroyDatabaseSnapshot();
48 parent::tearDownAfterClass();
49 }
50
51 protected function setUp(): void
52 {
53 parent::setUp();
54
55 // these settings are forwarded to the frontend sub-request as well
56 $this->internalRequestContext = (new InternalRequestContext())
57 ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
58
59 $this->writeSiteConfiguration(
60 'acme-com',
61 $this->buildSiteConfiguration(1000, 'https://acme.com/'),
62 [
63 $this->buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
64 $this->buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
65 $this->buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
66 ]
67 );
68
69 $this->withDatabaseSnapshot(function () {
70 $this->setUpDatabase();
71 });
72 }
73
74 protected function setUpDatabase()
75 {
76 $backendUser = $this->setUpBackendUserFromFixture(1);
77 Bootstrap::initializeLanguageObject();
78
79 $scenarioFile = __DIR__ . '/Fixtures/SlugScenario.yaml';
80 $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
81 $writer = DataHandlerWriter::withBackendUser($backendUser);
82 $writer->invokeFactory($factory);
83 static::failIfArrayIsNotEmpty(
84 $writer->getErrors()
85 );
86
87 $this->setUpFrontendRootPage(
88 1000,
89 [
90 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkRequest.typoscript',
91 ],
92 [
93 'title' => 'ACME Root',
94 'sitetitle' => $this->siteTitle,
95 ]
96 );
97 }
98
99 protected function tearDown(): void
100 {
101 unset($this->internalRequestContext);
102 parent::tearDown();
103 }
104
105 /**
106 * @param array $aspect
107 * @param array $enhancerLanguageUris
108 * @param array $enhancers
109 * @param string $variableName
110 * @param array $templateOptions
111 * @return array
112 */
113 protected function createDataSet(
114 array $aspect,
115 array $enhancerLanguageUris,
116 array $enhancers,
117 string $variableName = 'value',
118 array $templateOptions = []
119 ): array {
120 $dataSet = [];
121 foreach ($enhancers as $enhancer) {
122 $enhancerType = $enhancer['enhancer']['type'];
123 foreach ($enhancerLanguageUris[$enhancerType] as $languageId => $uri) {
124 $expectation = $enhancer['arguments'];
125 $expectation['staticArguments'] = $expectation['staticArguments'] ?? [];
126 $expectation['dynamicArguments'] = $expectation['dynamicArguments'] ?? [];
127 $expectation['queryArguments'] = $expectation['queryArguments'] ?? [];
128 if (preg_match('#\?cHash=([a-z0-9]+)#i', $uri, $matches)) {
129 $expectation['dynamicArguments']['cHash'] = $matches[1];
130 $expectation['queryArguments']['cHash'] = $matches[1];
131 }
132 $dataSet[] = [
133 array_merge(
134 $enhancer['enhancer'],
135 ['aspects' => [$variableName => $aspect]]
136 ),
137 $uri,
138 $languageId,
139 $expectation,
140 ];
141 }
142 }
143 $templatePrefix = isset($templateOptions['prefix']) ? $templateOptions['prefix'] : '';
144 $templateSuffix = isset($templateOptions['suffix']) ? $templateOptions['suffix'] : '';
145 return $this->keysFromTemplate(
146 $dataSet,
147 $templatePrefix . 'enhancer:%1$s, lang:%3$d' . $templateSuffix,
148 function (array $items) {
149 array_splice(
150 $items,
151 0,
152 1,
153 $items[0]['type']
154 );
155 return $items;
156 }
157 );
158 }
159
160 /**
161 * @param array $options
162 * @param bool $isStatic
163 * @return array
164 */
165 protected function getEnhancers(array $options = [], bool $isStatic = false): array
166 {
167 $inArguments = $isStatic ? 'staticArguments' : 'dynamicArguments';
168 $options = array_merge(['name' => 'enhance', 'value' => 100], $options);
169 return [
170 [
171 'arguments' => [
172 $inArguments => [
173 'value' => (string)$options['value'],
174 ],
175 ],
176 'enhancer' => [
177 'type' => 'Simple',
178 'routePath' => sprintf('/%s/{value}', $options['name']),
179 '_arguments' => [],
180 ],
181 ],
182 [
183 'arguments' => [
184 $inArguments => [
185 'testing' => [
186 'value' => (string)$options['value'],
187 ],
188 ],
189 ],
190 'enhancer' => [
191 'type' => 'Plugin',
192 'routePath' => sprintf('/%s/{value}', $options['name']),
193 'namespace' => 'testing',
194 '_arguments' => [],
195 ],
196 ],
197 [
198 'arguments' => array_merge_recursive([
199 $inArguments => [
200 'tx_testing_link' => [
201 'value' => (string)$options['value'],
202 ],
203 ],
204 ], [
205 'staticArguments' => [
206 'tx_testing_link' => [
207 'controller' => 'Link',
208 'action' => 'index',
209 ],
210 ],
211 ]),
212 'enhancer' => [
213 'type' => 'Extbase',
214 'routes' => [
215 [
216 'routePath' => sprintf('/%s/{value}', $options['name']),
217 '_controller' => 'Link::index',
218 '_arguments' => [],
219 ],
220 ],
221 'extension' => 'testing',
222 'plugin' => 'link',
223 ],
224 ],
225 ];
226 }
227
228 /**
229 * @return array
230 */
231 protected function createPageTypeDecorator(): array
232 {
233 return [
234 'type' => 'PageType',
235 'default' => '.html',
236 'index' => 'index',
237 'map' => [
238 '.html' => 0,
239 'menu.json' => 10,
240 ]
241 ];
242 }
243
244 /**
245 * @param string|array|null $options
246 * @return array
247 */
248 public function localeModifierDataProvider($options = null): array
249 {
250 if (!is_array($options)) {
251 $options = [];
252 }
253 $aspect = [
254 'type' => 'LocaleModifier',
255 'default' => 'enhance',
256 'localeMap' => [
257 [
258 'locale' => 'fr_FR',
259 'value' => 'augmenter'
260 ]
261 ],
262 ];
263
264 $enhancerLanguageUris = [
265 'Simple' => [
266 '0' => 'https://acme.us/welcome/enhance/100%s?cHash=46227b4ce096dc78a4e71463326c9020',
267 '1' => 'https://acme.fr/bienvenue/augmenter/100%s?cHash=46227b4ce096dc78a4e71463326c9020',
268 ],
269 'Plugin' => [
270 '0' => 'https://acme.us/welcome/enhance/100%s?cHash=e24d3d2d5503baba670d827c3b9470c8',
271 '1' => 'https://acme.fr/bienvenue/augmenter/100%s?cHash=e24d3d2d5503baba670d827c3b9470c8',
272 ],
273 'Extbase' => [
274 '0' => 'https://acme.us/welcome/enhance/100%s?cHash=eef21771ab3c3dac3514b4479eedd5ff',
275 '1' => 'https://acme.fr/bienvenue/augmenter/100%s?cHash=eef21771ab3c3dac3514b4479eedd5ff',
276 ]
277 ];
278
279 $pathSuffix = $options['pathSuffix'] ?? '';
280 foreach ($enhancerLanguageUris as &$enhancerUris) {
281 $enhancerUris = array_map(
282 function (string $enhancerUri) use ($pathSuffix) {
283 return sprintf($enhancerUri, $pathSuffix);
284 },
285 $enhancerUris
286 );
287 }
288
289 return $this->createDataSet(
290 $aspect,
291 $enhancerLanguageUris,
292 $this->getEnhancers(['name' => '{enhance_name}']),
293 'enhance_name',
294 ['prefix' => 'localeModifier/']
295 );
296 }
297
298 /**
299 * @param array $enhancer
300 * @param string $targetUri
301 * @param int $expectedLanguageId
302 * @param array $expectation
303 *
304 * @test
305 * @dataProvider localeModifierDataProvider
306 */
307 public function localeModifierIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
308 {
309 $this->assertPageArgumentsEquals(
310 $enhancer,
311 $targetUri,
312 $expectedLanguageId,
313 $expectation
314 );
315 }
316
317 /**
318 * @param string|array|null $options
319 * @return array
320 */
321 public function persistedAliasMapperDataProvider($options = null): array
322 {
323 if (!is_array($options)) {
324 $options = [];
325 }
326 $aspect = [
327 'type' => 'PersistedAliasMapper',
328 'tableName' => 'pages',
329 'routeFieldName' => 'slug',
330 'routeValuePrefix' => '/',
331 ];
332
333 $enhancerLanguageUris = $this->populateToKeys(
334 ['Simple', 'Plugin', 'Extbase'],
335 [
336 '0' => sprintf('https://acme.us/welcome/enhance/welcome%s', $options['pathSuffix'] ?? ''),
337 '1' => sprintf('https://acme.fr/bienvenue/enhance/bienvenue%s', $options['pathSuffix'] ?? ''),
338 ]
339 );
340
341 return $this->createDataSet(
342 $aspect,
343 $enhancerLanguageUris,
344 $this->getEnhancers(['value' => 1100], true),
345 'value',
346 ['prefix' => 'persistedAliasMapper/']
347 );
348 }
349
350 /**
351 * @param array $enhancer
352 * @param string $targetUri
353 * @param int $expectedLanguageId
354 * @param array $expectation
355 *
356 * @test
357 * @dataProvider persistedAliasMapperDataProvider
358 */
359 public function persistedAliasMapperIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
360 {
361 $this->assertPageArgumentsEquals(
362 $enhancer,
363 $targetUri,
364 $expectedLanguageId,
365 $expectation
366 );
367 }
368
369 /**
370 * @param string|array|null $options
371 * @return array
372 */
373 public function persistedPatternMapperDataProvider($options = null): array
374 {
375 if (!is_array($options)) {
376 $options = [];
377 }
378 $aspect = [
379 'type' => 'PersistedPatternMapper',
380 'tableName' => 'pages',
381 'routeFieldPattern' => '^(?P<subtitle>.+)-(?P<uid>\d+)$',
382 'routeFieldResult' => '{subtitle}-{uid}',
383 ];
384
385 $enhancerLanguageUris = $this->populateToKeys(
386 ['Simple', 'Plugin', 'Extbase'],
387 [
388 '0' => sprintf('https://acme.us/welcome/enhance/hello-and-welcome-1100%s', $options['pathSuffix'] ?? ''),
389 '1' => sprintf('https://acme.fr/bienvenue/enhance/salut-et-bienvenue-1100%s', $options['pathSuffix'] ?? ''),
390 ]
391 );
392
393 return $this->createDataSet(
394 $aspect,
395 $enhancerLanguageUris,
396 $this->getEnhancers(['value' => 1100], true),
397 'value',
398 ['prefix' => 'persistedPatternMapper/']
399 );
400 }
401
402 /**
403 * @param array $enhancer
404 * @param string $targetUri
405 * @param int $expectedLanguageId
406 * @param array $expectation
407 *
408 * @test
409 * @dataProvider persistedPatternMapperDataProvider
410 */
411 public function persistedPatternMapperIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
412 {
413 $this->assertPageArgumentsEquals(
414 $enhancer,
415 $targetUri,
416 $expectedLanguageId,
417 $expectation
418 );
419 }
420
421 /**
422 * @param string|array|null $options
423 * @return array
424 */
425 public function staticValueMapperDataProvider($options = null): array
426 {
427 if (!is_array($options)) {
428 $options = [];
429 }
430 $aspect = [
431 'type' => 'StaticValueMapper',
432 'map' => [
433 'hundred' => 100,
434 ],
435 'localeMap' => [
436 [
437 'locale' => 'fr_FR',
438 'map' => [
439 'cent' => 100,
440 ],
441 ]
442 ],
443 ];
444
445 $enhancerLanguageUris = $this->populateToKeys(
446 ['Simple', 'Plugin', 'Extbase'],
447 [
448 '0' => sprintf('https://acme.us/welcome/enhance/hundred%s', $options['pathSuffix'] ?? ''),
449 '1' => sprintf('https://acme.fr/bienvenue/enhance/cent%s', $options['pathSuffix'] ?? ''),
450 ]
451 );
452
453 return $this->createDataSet(
454 $aspect,
455 $enhancerLanguageUris,
456 $this->getEnhancers([], true),
457 'value',
458 ['prefix' => 'staticValueMapper/']
459 );
460 }
461
462 /**
463 * @param array $enhancer
464 * @param string $targetUri
465 * @param int $expectedLanguageId
466 * @param array $expectation
467 *
468 * @test
469 * @dataProvider staticValueMapperDataProvider
470 */
471 public function staticValueMapperIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
472 {
473 $this->assertPageArgumentsEquals(
474 $enhancer,
475 $targetUri,
476 $expectedLanguageId,
477 $expectation
478 );
479 }
480
481 /**
482 * @param string|array|null $options
483 * @return array
484 */
485 public function staticRangeMapperDataProvider($options = null): array
486 {
487 if (!is_array($options)) {
488 $options = [];
489 }
490 $aspect = [
491 'type' => 'StaticRangeMapper',
492 'start' => '1',
493 'end' => '100',
494 ];
495
496 $dataSet = [[]];
497 foreach (range(10, 100, 30) as $value) {
498 $enhancerLanguageUris = $this->populateToKeys(
499 ['Simple', 'Plugin', 'Extbase'],
500 [
501 '0' => sprintf('https://acme.us/welcome/enhance/%s%s', $value, $options['pathSuffix'] ?? ''),
502 '1' => sprintf('https://acme.fr/bienvenue/enhance/%s%s', $value, $options['pathSuffix'] ?? ''),
503 ]
504 );
505
506 $dataSet[] = $this->createDataSet(
507 $aspect,
508 $enhancerLanguageUris,
509 $this->getEnhancers(['value' => $value], true),
510 'value',
511 ['prefix' => 'staticRangeMapper/', 'suffix' => sprintf(', value:%d', $value)]
512 );
513 }
514 return array_merge(...$dataSet);
515 }
516
517 /**
518 * @param array $enhancer
519 * @param string $targetUri
520 * @param int $expectedLanguageId
521 * @param array $expectation
522 *
523 * @test
524 * @dataProvider staticRangeMapperDataProvider
525 */
526 public function staticRangeMapperIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
527 {
528 $this->assertPageArgumentsEquals(
529 $enhancer,
530 $targetUri,
531 $expectedLanguageId,
532 $expectation
533 );
534 }
535
536 /**
537 * @return array
538 */
539 public function pageTypeDecoratorIsAppliedDataProvider(): array
540 {
541 $instructions = [
542 ['pathSuffix' => '.html', 'type' => null],
543 ['pathSuffix' => '.html', 'type' => 0],
544 ['pathSuffix' => '/menu.json', 'type' => 10],
545 ];
546
547 $dataSet = [[]];
548 foreach ($instructions as $instruction) {
549 $templateSuffix = sprintf(
550 ' [%s=>%s]',
551 $instruction['pathSuffix'],
552 $instruction['type'] ?? 'null'
553 );
554 $expectedPageType = (string)($instruction['type'] ?? 0);
555 $dataProviderOptions = [
556 'pathSuffix' => $instruction['pathSuffix'],
557 ];
558 $dataSetCandidates = array_merge(
559 $this->localeModifierDataProvider($dataProviderOptions),
560 $this->persistedAliasMapperDataProvider($dataProviderOptions),
561 $this->persistedPatternMapperDataProvider($dataProviderOptions),
562 $this->staticValueMapperDataProvider($dataProviderOptions),
563 $this->staticRangeMapperDataProvider($dataProviderOptions)
564 );
565 // add expected pageType to data set candidates
566 $dataSetCandidates = array_map(
567 function (array $dataSetCandidate) use ($expectedPageType) {
568 $dataSetCandidate[3]['pageType'] = $expectedPageType;
569 return $dataSetCandidate;
570 },
571 $dataSetCandidates
572 );
573 $dataSetCandidatesKeys = array_map(
574 function (string $dataSetCandidatesKey) use ($templateSuffix) {
575 return $dataSetCandidatesKey . $templateSuffix;
576 },
577 array_keys($dataSetCandidates)
578 );
579 $dataSet[] = array_combine($dataSetCandidatesKeys, $dataSetCandidates);
580 }
581 return array_merge(...$dataSet);
582 }
583
584 /**
585 * @param array $enhancer
586 * @param string $targetUri
587 * @param int $expectedLanguageId
588 * @param array $expectation
589 *
590 * @test
591 * @dataProvider pageTypeDecoratorIsAppliedDataProvider
592 */
593 public function pageTypeDecoratorIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
594 {
595 $this->mergeSiteConfiguration('acme-com', [
596 'routeEnhancers' => [
597 'Enhancer' => $enhancer,
598 'PageType' => $this->createPageTypeDecorator()
599 ]
600 ]);
601
602 $allParameters = array_replace_recursive(
603 $expectation['dynamicArguments'],
604 $expectation['staticArguments']
605 );
606 $expectation['pageId'] = 1100;
607 $expectation['languageId'] = $expectedLanguageId;
608 $expectation['requestQueryParams'] = $allParameters;
609 $expectation['_GET'] = $allParameters;
610
611 $response = $this->executeFrontendRequest(
612 new InternalRequest($targetUri),
613 $this->internalRequestContext,
614 true
615 );
616
617 $pageArguments = json_decode((string)$response->getBody(), true);
618 static::assertEquals($expectation, $pageArguments);
619 }
620
621 /**
622 * @param array $enhancer
623 * @param string $targetUri
624 * @param int $expectedLanguageId
625 * @param array $expectation
626 */
627 protected function assertPageArgumentsEquals(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
628 {
629 $this->mergeSiteConfiguration('acme-com', [
630 'routeEnhancers' => ['Enhancer' => $enhancer]
631 ]);
632
633 $allParameters = array_replace_recursive(
634 $expectation['dynamicArguments'],
635 $expectation['staticArguments']
636 );
637 $expectation['pageId'] = 1100;
638 $expectation['pageType'] = '0';
639 $expectation['languageId'] = $expectedLanguageId;
640 $expectation['requestQueryParams'] = $allParameters;
641 $expectation['_GET'] = $allParameters;
642
643 $response = $this->executeFrontendRequest(
644 new InternalRequest($targetUri),
645 $this->internalRequestContext,
646 true
647 );
648
649 $pageArguments = json_decode((string)$response->getBody(), true);
650 static::assertEquals($expectation, $pageArguments);
651 }
652 }