Revert "[TASK] Avoid slow array functions in loops"
[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 = array_merge(
507 $dataSet,
508 $this->createDataSet(
509 $aspect,
510 $enhancerLanguageUris,
511 $this->getEnhancers(['value' => $value], true),
512 'value',
513 ['prefix' => 'staticRangeMapper/', 'suffix' => sprintf(', value:%d', $value)]
514 )
515 );
516 }
517 return $dataSet;
518 }
519
520 /**
521 * @param array $enhancer
522 * @param string $targetUri
523 * @param int $expectedLanguageId
524 * @param array $expectation
525 *
526 * @test
527 * @dataProvider staticRangeMapperDataProvider
528 */
529 public function staticRangeMapperIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
530 {
531 $this->assertPageArgumentsEquals(
532 $enhancer,
533 $targetUri,
534 $expectedLanguageId,
535 $expectation
536 );
537 }
538
539 /**
540 * @return array
541 */
542 public function pageTypeDecoratorIsAppliedDataProvider(): array
543 {
544 $instructions = [
545 ['pathSuffix' => '.html', 'type' => null],
546 ['pathSuffix' => '.html', 'type' => 0],
547 ['pathSuffix' => '/menu.json', 'type' => 10],
548 ];
549
550 $dataSet = [];
551 foreach ($instructions as $instruction) {
552 $templateSuffix = sprintf(
553 ' [%s=>%s]',
554 $instruction['pathSuffix'],
555 $instruction['type'] ?? 'null'
556 );
557 $expectedPageType = (string)($instruction['type'] ?? 0);
558 $dataProviderOptions = [
559 'pathSuffix' => $instruction['pathSuffix'],
560 ];
561 $dataSetCandidates = array_merge(
562 $this->localeModifierDataProvider($dataProviderOptions),
563 $this->persistedAliasMapperDataProvider($dataProviderOptions),
564 $this->persistedPatternMapperDataProvider($dataProviderOptions),
565 $this->staticValueMapperDataProvider($dataProviderOptions),
566 $this->staticRangeMapperDataProvider($dataProviderOptions)
567 );
568 // add expected pageType to data set candidates
569 $dataSetCandidates = array_map(
570 function (array $dataSetCandidate) use ($expectedPageType) {
571 $dataSetCandidate[3]['pageType'] = $expectedPageType;
572 return $dataSetCandidate;
573 },
574 $dataSetCandidates
575 );
576 $dataSetCandidatesKeys = array_map(
577 function (string $dataSetCandidatesKey) use ($templateSuffix) {
578 return $dataSetCandidatesKey . $templateSuffix;
579 },
580 array_keys($dataSetCandidates)
581 );
582 $dataSet = array_merge(
583 $dataSet,
584 array_combine($dataSetCandidatesKeys, $dataSetCandidates)
585 );
586 }
587 return $dataSet;
588 }
589
590 /**
591 * @param array $enhancer
592 * @param string $targetUri
593 * @param int $expectedLanguageId
594 * @param array $expectation
595 *
596 * @test
597 * @dataProvider pageTypeDecoratorIsAppliedDataProvider
598 */
599 public function pageTypeDecoratorIsApplied(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
600 {
601 $this->mergeSiteConfiguration('acme-com', [
602 'routeEnhancers' => [
603 'Enhancer' => $enhancer,
604 'PageType' => $this->createPageTypeDecorator()
605 ]
606 ]);
607
608 $allParameters = array_replace_recursive(
609 $expectation['dynamicArguments'],
610 $expectation['staticArguments']
611 );
612 $expectation['pageId'] = 1100;
613 $expectation['languageId'] = $expectedLanguageId;
614 $expectation['requestQueryParams'] = $allParameters;
615 $expectation['_GET'] = $allParameters;
616
617 $response = $this->executeFrontendRequest(
618 new InternalRequest($targetUri),
619 $this->internalRequestContext,
620 true
621 );
622
623 $pageArguments = json_decode((string)$response->getBody(), true);
624 static::assertEquals($expectation, $pageArguments);
625 }
626
627 /**
628 * @param array $enhancer
629 * @param string $targetUri
630 * @param int $expectedLanguageId
631 * @param array $expectation
632 */
633 protected function assertPageArgumentsEquals(array $enhancer, string $targetUri, int $expectedLanguageId, array $expectation)
634 {
635 $this->mergeSiteConfiguration('acme-com', [
636 'routeEnhancers' => ['Enhancer' => $enhancer]
637 ]);
638
639 $allParameters = array_replace_recursive(
640 $expectation['dynamicArguments'],
641 $expectation['staticArguments']
642 );
643 $expectation['pageId'] = 1100;
644 $expectation['pageType'] = '0';
645 $expectation['languageId'] = $expectedLanguageId;
646 $expectation['requestQueryParams'] = $allParameters;
647 $expectation['_GET'] = $allParameters;
648
649 $response = $this->executeFrontendRequest(
650 new InternalRequest($targetUri),
651 $this->internalRequestContext,
652 true
653 );
654
655 $pageArguments = json_decode((string)$response->getBody(), true);
656 static::assertEquals($expectation, $pageArguments);
657 }
658 }