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