39ef48c3f2aad690bac65a59dbd5516b79acd4f9
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Functional / SiteHandling / LinkGeneratorTest.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\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Core\Bootstrap;
20 use TYPO3\CMS\Core\TypoScript\TemplateService;
21 use TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\Fixtures\LinkGeneratorController;
22 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
23 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
24 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\ArrayValueInstruction;
25 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\TypoScriptInstruction;
26 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
27 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
28
29 /**
30 * Test case for frontend requests having site handling configured
31 */
32 class LinkGeneratorTest extends AbstractTestCase
33 {
34 /**
35 * @var string
36 */
37 private $siteTitle = 'A Company that Manufactures Everything Inc';
38
39 /**
40 * @var InternalRequestContext
41 */
42 private $internalRequestContext;
43
44 protected function setUp()
45 {
46 parent::setUp();
47
48 // these settings are forwarded to the frontend sub-request as well
49 $this->internalRequestContext = (new InternalRequestContext())
50 ->withGlobalSettings(['TYPO3_CONF_VARS' => static::TYPO3_CONF_VARS]);
51
52 $backendUser = $this->setUpBackendUserFromFixture(1);
53 Bootstrap::initializeLanguageObject();
54
55 $scenarioFile = __DIR__ . '/Fixtures/scenario.yaml';
56 $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
57 $writer = DataHandlerWriter::withBackendUser($backendUser);
58 $writer->invokeFactory($factory);
59 static::failIfArrayIsNotEmpty(
60 $writer->getErrors()
61 );
62
63 $this->setUpFrontendRootPage(
64 1000,
65 [
66 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkGenerator.typoscript',
67 ],
68 [
69 'title' => 'ACME Root',
70 'sitetitle' => $this->siteTitle,
71 ]
72 );
73 $this->setUpFrontendRootPage(
74 2000,
75 [
76 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkGenerator.typoscript',
77 ],
78 [
79 'title' => 'ACME Root',
80 'sitetitle' => $this->siteTitle,
81 ]
82 );
83
84 $this->writeSiteConfiguration(
85 'acme-com',
86 $this->buildSiteConfiguration(1000, 'https://acme.com/'),
87 [
88 $this->buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
89 $this->buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
90 $this->buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
91 ]
92 );
93 $this->writeSiteConfiguration(
94 'products-acme-com',
95 $this->buildSiteConfiguration(1300, 'https://products.acme.com/')
96 );
97 $this->writeSiteConfiguration(
98 'blog-acme-com',
99 $this->buildSiteConfiguration(2000, 'https://blog.acme.com/')
100 );
101 $this->writeSiteConfiguration(
102 'john-blog-acme-com',
103 $this->buildSiteConfiguration(2110, 'https://blog.acme.com/john/')
104 );
105 $this->writeSiteConfiguration(
106 'jane-blog-acme-com',
107 $this->buildSiteConfiguration(2120, 'https://blog.acme.com/jane/')
108 );
109 }
110
111 protected function tearDown()
112 {
113 unset($this->internalRequestContext);
114 parent::tearDown();
115 }
116
117 /**
118 * @return array
119 */
120 public function linkIsGeneratedDataProvider(): array
121 {
122 $instructions = [
123 // acme.com -> acme.com (same site)
124 [1100, 1000, '/?id=acme-root'],
125 [1100, 1100, '/?id=acme-first'],
126 [1100, 1200, '/?id=1200'],
127 [1100, 1210, '/?id=1210'],
128 [1100, 404, '/?id=404'],
129 // acme.com -> products.acme.com (nested sub-site)
130 [1100, 1300, '/?id=1300'],
131 [1100, 1310, '/?id=1310'],
132 // acme.com -> blog.acme.com (different site)
133 // @todo https://blog.acme.com/ not prefixed
134 [1100, 2000, '/?id=blog-root'],
135 [1100, 2100, '/?id=2100'],
136 [1100, 2110, '/john/?id=2110'],
137 [1100, 2111, '/john/?id=2111'],
138 // blog.acme.com -> acme.com (different site)
139 // @todo https://acme.com/ not prefixed
140 [2100, 1000, '/?id=acme-root'],
141 [2100, 1100, '/?id=acme-first'],
142 [2100, 1200, '/?id=1200'],
143 [2100, 1210, '/?id=1210'],
144 [2100, 404, '/?id=404'],
145 // blog.acme.com -> products.acme.com (different sub-site)
146 [2100, 1300, '/?id=1300'],
147 [2100, 1310, '/?id=1310'],
148 ];
149
150 return $this->keysFromTemplate(
151 $instructions,
152 '%1$d->%2$d'
153 );
154 }
155
156 /**
157 * @param int $sourcePageId
158 * @param int $targetPageId
159 * @param string $expectation
160 *
161 * @test
162 * @dataProvider linkIsGeneratedDataProvider
163 */
164 public function linkIsGenerated(int $sourcePageId, int $targetPageId, string $expectation)
165 {
166 $response = $this->executeFrontendRequest(
167 (new InternalRequest())
168 ->withPageId($sourcePageId)
169 ->withInstructions([
170 $this->createTypoLinkUrlInstruction([
171 'parameter' => $targetPageId,
172 ])
173 ]),
174 $this->internalRequestContext
175 );
176
177 static::assertSame($expectation, (string)$response->getBody());
178 }
179
180 /**
181 * @return array
182 */
183 public function linkIsGeneratedFromMountPointDataProvider(): array
184 {
185 $instructions = [
186 // acme.com -> acme.com (same site)
187 [[7100, 1700], 7110, 1000, '/?id=acme-root'],
188 [[7100, 1700], 7110, 1100, '/?id=acme-first'],
189 [[7100, 1700], 7110, 1200, '/?id=1200'],
190 [[7100, 1700], 7110, 1210, '/?id=1210'],
191 [[7100, 1700], 7110, 404, '/?id=404'],
192 // acme.com -> products.acme.com (nested sub-site)
193 [[7100, 1700], 7110, 1300, '/?id=1300'],
194 [[7100, 1700], 7110, 1310, '/?id=1310'],
195 // acme.com -> blog.acme.com (different site)
196 // @todo https://blog.acme.com/ not prefixed
197 [[7100, 1700], 7110, 2000, '/?id=blog-root'],
198 [[7100, 1700], 7110, 2100, '/?id=2100'],
199 [[7100, 1700], 7110, 2110, '/john/?id=2110'],
200 [[7100, 1700], 7110, 2111, '/john/?id=2111'],
201 // blog.acme.com -> acme.com (different site)
202 // @todo https://acme.com/ not prefixed
203 [[7100, 2700], 7110, 1000, '/?id=acme-root'],
204 [[7100, 2700], 7110, 1100, '/?id=acme-first'],
205 [[7100, 2700], 7110, 1200, '/?id=1200'],
206 [[7100, 2700], 7110, 1210, '/?id=1210'],
207 [[7100, 2700], 7110, 404, '/?id=404'],
208 // blog.acme.com -> products.acme.com (different sub-site)
209 [[7100, 2700], 7110, 1300, '/?id=1300'],
210 [[7100, 2700], 7110, 1310, '/?id=1310'],
211 ];
212
213 return $this->keysFromTemplate(
214 $instructions,
215 '%2$d->%3$d (mount:%1$s)',
216 function (array $items) {
217 array_splice(
218 $items,
219 0,
220 1,
221 [implode('->', $items[0])]
222 );
223 return $items;
224 }
225 );
226 }
227
228 /**
229 * @param array $pageMount
230 * @param int $sourcePageId
231 * @param int $targetPageId
232 * @param string $expectation
233 *
234 * @test
235 * @dataProvider linkIsGeneratedFromMountPointDataProvider
236 */
237 public function linkIsGeneratedFromMountPoint(array $pageMount, int $sourcePageId, int $targetPageId, string $expectation)
238 {
239 $response = $this->executeFrontendRequest(
240 (new InternalRequest())
241 ->withMountPoint(...$pageMount)
242 ->withPageId($sourcePageId)
243 ->withInstructions([
244 $this->createTypoLinkUrlInstruction([
245 'parameter' => $targetPageId,
246 ])
247 ]),
248 $this->internalRequestContext
249 );
250
251 static::assertSame($expectation, (string)$response->getBody());
252 }
253
254 /**
255 * @return array
256 */
257 public function linkIsGeneratedForLanguageDataProvider(): array
258 {
259 // @todo L-parameter is not applied
260 $instructions = [
261 // acme.com -> acme.com (same site)
262 [1100, 1100, 0, '/?id=acme-first'],
263 [1100, 1100, 1, '/?id=acme-first'],
264 [1100, 1100, 2, '/?id=acme-first'],
265 // @todo Configuration bug on duplicating alias names and uniqueness
266 [1100, 1101, 0, '/?id=acme-first0'],
267 [1100, 1102, 0, '/?id=acme-first1'],
268 // acme.com -> products.acme.com (nested sub-site)
269 [1100, 1300, 0, '/?id=1300'],
270 [1100, 1310, 0, '/?id=1310'],
271 // acme.com -> archive (outside site)
272 [1100, 3100, 0, 'index.php?id=3100&L=0'],
273 [1100, 3100, 1, 'index.php?id=3100&L=1'],
274 [1100, 3100, 2, 'index.php?id=3100&L=2'],
275 [1100, 3101, 0, 'index.php?id=3101&L=0'],
276 [1100, 3102, 0, 'index.php?id=3102&L=0'],
277 // blog.acme.com -> acme.com (different site)
278 // @todo https://acme.com/ not prefixed
279 [2100, 1100, 0, '/?id=acme-first'],
280 [2100, 1100, 1, '/?id=acme-first'],
281 [2100, 1100, 2, '/?id=acme-first'],
282 // @todo Configuration bug on duplicating alias names and uniqueness
283 [2100, 1101, 0, '/?id=acme-first0'],
284 [2100, 1102, 0, '/?id=acme-first1'],
285 // blog.acme.com -> archive (outside site)
286 [2100, 3100, 0, 'index.php?id=3100&L=0'],
287 [2100, 3100, 1, 'index.php?id=3100&L=1'],
288 [2100, 3100, 2, 'index.php?id=3100&L=2'],
289 [2100, 3101, 0, 'index.php?id=3101&L=0'],
290 [2100, 3102, 0, 'index.php?id=3102&L=0'],
291 // blog.acme.com -> products.acme.com (different sub-site)
292 [2100, 1300, 0, '/?id=1300'],
293 [2100, 1310, 0, '/?id=1310'],
294 ];
295
296 return $this->keysFromTemplate(
297 $instructions,
298 '%1$d->%2$d (lang:%3$d)'
299 );
300 }
301
302 /**
303 * @param int $sourcePageId
304 * @param int $targetPageId
305 * @param int $targetLanguageId
306 * @param string $expectation
307 *
308 * @test
309 * @dataProvider linkIsGeneratedForLanguageDataProvider
310 */
311 public function linkIsGeneratedForLanguage(int $sourcePageId, int $targetPageId, int $targetLanguageId, string $expectation)
312 {
313 $response = $this->executeFrontendRequest(
314 (new InternalRequest())
315 ->withPageId($sourcePageId)
316 ->withInstructions([
317 $this->createTypoLinkUrlInstruction([
318 'parameter' => $targetPageId,
319 'additionalParams' => '&L=' . $targetLanguageId,
320 ])
321 ]),
322 $this->internalRequestContext
323 );
324
325 static::assertSame($expectation, (string)$response->getBody());
326 }
327
328 /**
329 * @return array
330 */
331 public function linkIsGeneratedWithQueryParametersDataProvider(): array
332 {
333 $instructions = [
334 // acme.com -> acme.com (same site)
335 [1100, 1000, '/?id=acme-root&testing%5Bvalue%5D=1&cHash=7d1f13fa91159dac7feb3c824936b39d'],
336 [1100, 1100, '/?id=acme-first&testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
337 [1100, 1200, '/?id=1200&testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
338 [1100, 1210, '/?id=1210&testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
339 [1100, 404, '/?id=404&testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
340 // acme.com -> products.acme.com (nested sub-site)
341 [1100, 1300, '/?id=1300&testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
342 [1100, 1310, '/?id=1310&testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
343 // acme.com -> blog.acme.com (different site)
344 // @todo https://blog.acme.com/ not prefixed
345 [1100, 2000, '/?id=blog-root&testing%5Bvalue%5D=1&cHash=a14da633e46dba71640cb85226cd12c5'],
346 [1100, 2100, '/?id=2100&testing%5Bvalue%5D=1&cHash=d23d74cb50383f8788a9930ec8ba679f'],
347 [1100, 2110, '/john/?id=2110&testing%5Bvalue%5D=1&cHash=bf25eea89f44a9a79dabdca98f38a432'],
348 [1100, 2111, '/john/?id=2111&testing%5Bvalue%5D=1&cHash=42dbaeb9172b6b1ca23b49941e194db2'],
349 // blog.acme.com -> acme.com (different site)
350 // @todo https://acme.com/ not prefixed
351 [2100, 1000, '/?id=acme-root&testing%5Bvalue%5D=1&cHash=7d1f13fa91159dac7feb3c824936b39d'],
352 [2100, 1100, '/?id=acme-first&testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
353 [2100, 1200, '/?id=1200&testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
354 [2100, 1210, '/?id=1210&testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
355 [2100, 404, '/?id=404&testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
356 // blog.acme.com -> products.acme.com (different sub-site)
357 [2100, 1300, '/?id=1300&testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
358 [2100, 1310, '/?id=1310&testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
359 ];
360
361 return $this->keysFromTemplate(
362 $instructions,
363 '%1$d->%2$d'
364 );
365 }
366
367 /**
368 * @param int $sourcePageId
369 * @param int $targetPageId
370 * @param string $expectation
371 *
372 * @test
373 * @dataProvider linkIsGeneratedWithQueryParametersDataProvider
374 */
375 public function linkIsGeneratedWithQueryParameters(int $sourcePageId, int $targetPageId, string $expectation)
376 {
377 $response = $this->executeFrontendRequest(
378 (new InternalRequest())
379 ->withPageId($sourcePageId)
380 ->withInstructions([
381 $this->createTypoLinkUrlInstruction([
382 'parameter' => $targetPageId,
383 'additionalParams' => '&testing[value]=1',
384 'useCacheHash' => 1,
385 ])
386 ]),
387 $this->internalRequestContext
388 );
389
390 static::assertSame($expectation, (string)$response->getBody());
391 }
392
393 /**
394 * @return array
395 */
396 public function linkIsGeneratedForRestrictedPageDataProvider(): array
397 {
398 $instructions = [
399 [1100, 1510, 0, ''],
400 // [1100, 1511, 0, ''], // @todo Fails, not expanded to sub-pages
401 [1100, 1512, 0, ''],
402 [1100, 1515, 0, ''],
403 [1100, 1520, 0, ''],
404 // [1100, 1521, 0, ''], // @todo Fails, not expanded to sub-pages
405 //
406 [1100, 1510, 1, '/?id=1510'],
407 [1100, 1511, 1, '/?id=1511'],
408 [1100, 1512, 1, '/?id=1512'],
409 [1100, 1515, 1, ''],
410 [1100, 1520, 1, ''],
411 // [1100, 1521, 1, ''], // @todo Fails, not expanded to sub-pages
412 //
413 [1100, 1510, 2, '/?id=1510'],
414 [1100, 1511, 2, '/?id=1511'],
415 [1100, 1512, 2, ''],
416 [1100, 1515, 2, '/?id=1515'],
417 [1100, 1520, 2, '/?id=1520'],
418 [1100, 1521, 2, '/?id=1521'],
419 //
420 [1100, 1510, 3, '/?id=1510'],
421 [1100, 1511, 3, '/?id=1511'],
422 [1100, 1512, 3, '/?id=1512'],
423 [1100, 1515, 3, '/?id=1515'],
424 [1100, 1520, 3, '/?id=1520'],
425 [1100, 1521, 3, '/?id=1521'],
426 ];
427
428 return $this->keysFromTemplate(
429 $instructions,
430 '%1$d->%2$d (user:%3$d)'
431 );
432 }
433
434 /**
435 * @param int $sourcePageId
436 * @param int $targetPageId
437 * @param int $frontendUserId
438 * @param string $expectation
439 *
440 * @test
441 * @dataProvider linkIsGeneratedForRestrictedPageDataProvider
442 */
443 public function linkIsGeneratedForRestrictedPage(int $sourcePageId, int $targetPageId, int $frontendUserId, string $expectation)
444 {
445 $response = $this->executeFrontendRequest(
446 (new InternalRequest())
447 ->withPageId($sourcePageId)
448 ->withInstructions([
449 $this->createTypoLinkUrlInstruction([
450 'parameter' => $targetPageId,
451 ])
452 ]),
453 $this->internalRequestContext
454 ->withFrontendUserId($frontendUserId)
455 );
456
457 static::assertSame($expectation, (string)$response->getBody());
458 }
459
460 /**
461 * @return array
462 */
463 public function linkIsGeneratedForRestrictedPageUsingLoginPageDataProvider(): array
464 {
465 $instructions = [
466 // no frontend user given
467 [1100, 1510, 1500, 0, '/?id=1500&pageId=1510'],
468 // [1100, 1511, 1500, 0, '/?id=1500&pageId=1511'], // @todo Fails, not expanded to sub-pages
469 [1100, 1512, 1500, 0, '/?id=1500&pageId=1512'],
470 [1100, 1515, 1500, 0, '/?id=1500&pageId=1515'],
471 [1100, 1520, 1500, 0, '/?id=1500&pageId=1520'],
472 // [1100, 1521, 1500, 0, '/?id=1500&pageId=1521'], // @todo Fails, not expanded to sub-pages
473 // frontend user 1
474 [1100, 1510, 1500, 1, '/?id=1510'],
475 [1100, 1511, 1500, 1, '/?id=1511'],
476 [1100, 1512, 1500, 1, '/?id=1512'],
477 [1100, 1515, 1500, 1, '/?id=1500&pageId=1515'],
478 [1100, 1520, 1500, 1, '/?id=1500&pageId=1520'],
479 // [1100, 1521, 1500, 1, '/?id=1500&pageId=1521'], // @todo Fails, not expanded to sub-pages
480 // frontend user 2
481 [1100, 1510, 1500, 2, '/?id=1510'],
482 [1100, 1511, 1500, 2, '/?id=1511'],
483 [1100, 1512, 1500, 2, '/?id=1500&pageId=1512'],
484 [1100, 1515, 1500, 2, '/?id=1515'],
485 [1100, 1520, 1500, 2, '/?id=1520'],
486 [1100, 1521, 1500, 2, '/?id=1521'],
487 // frontend user 3
488 [1100, 1510, 1500, 3, '/?id=1510'],
489 [1100, 1511, 1500, 3, '/?id=1511'],
490 [1100, 1512, 1500, 3, '/?id=1512'],
491 [1100, 1515, 1500, 3, '/?id=1515'],
492 [1100, 1520, 1500, 3, '/?id=1520'],
493 [1100, 1521, 1500, 3, '/?id=1521'],
494 ];
495
496 return $this->keysFromTemplate(
497 $instructions,
498 '%1$d->%2$d (via: %3$d, user:%4$d)'
499 );
500 }
501
502 /**
503 * @param int $sourcePageId
504 * @param int $targetPageId
505 * @param int $loginPageId
506 * @param int $frontendUserId
507 * @param string $expectation
508 *
509 * @test
510 * @dataProvider linkIsGeneratedForRestrictedPageUsingLoginPageDataProvider
511 */
512 public function linkIsGeneratedForRestrictedPageUsingLoginPage(int $sourcePageId, int $targetPageId, int $loginPageId, int $frontendUserId, string $expectation)
513 {
514 $response = $this->executeFrontendRequest(
515 (new InternalRequest())
516 ->withPageId($sourcePageId)
517 ->withInstructions([
518 (new TypoScriptInstruction(TemplateService::class))
519 ->withTypoScript([
520 'config.' => [
521 'typolinkLinkAccessRestrictedPages' => $loginPageId,
522 'typolinkLinkAccessRestrictedPages_addParams' => '&pageId=###PAGE_ID###'
523 ],
524 ]),
525 $this->createTypoLinkUrlInstruction([
526 'parameter' => $targetPageId,
527 ])
528 ]),
529 $this->internalRequestContext
530 ->withFrontendUserId($frontendUserId)
531 );
532
533 static::assertSame($expectation, (string)$response->getBody());
534 }
535
536 public function linkIsGeneratedForPageVersionDataProvider(): array
537 {
538 // @todo Generation is not consistent "?id=" vs "index.php?id="
539 // -> most probably since pid=-1 is not correctly resolved
540 $instructions = [
541 // acme.com -> acme.com (same site)
542 [1100, 1100, false, '/?id=acme-first'],
543 [1100, 1100, true, 'index.php?id=acme-first'],
544 // [1100, 1950, false, '/?id=1950'], // @todo Not generated for new-placeholder
545 [1100, 1950, true, 'index.php?id={targetPageId}'],
546 // blog.acme.com -> acme.com (different site)
547 // @todo https://acme.com/ not prefixed
548 [2100, 1100, false, '/?id=acme-first'],
549 [2100, 1100, true, 'index.php?id=acme-first'],
550 // [2100, 1950, false, '/?id=1950'], // @todo Not generated for new-placeholder
551 [2100, 1950, true, 'index.php?id={targetPageId}'],
552 ];
553
554 return $this->keysFromTemplate(
555 $instructions,
556 '%1$d->%2$d (resolve:%3$d)'
557 );
558 }
559
560 /**
561 * @param int $sourcePageId
562 * @param int $targetPageId
563 * @param bool $resolveVersion
564 * @param string $expectation
565 *
566 * @test
567 * @dataProvider linkIsGeneratedForPageVersionDataProvider
568 */
569 public function linkIsGeneratedForPageVersion(int $sourcePageId, int $targetPageId, bool $resolveVersion, string $expectation)
570 {
571 $workspaceId = 1;
572 if ($resolveVersion) {
573 $targetPageId = BackendUtility::getWorkspaceVersionOfRecord(
574 $workspaceId,
575 'pages',
576 $targetPageId,
577 'uid'
578 )['uid'] ?? null;
579 }
580
581 $response = $this->executeFrontendRequest(
582 (new InternalRequest())
583 ->withPageId($sourcePageId)
584 ->withInstructions([
585 $this->createTypoLinkUrlInstruction([
586 'parameter' => $targetPageId,
587 ])
588 ]),
589 $this->internalRequestContext
590 ->withWorkspaceId($workspaceId)
591 );
592
593 $expectation = str_replace(
594 ['{targetPageId}'],
595 [$targetPageId],
596 $expectation
597 );
598
599 static::assertSame($expectation, (string)$response->getBody());
600 }
601
602 public function menuIsGeneratedDataProvider(): array
603 {
604 return [
605 'ACME Inc' => [
606 1100,
607 [
608 ['title' => 'EN: Welcome', 'link' => '/?id=acme-first'],
609 [
610 'title' => 'EN: Features',
611 'link' => '/?id=1200',
612 'children' => [
613 [
614 'title' => 'EN: Frontend Editing',
615 'link' => '/?id=1210',
616 ],
617 ],
618 ],
619 [
620 'title' => 'EN: Products',
621 'link' => '/?id=1300',
622 'children' => [
623 [
624 'title' => 'EN: Planets',
625 'link' => '/?id=1310',
626 ],
627 [
628 'title' => 'EN: Spaceships',
629 'link' => '/?id=1320',
630 ],
631 [
632 'title' => 'EN: Dark Matter',
633 'link' => '/?id=1330',
634 ],
635 ],
636 ],
637 ['title' => 'Internal', 'link' => '/?id=1500'],
638 ['title' => 'About us', 'link' => '/?id=1600'],
639 [
640 'title' => 'Announcements & News',
641 'link' => '/?id=1700',
642 'children' => [
643 [
644 'title' => 'Markets',
645 'link' => 'index.php?id=7110&MP=7100-1700',
646 ],
647 [
648 'title' => 'Products',
649 'link' => 'index.php?id=7120&MP=7100-1700',
650 ],
651 [
652 'title' => 'Partners',
653 'link' => 'index.php?id=7130&MP=7100-1700',
654 ],
655 ],
656 ],
657 ['title' => 'Page not found', 'link' => '/?id=404'],
658 // @todo Link should be prefixed with different site
659 ['title' => 'Our Blog', 'link' => '/?id=2100'],
660 ]
661 ],
662 'ACME Blog' => [
663 2100,
664 [
665 [
666 'title' => 'Authors',
667 'link' => '/?id=2100',
668 'children' => [
669 [
670 'title' => 'John Doe',
671 'link' => '/john/?id=2110',
672 ],
673 [
674 'title' => 'Jane Doe',
675 'link' => '/jane/?id=2120',
676 ],
677 ],
678 ],
679 1 =>
680 [
681 'title' => 'Announcements & News',
682 'link' => '/?id=2700',
683 'children' => [
684 [
685 'title' => 'Markets',
686 'link' => 'index.php?id=7110&MP=7100-2700',
687 ],
688 [
689 'title' => 'Products',
690 'link' => 'index.php?id=7120&MP=7100-2700',
691 ],
692 [
693 'title' => 'Partners',
694 'link' => 'index.php?id=7130&MP=7100-2700',
695 ],
696 ],
697 ],
698 // @todo Link should be prefixed with different site
699 ['title' => 'ACME Inc', 'link' => '/?id=acme-first'],
700 ]
701 ]
702 ];
703 }
704
705 /**
706 * @param int $sourcePageId
707 * @param array $expectation
708 *
709 * @test
710 * @dataProvider menuIsGeneratedDataProvider
711 */
712 public function menuIsGenerated(int $sourcePageId, array $expectation)
713 {
714 $response = $this->executeFrontendRequest(
715 (new InternalRequest())
716 ->withPageId($sourcePageId)
717 ->withInstructions([
718 $this->createMenuProcessorInstruction([
719 'levels' => 2,
720 'entryLevel' => 0,
721 'expandAll' => 1,
722 'includeSpacer' => 1,
723 'titleField' => 'title',
724 'as' => 'results',
725 ])
726 ]),
727 $this->internalRequestContext
728 );
729
730 $json = json_decode((string)$response->getBody(), true);
731 $json = $this->filterMenu($json);
732
733 static::assertSame($expectation, $json);
734 }
735
736 /**
737 * @param array $typoScript
738 * @return ArrayValueInstruction
739 */
740 private function createTypoLinkUrlInstruction(array $typoScript): ArrayValueInstruction
741 {
742 return (new ArrayValueInstruction(LinkGeneratorController::class))
743 ->withArray([
744 '10' => 'TEXT',
745 '10.' => [
746 'typolink.' => array_merge(
747 $typoScript,
748 ['returnLast' => 'url']
749 )
750 ]
751 ]);
752 }
753
754 /**
755 * @param array $typoScript
756 * @return ArrayValueInstruction
757 */
758 private function createMenuProcessorInstruction(array $typoScript): ArrayValueInstruction
759 {
760 return (new ArrayValueInstruction(LinkGeneratorController::class))
761 ->withArray([
762 '10' => 'FLUIDTEMPLATE',
763 '10.' => [
764 'file' => 'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/FluidJson.html',
765 'dataProcessing.' => [
766 '1' => 'TYPO3\\CMS\\Frontend\\DataProcessing\\MenuProcessor',
767 '1.' => $typoScript
768 ],
769 ],
770 ]);
771 }
772
773 /**
774 * Filters and keeps only desired names.
775 *
776 * @param array $menu
777 * @param array $keepNames
778 * @return array
779 */
780 private function filterMenu(
781 array $menu,
782 array $keepNames = ['title', 'link']
783 ): array {
784 if (!in_array('children', $keepNames)) {
785 $keepNames[] = 'children';
786 }
787 return array_map(
788 function (array $menuItem) use ($keepNames) {
789 $menuItem = array_filter(
790 $menuItem,
791 function (string $name) use ($keepNames) {
792 return in_array($name, $keepNames);
793 },
794 ARRAY_FILTER_USE_KEY
795 );
796 if (is_array($menuItem['children'] ?? null)) {
797 $menuItem['children'] = $this->filterMenu(
798 $menuItem['children'],
799 $keepNames
800 );
801 }
802 return $menuItem;
803 },
804 $menu
805 );
806 }
807 }