‪TYPO3CMS  11.5
SlugLinkGeneratorTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 
19 
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
25 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
26 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
27 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\ArrayValueInstruction;
28 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Internal\TypoScriptInstruction;
29 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
30 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
31 
36 {
37  protected function ‪setUp(): void
38  {
39  parent::setUp();
40 
42  'acme-com',
43  $this->‪buildSiteConfiguration(1000, 'https://acme.com/'),
44  [
45  $this->‪buildDefaultLanguageConfiguration('EN', 'https://acme.us/'),
46  $this->‪buildLanguageConfiguration('FR', 'https://acme.fr/', ['EN']),
47  $this->‪buildLanguageConfiguration('FR-CA', 'https://acme.ca/', ['FR', 'EN']),
48  ]
49  );
51  'products-acme-com',
52  $this->‪buildSiteConfiguration(1300, 'https://products.acme.com/')
53  );
55  'blog-acme-com',
56  $this->‪buildSiteConfiguration(2000, 'https://blog.acme.com/')
57  );
59  'john-blog-acme-com',
60  $this->‪buildSiteConfiguration(2110, 'https://blog.acme.com/john/')
61  );
63  'jane-blog-acme-com',
64  $this->‪buildSiteConfiguration(2120, 'https://blog.acme.com/jane/')
65  );
67  'archive-acme-com',
68  $this->‪buildSiteConfiguration(3000, 'https://archive.acme.com/'),
69  [
70  $this->‪buildDefaultLanguageConfiguration('EN', '/'),
71  $this->‪buildLanguageConfiguration('FR', 'https://archive.acme.com/fr/', ['EN']),
72  $this->‪buildLanguageConfiguration('FR-CA', 'https://archive.acme.com/ca/', ['FR', 'EN']),
73  ]
74  );
76  'common-collection',
77  $this->‪buildSiteConfiguration(7000, 'https://common.acme.com/')
78  );
80  'usual-collection',
81  $this->‪buildSiteConfiguration(8000, 'https://usual.acme.com/')
82  );
83 
84  $this->withDatabaseSnapshot(function () {
85  $this->‪setUpDatabase();
86  });
87 
88  // @todo: This is ugly: backend user and lang object are needed for DH db snapshot operations
89  // already, but also need to be setup properly for subsequent tests, so its done here
90  // a second time for the first test. This should be changed somehow.
91  $this->setUpBackendUser(1);
93  }
94 
95  protected function ‪setUpDatabase(): void
96  {
97  $backendUser = $this->setUpBackendUserFromFixture(1);
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  ]
116  );
117  $this->setUpFrontendRootPage(
118  2000,
119  [
120  'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/LinkGenerator.typoscript',
121  ],
122  [
123  'title' => 'ACME Blog',
124  ]
125  );
126  }
127 
131  public function ‪linkIsGeneratedDataProvider(): array
132  {
133  $instructions = [
134  // acme.com -> acme.com (same site)
135  ['https://acme.us/', 1100, 1000, '/welcome'], // shortcut page is resolved directly
136  ['https://acme.us/', 1100, 1100, '/welcome'],
137  ['https://acme.us/', 1100, 1200, '/features'],
138  ['https://acme.us/', 1100, 1210, '/features/frontend-editing/'],
139  ['https://acme.us/', 1100, 404, '/404'],
140  // acme.com -> products.acme.com (nested sub-site)
141  ['https://acme.us/', 1100, 1300, 'https://products.acme.com/products'],
142  ['https://acme.us/', 1100, 1310, 'https://products.acme.com/products/planets'],
143  // acme.com -> blog.acme.com (different site)
144  ['https://acme.us/', 1100, 2000, 'https://blog.acme.com/authors'], // recursive shortcut page is resolved directly
145  ['https://acme.us/', 1100, 2100, 'https://blog.acme.com/authors'],
146  ['https://acme.us/', 1100, 2110, 'https://blog.acme.com/john/john'],
147  ['https://acme.us/', 1100, 2111, 'https://blog.acme.com/john/about-john'],
148  // blog.acme.com -> acme.com (different site)
149  ['https://blog.acme.com/', 2100, 1000, 'https://acme.us/welcome'], // shortcut page is resolved directly
150  ['https://blog.acme.com/', 2100, 1100, 'https://acme.us/welcome'],
151  ['https://blog.acme.com/', 2100, 1200, 'https://acme.us/features'],
152  ['https://blog.acme.com/', 2100, 1210, 'https://acme.us/features/frontend-editing/'],
153  ['https://blog.acme.com/', 2100, 404, 'https://acme.us/404'],
154  // blog.acme.com -> products.acme.com (different sub-site)
155  ['https://blog.acme.com/', 2100, 1300, 'https://products.acme.com/products'],
156  ['https://blog.acme.com/', 2100, 1310, 'https://products.acme.com/products/planets'],
157  ];
158 
159  return $this->‪keysFromTemplate(
160  $instructions,
161  '%2$d->%3$d'
162  );
163  }
164 
174  public function ‪linkIsGenerated(string $hostPrefix, int $sourcePageId, int $targetPageId, string $expectation): void
175  {
176  $response = $this->executeFrontendSubRequest(
177  (new InternalRequest($hostPrefix))
178  ->withPageId($sourcePageId)
179  ->withInstructions([
181  'parameter' => $targetPageId,
182  ]),
183  ])
184  );
185 
186  self::assertSame($expectation, (string)$response->getBody());
187  }
188 
193  {
194  $instructions = [
195  // acme.com -> acme.com (same site)
196  ['https://acme.us/', [7100, 1700], 7110, 1000, '/welcome'], // shortcut page is resolved directly
197  ['https://acme.us/', [7100, 1700], 7110, 1100, '/welcome'],
198  ['https://acme.us/', [7100, 1700], 7110, 1200, '/features'],
199  ['https://acme.us/', [7100, 1700], 7110, 1210, '/features/frontend-editing/'],
200  ['https://acme.us/', [7100, 1700], 7110, 404, '/404'],
201  // acme.com -> products.acme.com (nested sub-site)
202  ['https://acme.us/', [7100, 1700], 7110, 1300, 'https://products.acme.com/products'],
203  ['https://acme.us/', [7100, 1700], 7110, 1310, 'https://products.acme.com/products/planets'],
204  // acme.com -> blog.acme.com (different site)
205  ['https://acme.us/', [7100, 1700], 7110, 2000, 'https://blog.acme.com/authors'], // shortcut page is resolved directly
206  ['https://acme.us/', [7100, 1700], 7110, 2100, 'https://blog.acme.com/authors'],
207  ['https://acme.us/', [7100, 1700], 7110, 2110, 'https://blog.acme.com/john/john'],
208  ['https://acme.us/', [7100, 1700], 7110, 2111, 'https://blog.acme.com/john/about-john'],
209  // blog.acme.com -> acme.com (different site)
210  ['https://blog.acme.com/', [7100, 2700], 7110, 1000, 'https://acme.us/welcome'], // shortcut page is resolved directly
211  ['https://blog.acme.com/', [7100, 2700], 7110, 1100, 'https://acme.us/welcome'],
212  ['https://blog.acme.com/', [7100, 2700], 7110, 1200, 'https://acme.us/features'],
213  ['https://blog.acme.com/', [7100, 2700], 7110, 1210, 'https://acme.us/features/frontend-editing/'],
214  ['https://blog.acme.com/', [7100, 2700], 7110, 404, 'https://acme.us/404'],
215  // blog.acme.com -> products.acme.com (different sub-site)
216  ['https://blog.acme.com/', [7100, 2700], 7110, 1300, 'https://products.acme.com/products'],
217  ['https://blog.acme.com/', [7100, 2700], 7110, 1310, 'https://products.acme.com/products/planets'],
218  ];
219 
220  return $this->‪keysFromTemplate(
221  $instructions,
222  '%3$d->%4$d (mount:%2$s)',
223  static function (array $items) {
224  array_splice(
225  $items,
226  1,
227  1,
228  [implode('->', $items[1])]
229  );
230  return $items;
231  }
232  );
233  }
234 
245  public function ‪linkIsGeneratedFromMountPoint(string $hostPrefix, array $pageMount, int $sourcePageId, int $targetPageId, string $expectation): void
246  {
247  $response = $this->executeFrontendSubRequest(
248  (new InternalRequest($hostPrefix))
249  ->withMountPoint(...$pageMount)
250  ->withPageId($sourcePageId)
251  ->withInstructions([
253  'parameter' => $targetPageId,
254  ]),
255  ])
256  );
257 
258  self::assertSame($expectation, (string)$response->getBody());
259  }
260 
265  {
266  $instructions = [
267  // acme.com -> acme.com (same site)
268  ['https://acme.us/', 1100, 1100, 0, '/welcome'],
269  ['https://acme.us/', 1100, 1100, 1, 'https://acme.fr/bienvenue'],
270  ['https://acme.us/', 1100, 1100, 2, 'https://acme.ca/bienvenue'],
271  ['https://acme.us/', 1100, 1101, 0, 'https://acme.fr/bienvenue'],
272  ['https://acme.us/', 1100, 1102, 0, 'https://acme.ca/bienvenue'],
273  // acme.com -> products.acme.com (nested sub-site)
274  ['https://acme.us/', 1100, 1300, 0, 'https://products.acme.com/products'],
275  ['https://acme.us/', 1100, 1310, 0, 'https://products.acme.com/products/planets'],
276  // acme.com -> products.acme.com (nested sub-site, l18n_cfg=1)
277  ['https://acme.us/', 1100, 1410, 0, ''],
278  ['https://acme.us/', 1100, 1410, 1, 'https://acme.fr/acme-dans-votre-region/groupes'],
279  ['https://acme.us/', 1100, 1410, 2, 'https://acme.ca/acme-dans-votre-quebec/groupes'],
280  ['https://acme.us/', 1100, 1411, 0, 'https://acme.fr/acme-dans-votre-region/groupes'],
281  ['https://acme.us/', 1100, 1412, 0, 'https://acme.ca/acme-dans-votre-quebec/groupes'],
282  // acme.com -> archive (outside site)
283  ['https://acme.us/', 1100, 3100, 0, 'https://archive.acme.com/statistics'],
284  ['https://acme.us/', 1100, 3100, 1, 'https://archive.acme.com/fr/statistics'],
285  ['https://acme.us/', 1100, 3100, 2, 'https://archive.acme.com/ca/statistics'],
286  ['https://acme.us/', 1100, 3101, 0, 'https://archive.acme.com/fr/statistics'],
287  ['https://acme.us/', 1100, 3102, 0, 'https://archive.acme.com/ca/statistics'],
288  // blog.acme.com -> acme.com (different site)
289  ['https://blog.acme.com/', 2100, 1100, 0, 'https://acme.us/welcome'],
290  ['https://blog.acme.com/', 2100, 1100, 1, 'https://acme.fr/bienvenue'],
291  ['https://blog.acme.com/', 2100, 1100, 2, 'https://acme.ca/bienvenue'],
292  ['https://blog.acme.com/', 2100, 1101, 0, 'https://acme.fr/bienvenue'],
293  ['https://blog.acme.com/', 2100, 1102, 0, 'https://acme.ca/bienvenue'],
294  // blog.acme.com -> archive (outside site)
295  ['https://blog.acme.com/', 2100, 3100, 0, 'https://archive.acme.com/statistics'],
296  ['https://blog.acme.com/', 2100, 3100, 1, 'https://archive.acme.com/fr/statistics'],
297  ['https://blog.acme.com/', 2100, 3100, 2, 'https://archive.acme.com/ca/statistics'],
298  ['https://blog.acme.com/', 2100, 3101, 0, 'https://archive.acme.com/fr/statistics'],
299  ['https://blog.acme.com/', 2100, 3102, 0, 'https://archive.acme.com/ca/statistics'],
300  // blog.acme.com -> products.acme.com (different sub-site)
301  ['https://blog.acme.com/', 2100, 1300, 0, 'https://products.acme.com/products'],
302  ['https://blog.acme.com/', 2100, 1310, 0, 'https://products.acme.com/products/planets'],
303  ];
304 
305  return $this->‪keysFromTemplate(
306  $instructions,
307  '%2$d->%3$d (lang:%4$d)'
308  );
309  }
310 
321  public function ‪linkIsGeneratedForLanguageWithLanguageProperty(string $hostPrefix, int $sourcePageId, int $targetPageId, int $targetLanguageId, string $expectation): void
322  {
323  $response = $this->executeFrontendSubRequest(
324  (new InternalRequest($hostPrefix))
325  ->withPageId($sourcePageId)
326  ->withInstructions([
328  'parameter' => $targetPageId,
329  'language' => $targetLanguageId,
330  ]),
331  ])
332  );
333 
334  self::assertSame($expectation, (string)$response->getBody());
335  }
336 
341  {
342  $instructions = [
343  // acme.com -> acme.com (same site)
344  ['https://acme.us/', 1100, 1000, '/welcome?testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'], // shortcut page is resolved directly
345  ['https://acme.us/', 1100, 1100, '/welcome?testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
346  ['https://acme.us/', 1100, 1200, '/features?testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
347  ['https://acme.us/', 1100, 1210, '/features/frontend-editing/?testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
348  ['https://acme.us/', 1100, 404, '/404?testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
349  // acme.com -> products.acme.com (nested sub-site)
350  ['https://acme.us/', 1100, 1300, 'https://products.acme.com/products?testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
351  ['https://acme.us/', 1100, 1310, 'https://products.acme.com/products/planets?testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
352  // acme.com -> blog.acme.com (different site)
353  ['https://acme.us/', 1100, 2000, 'https://blog.acme.com/authors?testing%5Bvalue%5D=1&cHash=d23d74cb50383f8788a9930ec8ba679f'], // shortcut page is resolved directly
354  ['https://acme.us/', 1100, 2100, 'https://blog.acme.com/authors?testing%5Bvalue%5D=1&cHash=d23d74cb50383f8788a9930ec8ba679f'],
355  ['https://acme.us/', 1100, 2110, 'https://blog.acme.com/john/john?testing%5Bvalue%5D=1&cHash=bf25eea89f44a9a79dabdca98f38a432'],
356  ['https://acme.us/', 1100, 2111, 'https://blog.acme.com/john/about-john?testing%5Bvalue%5D=1&cHash=42dbaeb9172b6b1ca23b49941e194db2'],
357  // blog.acme.com -> acme.com (different site)
358  ['https://blog.acme.com/', 2100, 1000, 'https://acme.us/welcome?testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'], // shortcut page is resolved directly
359  ['https://blog.acme.com/', 2100, 1100, 'https://acme.us/welcome?testing%5Bvalue%5D=1&cHash=f42b850e435f0cedd366f5db749fc1af'],
360  ['https://blog.acme.com/', 2100, 1200, 'https://acme.us/features?testing%5Bvalue%5D=1&cHash=784e11c50ea1a13fd7d969df4ec53ea3'],
361  ['https://blog.acme.com/', 2100, 1210, 'https://acme.us/features/frontend-editing/?testing%5Bvalue%5D=1&cHash=ccb7067022b9835ebfd8f720722bc708'],
362  ['https://blog.acme.com/', 2100, 404, 'https://acme.us/404?testing%5Bvalue%5D=1&cHash=864e96f586a78a53452f3bf0f4d24591'],
363  // blog.acme.com -> products.acme.com (different sub-site)
364  ['https://blog.acme.com/', 2100, 1300, 'https://products.acme.com/products?testing%5Bvalue%5D=1&cHash=dbd6597d72ed5098cce3d03eac1eeefe'],
365  ['https://blog.acme.com/', 2100, 1310, 'https://products.acme.com/products/planets?testing%5Bvalue%5D=1&cHash=e64bfc7ab7dd6b70d161e4d556be9726'],
366  ];
367 
368  return $this->‪keysFromTemplate(
369  $instructions,
370  '%2$d->%3$d'
371  );
372  }
373 
383  public function ‪linkIsGeneratedWithQueryParameters(string $hostPrefix, int $sourcePageId, int $targetPageId, string $expectation): void
384  {
385  $response = $this->executeFrontendSubRequest(
386  (new InternalRequest($hostPrefix))
387  ->withPageId($sourcePageId)
388  ->withInstructions([
390  'parameter' => $targetPageId,
391  'additionalParams' => '&testing[value]=1',
392  ]),
393  ])
394  );
395 
396  self::assertSame($expectation, (string)$response->getBody());
397  }
398 
403  {
404  $instructions = [
405  ['https://acme.us/', 1100, 1510, 0, ''],
406  // ['https://acme.us/', 1100, 1511, 0, ''], // @todo Fails, not expanded to sub-pages
407  ['https://acme.us/', 1100, 1512, 0, ''],
408  ['https://acme.us/', 1100, 1515, 0, ''],
409  ['https://acme.us/', 1100, 1520, 0, ''],
410  // ['https://acme.us/', 1100, 1521, 0, ''], // @todo Fails, not expanded to sub-pages
411  //
412  ['https://acme.us/', 1100, 1510, 1, '/my-acme/whitepapers'],
413  ['https://acme.us/', 1100, 1511, 1, '/my-acme/whitepapers/products'],
414  ['https://acme.us/', 1100, 1512, 1, '/my-acme/whitepapers/solutions'],
415  ['https://acme.us/', 1100, 1515, 1, ''],
416  ['https://acme.us/', 1100, 1520, 1, ''],
417  // ['https://acme.us/', 1100, 1521, 1, ''], // @todo Fails, not expanded to sub-pages
418  //
419  ['https://acme.us/', 1100, 1510, 2, '/my-acme/whitepapers'],
420  ['https://acme.us/', 1100, 1511, 2, '/my-acme/whitepapers/products'],
421  ['https://acme.us/', 1100, 1512, 2, ''],
422  ['https://acme.us/', 1100, 1515, 2, '/my-acme/whitepapers/research'],
423  ['https://acme.us/', 1100, 1520, 2, '/my-acme/forecasts'],
424  ['https://acme.us/', 1100, 1521, 2, '/my-acme/forecasts/current-year'],
425  //
426  ['https://acme.us/', 1100, 1510, 3, '/my-acme/whitepapers'],
427  ['https://acme.us/', 1100, 1511, 3, '/my-acme/whitepapers/products'],
428  ['https://acme.us/', 1100, 1512, 3, '/my-acme/whitepapers/solutions'],
429  ['https://acme.us/', 1100, 1515, 3, '/my-acme/whitepapers/research'],
430  ['https://acme.us/', 1100, 1520, 3, '/my-acme/forecasts'],
431  ['https://acme.us/', 1100, 1521, 3, '/my-acme/forecasts/current-year'],
432  ];
433 
434  return $this->‪keysFromTemplate(
435  $instructions,
436  '%2$d->%3$d (user:%4$d)'
437  );
438  }
439 
450  public function ‪linkIsGeneratedForRestrictedPage(string $hostPrefix, int $sourcePageId, int $targetPageId, int $frontendUserId, string $expectation): void
451  {
452  $response = $this->executeFrontendSubRequest(
453  (new InternalRequest($hostPrefix))
454  ->withPageId($sourcePageId)
455  ->withInstructions([
457  'parameter' => $targetPageId,
458  ]),
459  ]),
460  (new InternalRequestContext())->withFrontendUserId($frontendUserId)
461  );
462 
463  self::assertSame($expectation, (string)$response->getBody());
464  }
465 
470  {
471  $instructions = [
472  // no frontend user given
473  ['https://acme.us/', 1100, 1510, 1500, 0, '/my-acme?pageId=1510&cHash=119c4870e323bb7e8c9fae2941726b0d'],
474  // ['https://acme.us/', 1100, 1511, 1500, 0, '/my-acme?pageId=1511'], // @todo Fails, not expanded to sub-pages
475  ['https://acme.us/', 1100, 1512, 1500, 0, '/my-acme?pageId=1512&cHash=0ced3db0fd4aae0019a99f59cfa58cb0'],
476  ['https://acme.us/', 1100, 1515, 1500, 0, '/my-acme?pageId=1515&cHash=176f16b31d2c731347d411861d8b06dc'],
477  ['https://acme.us/', 1100, 1520, 1500, 0, '/my-acme?pageId=1520&cHash=253d3dccd4794c4a9473226f683bc36a'],
478  // ['https://acme.us/', 1100, 1521, 1500, 0, '/my-acme?pageId=1521'], // @todo Fails, not expanded to sub-pages
479  // frontend user 1
480  ['https://acme.us/', 1100, 1510, 1500, 1, '/my-acme/whitepapers'],
481  ['https://acme.us/', 1100, 1511, 1500, 1, '/my-acme/whitepapers/products'],
482  ['https://acme.us/', 1100, 1512, 1500, 1, '/my-acme/whitepapers/solutions'],
483  ['https://acme.us/', 1100, 1515, 1500, 1, '/my-acme?pageId=1515&cHash=176f16b31d2c731347d411861d8b06dc'],
484  ['https://acme.us/', 1100, 1520, 1500, 1, '/my-acme?pageId=1520&cHash=253d3dccd4794c4a9473226f683bc36a'],
485  // ['https://acme.us/', 1100, 1521, 1500, 1, '/my-acme?pageId=1521'], // @todo Fails, not expanded to sub-pages
486  // frontend user 2
487  ['https://acme.us/', 1100, 1510, 1500, 2, '/my-acme/whitepapers'],
488  ['https://acme.us/', 1100, 1511, 1500, 2, '/my-acme/whitepapers/products'],
489  ['https://acme.us/', 1100, 1512, 1500, 2, '/my-acme?pageId=1512&cHash=0ced3db0fd4aae0019a99f59cfa58cb0'],
490  ['https://acme.us/', 1100, 1515, 1500, 2, '/my-acme/whitepapers/research'],
491  ['https://acme.us/', 1100, 1520, 1500, 2, '/my-acme/forecasts'],
492  ['https://acme.us/', 1100, 1521, 1500, 2, '/my-acme/forecasts/current-year'],
493  // frontend user 3
494  ['https://acme.us/', 1100, 1510, 1500, 3, '/my-acme/whitepapers'],
495  ['https://acme.us/', 1100, 1511, 1500, 3, '/my-acme/whitepapers/products'],
496  ['https://acme.us/', 1100, 1512, 1500, 3, '/my-acme/whitepapers/solutions'],
497  ['https://acme.us/', 1100, 1515, 1500, 3, '/my-acme/whitepapers/research'],
498  ['https://acme.us/', 1100, 1520, 1500, 3, '/my-acme/forecasts'],
499  ['https://acme.us/', 1100, 1521, 1500, 3, '/my-acme/forecasts/current-year'],
500  ];
501 
502  return $this->‪keysFromTemplate(
503  $instructions,
504  '%2$d->%3$d (via: %4$d, user:%5$d)'
505  );
506  }
507 
519  public function ‪linkIsGeneratedForRestrictedPageUsingLoginPage(string $hostPrefix, int $sourcePageId, int $targetPageId, int $loginPageId, int $frontendUserId, string $expectation): void
520  {
521  $response = $this->executeFrontendSubRequest(
522  (new InternalRequest($hostPrefix))
523  ->withPageId($sourcePageId)
524  ->withInstructions([
525  (new TypoScriptInstruction(TemplateService::class))
526  ->withTypoScript([
527  'config.' => [
528  'typolinkLinkAccessRestrictedPages' => $loginPageId,
529  'typolinkLinkAccessRestrictedPages_addParams' => '&pageId=###PAGE_ID###',
530  ],
531  ]),
533  'parameter' => $targetPageId,
534  ]),
535  ]),
536  (new InternalRequestContext())->withFrontendUserId($frontendUserId)
537  );
538 
539  self::assertSame($expectation, (string)$response->getBody());
540  }
541 
546  {
547  $instructions = [
548  // default language (0)
549  ['https://acme.us/', 1100, 1510, 0, '/my-acme/whitepapers'],
550  ['https://acme.us/', 1100, 1512, 0, '/my-acme/whitepapers/solutions'],
551  ['https://acme.us/', 1100, 1515, 0, '/my-acme/whitepapers/research'],
552  // french language (1)
553  ['https://acme.fr/', 1100, 1510, 1, '/my-acme/papiersblanc'],
554  ['https://acme.fr/', 1100, 1512, 1, '/my-acme/papiersblanc/la-solutions'],
555  ['https://acme.fr/', 1100, 1515, 1, '/my-acme/papiersblanc/recherche'],
556  // canadian french language (2)
557  ['https://acme.ca/', 1100, 1510, 2, '/my-acme-ca/papiersblanc'],
558  ['https://acme.ca/', 1100, 1512, 2, '/my-acme-ca/papiersblanc/la-solutions'],
559  ['https://acme.ca/', 1100, 1515, 2, '/my-acme-ca/papiersblanc/recherche'],
560  ];
561 
562  return $this->‪keysFromTemplate(
563  $instructions,
564  '%2$d->%3$d (language: %4$d)'
565  );
566  }
567 
578  public function ‪linkIsGeneratedForRestrictedPageForGuestsUsingTypolinkLinkAccessRestrictedPages(string $hostPrefix, int $sourcePageId, int $targetPageId, int $languageId, string $expectation): void
579  {
580  $response = $this->executeFrontendSubRequest(
581  (new InternalRequest($hostPrefix))
582  ->withPageId($sourcePageId)
583  ->withInstructions([
584  (new TypoScriptInstruction(TemplateService::class))
585  ->withTypoScript([
586  'config.' => [
587  'typolinkLinkAccessRestrictedPages' => 'NONE',
588  ],
589  ]),
591  'parameter' => $targetPageId,
592  ]),
593  ])
594  );
595 
596  self::assertSame($expectation, (string)$response->getBody());
597  }
598 
603  {
604  $instructions = [
605  // acme.com -> acme.com (same site): link to changed page
606  ['https://acme.us/', 1100, 1100, false, 1, '/welcome-modified'],
607  ['https://acme.us/', 1100, 1100, true, 1, '/welcome-modified'],
608  ['https://acme.us/', 1100, 1100, false, 0, '/welcome'],
609  ['https://acme.us/', 1100, 1100, true, 0, ''], // @todo link is empty, but should create a link
610  // acme.com -> acme.com (same site): link to new page (no need to resolve the version for the new page)
611  ['https://acme.us/', 1100, 1950, false, 1, '/bye'],
612  ['https://acme.us/', 1100, 1950, false, 0, ''],
613  // blog.acme.com -> acme.com (different site): link to changed page
614  ['https://blog.acme.com/', 2100, 1100, true, 1, 'https://acme.us/welcome-modified'],
615  ['https://blog.acme.com/', 2100, 1100, false, 1, 'https://acme.us/welcome-modified'],
616  ['https://blog.acme.com/', 2100, 1100, false, 0, 'https://acme.us/welcome'],
617  ['https://blog.acme.com/', 2100, 1100, true, 0, ''], // @todo link is empty, but should create a link
618  // blog.acme.com -> acme.com (different site): link to new page (no need to resolve the version for the new page)
619  ['https://blog.acme.com/', 2100, 1950, false, 1, 'https://acme.us/bye'],
620  ['https://blog.acme.com/', 2100, 1950, false, 0, ''],
621  ];
622 
623  return $this->‪keysFromTemplate(
624  $instructions,
625  '%2$d->%3$d (resolve:%4$d, be_user:%5$d)'
626  );
627  }
628 
640  public function ‪linkIsGeneratedForPageVersion(string $hostPrefix, int $sourcePageId, int $targetPageId, bool $resolveVersion, int $backendUserId, string $expectation): void
641  {
642  $workspaceId = 1;
643  if ($resolveVersion) {
644  $targetPageId = BackendUtility::getWorkspaceVersionOfRecord(
645  $workspaceId,
646  'pages',
647  $targetPageId,
648  'uid'
649  )['uid'] ?? null;
650  $targetPageId = $targetPageId ? (int)$targetPageId : null;
651  }
652 
653  $response = $this->executeFrontendSubRequest(
654  (new InternalRequest($hostPrefix))
655  ->withPageId($sourcePageId)
656  ->withInstructions([
658  'parameter' => $targetPageId,
659  ]),
660  ]),
661  (new InternalRequestContext())
662  ->withWorkspaceId($backendUserId !== 0 ? $workspaceId : 0)
663  ->withBackendUserId($backendUserId)
664  );
665 
666  $expectation = str_replace(
667  ['{targetPageId}'],
668  [$targetPageId],
669  $expectation
670  );
671 
672  self::assertSame($expectation, (string)$response->getBody());
673  }
674 
679  {
680  $response = $this->executeFrontendSubRequest(
681  (new InternalRequest('https://blog.acme.com/'))
682  ->withPageId(2100)
683  ->withInstructions([
684  (new TypoScriptInstruction(TemplateService::class))
685  ->withTypoScript([
686  'config.' => [
687  'spamProtectEmailAddresses' => 'ascii',
688  ],
689  ]),
690  (new ArrayValueInstruction(LinkHandlingController::class))
691  ->withArray([
692  '10' => 'TEXT',
693  '10.' => [
694  'typolink.' => [
695  'parameter' => 2910,
696  ],
697  ],
698  ]),
699  ]),
700  );
701 
702  $result = (string)$response->getBody();
703  $expectation = '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#121;&#111;&#117;&#45;&#110;&#101;&#118;&#101;&#114;&#45;&#103;&#101;&#116;&#45;&#97;&#110;&#45;&#97;&#110;&#115;&#119;&#101;&#114;">Contact wikipedia</a>';
704  self::assertSame($expectation, $result);
705  }
706 
711  {
712  return [
713  'ACME Inc' => [
714  'https://acme.us/',
715  1100,
716  [
717  ['title' => 'EN: Welcome', 'link' => '/welcome', 'target' => ''],
718  [
719  'title' => 'ZH-CN: Welcome Default',
720  // Symfony UrlGenerator, which is used for uri generation, rawurlencodes the url internally.
721  'link' => '/%E7%AE%80-bienvenue',
722  'target' => '',
723  ],
724  [
725  'title' => 'EN: Features',
726  'link' => '/features',
727  'target' => '',
728  'children' => [
729  [
730  'title' => 'EN: Frontend Editing',
731  'link' => '/features/frontend-editing/',
732  'target' => '',
733  ],
734  ],
735  ],
736  [
737  'title' => 'EN: Products',
738  'link' => 'https://products.acme.com/products',
739  'target' => '',
740  'children' => [
741  [
742  'title' => 'EN: Planets',
743  'link' => 'https://products.acme.com/products/planets',
744  'target' => '',
745  ],
746  [
747  'title' => 'EN: Spaceships',
748  'link' => 'https://products.acme.com/products/spaceships',
749  'target' => '',
750  ],
751  [
752  'title' => 'EN: Dark Matter',
753  'link' => 'https://products.acme.com/products/dark-matter',
754  'target' => '',
755  ],
756  ],
757  ],
758  ['title' => 'EN: ACME in your Region', 'link' => '/acme-in-your-region', 'target' => ''],
759  [
760  'title' => 'Divider',
761  'link' => '/divider',
762  'target' => '',
763  'children' => [
764  [
765  'title' => 'EN: Subpage of Spacer',
766  'link' => '/divider/subpage-of-spacer',
767  'target' => '',
768  ],
769  ],
770  ],
771  ['title' => 'Internal', 'link' => '/my-acme', 'target' => ''],
772  ['title' => 'About us', 'link' => '/about', 'target' => ''],
773  [
774  'title' => 'Announcements & News',
775  'link' => '/news',
776  'target' => '',
777  'children' => [
778  [
779  'title' => 'Markets',
780  'link' => '/news/common/markets',
781  'target' => '',
782  ],
783  [
784  'title' => 'Products',
785  'link' => '/news/common/products',
786  'target' => '',
787  ],
788  [
789  'title' => 'Partners',
790  'link' => '/news/common/partners',
791  'target' => '_blank',
792  ],
793  ],
794  ],
795  ['title' => 'That page is forbidden to you', 'link' => '/403', 'target' => ''],
796  ['title' => 'That page was not found', 'link' => '/404', 'target' => ''],
797  ['title' => 'Our Blog', 'link' => 'https://blog.acme.com/authors', 'target' => ''],
798  ['title' => 'Cross Site Shortcut', 'link' => 'https://blog.acme.com/authors', 'target' => ''],
799  ],
800  ],
801  'ACME Blog' => [
802  'https://blog.acme.com/',
803  2100,
804  [
805  [
806  'title' => 'Authors',
807  'link' => '/authors',
808  'target' => '',
809  'children' => [
810  [
811  'title' => 'John Doe',
812  'link' => 'https://blog.acme.com/john/john',
813  'target' => '',
814  ],
815  [
816  'title' => 'Jane Doe',
817  'link' => 'https://blog.acme.com/jane/jane',
818  'target' => '',
819  ],
820  [
821  'title' => 'Malloy Doe',
822  'link' => '/malloy',
823  'target' => '',
824  ],
825  ],
826  ],
827  1 =>
828  [
829  'title' => 'Announcements & News',
830  'link' => '/news',
831  'target' => '',
832  'children' => [
833  [
834  'title' => 'Markets',
835  'link' => '/news/common/markets',
836  'target' => '',
837  ],
838  [
839  'title' => 'Products',
840  'link' => '/news/common/products',
841  'target' => '',
842  ],
843  [
844  'title' => 'Partners',
845  'link' => '/news/common/partners',
846  'target' => '_blank',
847  ],
848  ],
849  ],
850  ['title' => 'What is a blog on Wikipedia', 'link' => 'https://en.wikipedia.org/wiki/Blog', 'target' => 'a_new_tab'],
851  // target is empty because no fluid_styled_content typoscript with config.extTarget is active
852  ['title' => 'What is Wikipedia in a separate window', 'link' => 'https://en.wikipedia.org/', 'target' => ''],
853  ['title' => 'ACME Inc', 'link' => 'https://acme.us/welcome', 'target' => ''],
854  ],
855  ],
856  ];
857  }
858 
867  public function ‪hierarchicalMenuIsGenerated(string $hostPrefix, int $sourcePageId, array $expectation): void
868  {
869  $response = $this->executeFrontendSubRequest(
870  (new InternalRequest($hostPrefix))
871  ->withPageId($sourcePageId)
872  ->withInstructions([
874  'levels' => 2,
875  'entryLevel' => 0,
876  'expandAll' => 1,
877  'includeSpacer' => 1,
878  'titleField' => 'title',
879  ]),
880  ])
881  );
882 
883  $json = json_decode((string)$response->getBody(), true);
884  $json = $this->‪filterMenu($json, ['title', 'link', 'target']);
885 
886  self::assertSame($expectation, $json);
887  }
888 
893  {
894  $expectation = [
895  [
896  'title' => 'John Doe',
897  'link' => 'https://blog.acme.com/john/john',
898  'hasSubpages' => 1,
899  'children' => [
900  [
901  'title' => 'About',
902  'link' => 'https://blog.acme.com/john/about-john',
903  'hasSubpages' => 0,
904  ],
905  ],
906  ],
907  [
908  'title' => 'Jane Doe',
909  'link' => 'https://blog.acme.com/jane/jane',
910  'hasSubpages' => 1,
911  'children' => [
912  [
913  'title' => 'About',
914  'link' => 'https://blog.acme.com/jane/about-jane',
915  'hasSubpages' => 0,
916  ],
917  ],
918  ],
919  [
920  'title' => 'Malloy Doe',
921  'link' => '/malloy',
922  'hasSubpages' => 0,
923  ],
924  ];
925  $response = $this->executeFrontendSubRequest(
926  (new InternalRequest('https://blog.acme.com/'))
927  ->withPageId(2130)
928  ->withInstructions([
930  'levels' => 2,
931  'entryLevel' => 1,
932  'expandAll' => 1,
933  'includeSpacer' => 1,
934  'titleField' => 'title',
935  ]),
936  ])
937  );
938 
939  $json = json_decode((string)$response->getBody(), true);
940  $json = $this->‪filterMenu($json, ['title', 'link', 'hasSubpages']);
941 
942  self::assertSame($expectation, $json);
943  }
944 
946  {
947  return [
948  'regular page' => [
949  'https://acme.us/',
950  1310,
951  '1300',
952  [
953  [
954  'title' => 'EN: Products',
955  'link' => 'https://products.acme.com/products',
956  'active' => 1,
957  'current' => 0,
958  'children' => [
959  [
960  'title' => 'EN: Planets',
961  'link' => 'https://products.acme.com/products/planets',
962  'active' => 1,
963  'current' => 1,
964  ],
965  [
966  'title' => 'EN: Spaceships',
967  'link' => 'https://products.acme.com/products/spaceships',
968  'active' => 0,
969  'current' => 0,
970  ],
971  [
972  'title' => 'EN: Dark Matter',
973  'link' => 'https://products.acme.com/products/dark-matter',
974  'active' => 0,
975  'current' => 0,
976  ],
977  ],
978  ],
979  ],
980  ],
981  'resolved shortcut' => [
982  'https://blog.acme.com/',
983  2100,
984  '1930',
985  [
986  [
987  'title' => 'Our Blog',
988  'link' => '/authors',
989  'active' => 1,
990  'current' => 1,
991  ],
992  ],
993  ],
994  ];
995  }
996 
1001  public function ‪hierarchicalMenuSetsActiveStateProperly(string $hostPrefix, int $sourcePageId, string $menuPageIds, array $expectation): void
1002  {
1003  $response = $this->executeFrontendSubRequest(
1004  (new InternalRequest($hostPrefix))
1005  ->withPageId($sourcePageId)
1006  ->withInstructions([
1008  'levels' => 2,
1009  'special' => 'list',
1010  'special.' => [
1011  'value' => $menuPageIds,
1012  ],
1013  'includeSpacer' => 1,
1014  'titleField' => 'title',
1015  ]),
1016  ]),
1017  );
1018 
1019  $json = json_decode((string)$response->getBody(), true);
1020  $json = $this->‪filterMenu($json, ['title', 'active', 'current', 'link']);
1021 
1022  self::assertSame($expectation, $json);
1023  }
1024 
1026  {
1027  return [
1028  'no banned IDs in default language' => [
1029  'language' => 0,
1030  'banned IDs' => '',
1031  'expected menu items' => 13,
1032  ],
1033  'no banned IDs in FR' => [
1034  'language' => 1,
1035  'banned IDs' => '',
1036  'expected menu items' => 13,
1037  ],
1038  'banned IDs in default language' => [
1039  'language' => 0,
1040  'banned IDs' => '1100,1200,1300,1400,403,404',
1041  'expected menu items' => 7,
1042  ],
1043  'banned IDs in FR language' => [
1044  'language' => 1,
1045  'banned IDs' => '1100,1200,1300,1400,403,404',
1046  'expected menu items' => 7,
1047  ],
1048  'banned translated IDs in default language' => [
1049  'language' => 0,
1050  'banned IDs' => '1101,1200,1300,1400,403,404',
1051  'expected menu items' => 8,
1052  ],
1053  'banned translated IDs in FR language' => [
1054  'language' => 1,
1055  'banned IDs' => '1101,1200,1300,1400,403,404',
1056  'expected menu items' => 7,
1057  ],
1058  ];
1059  }
1060 
1067  public function ‪hierarchicalMenuAlwaysResolvesToDefaultLanguage(int $languageId, string $excludedUidList, int $expectedMenuItems): void
1068  {
1069  $response = $this->executeFrontendSubRequest(
1070  (new InternalRequest('https://acme.us/'))
1071  ->withPageId(1100)
1072  ->withLanguageId($languageId)
1073  ->withInstructions([
1075  'levels' => 1,
1076  'entryLevel' => 0,
1077  'excludeUidList' => $excludedUidList,
1078  'expandAll' => 1,
1079  'includeSpacer' => 1,
1080  'titleField' => 'title',
1081  ]),
1082  ])
1083  );
1084 
1085  $json = json_decode((string)$response->getBody(), true);
1086  self::assertSame($expectedMenuItems, count($json));
1087  }
1088 
1090  {
1091  return [
1092  'ACME Inc First Level - Live' => [
1093  'https://acme.us/',
1094  1100,
1095  1000,
1096  0,
1097  0,
1098  [
1099  [
1100  'title' => 'EN: Welcome',
1101  'link' => '/welcome',
1102  ],
1103  [
1104  'title' => 'ZH-CN: Welcome Default',
1105  // Symfony UrlGenerator, which is used for uri generation, rawurlencodes the url internally.
1106  'link' => '/%E7%AE%80-bienvenue',
1107  ],
1108  [
1109  'title' => 'EN: Features',
1110  'link' => '/features',
1111  ],
1112  [
1113  'title' => 'EN: Products',
1114  'link' => 'https://products.acme.com/products',
1115  ],
1116  [
1117  'title' => 'EN: ACME in your Region',
1118  'link' => '/acme-in-your-region',
1119  ],
1120  [
1121  'title' => 'Internal',
1122  'link' => '/my-acme',
1123  ],
1124  [
1125  'title' => 'About us',
1126  'link' => '/about',
1127  ],
1128  [
1129  'title' => 'Announcements & News',
1130  'link' => '/news',
1131  ],
1132  [
1133  'title' => 'That page is forbidden to you',
1134  'link' => '/403',
1135  ],
1136  [
1137  'title' => 'That page was not found',
1138  'link' => '/404',
1139  ],
1140  [
1141  'title' => 'Our Blog',
1142  'link' => 'https://blog.acme.com/authors',
1143  ],
1144  [
1145  'title' => 'Cross Site Shortcut',
1146  'link' => 'https://blog.acme.com/authors',
1147  ],
1148  ],
1149  ],
1150  'ACME Inc First Level - Draft Workspace' => [
1151  'https://acme.us/',
1152  1100,
1153  1000,
1154  1,
1155  1,
1156  [
1157  [
1158  'title' => 'EN: Goodbye',
1159  'link' => '/bye',
1160  ],
1161  [
1162  'title' => 'EN: Welcome to ACME Inc',
1163  'link' => '/welcome-modified',
1164  ],
1165  [
1166  'title' => 'ZH-CN: Welcome Default',
1167  // Symfony UrlGenerator, which is used for uri generation, rawurlencodes the url internally.
1168  'link' => '/%E7%AE%80-bienvenue',
1169  ],
1170  [
1171  'title' => 'EN: Features',
1172  'link' => '/features',
1173  ],
1174  [
1175  'title' => 'EN: Products',
1176  'link' => 'https://products.acme.com/products',
1177  ],
1178  [
1179  'title' => 'EN: ACME in your Region',
1180  'link' => '/acme-in-your-region',
1181  ],
1182  [
1183  'title' => 'Internal',
1184  'link' => '/my-acme',
1185  ],
1186  [
1187  'title' => 'About us',
1188  'link' => '/about',
1189  ],
1190  [
1191  'title' => 'Announcements & News',
1192  'link' => '/news',
1193  ],
1194  [
1195  'title' => 'That page is forbidden to you',
1196  'link' => '/403',
1197  ],
1198  [
1199  'title' => 'That page was not found',
1200  'link' => '/404',
1201  ],
1202  [
1203  'title' => 'Our Blog',
1204  'link' => 'https://blog.acme.com/authors',
1205  ],
1206  [
1207  'title' => 'Cross Site Shortcut',
1208  'link' => 'https://blog.acme.com/authors',
1209  ],
1210  ],
1211  ],
1212  ];
1213  }
1214 
1219  public function ‪directoryMenuIsGenerated(string $hostPrefix, int $sourcePageId, int $directoryMenuParentPage, int $backendUserId, int $workspaceId, array $expectation): void
1220  {
1221  $response = $this->executeFrontendSubRequest(
1222  (new InternalRequest($hostPrefix))
1223  ->withPageId($sourcePageId)
1224  ->withInstructions([
1226  'special' => 'directory',
1227  'special.' => [
1228  'value' => $directoryMenuParentPage,
1229  ],
1230  'titleField' => 'title',
1231  ]),
1232  ]),
1233  (new InternalRequestContext())
1234  ->withWorkspaceId($backendUserId !== 0 ? $workspaceId : 0)
1235  ->withBackendUserId($backendUserId)
1236  );
1237 
1238  $json = json_decode((string)$response->getBody(), true);
1239  $json = $this->‪filterMenu($json);
1240 
1241  self::assertSame($expectation, $json);
1242  }
1243 
1245  {
1246  return [
1247  'All restricted pages are linked to welcome page' => [
1248  'https://acme.us/',
1249  1100,
1250  1500,
1251  1100,
1252  0,
1253  0,
1254  [
1255  [
1256  'title' => 'Whitepapers',
1257  'link' => '/welcome',
1258  ],
1259  [
1260  'title' => 'Forecasts',
1261  'link' => '/welcome',
1262  ],
1263  [
1264  // Shortcut page, which resolves the shortcut and then the next page
1265  'title' => 'Employees',
1266  'link' => '/welcome',
1267  ],
1268  ],
1269  ],
1270  'Inherited restricted pages are linked' => [
1271  'https://acme.us/',
1272  1100,
1273  1520,
1274  1100,
1275  0,
1276  0,
1277  [
1278  [
1279  'title' => 'Current Year',
1280  // Should be
1281  // 'link' => '/welcome',
1282  // see https://forge.typo3.org/issues/16561
1283  'link' => '/my-acme/forecasts/current-year',
1284  ],
1285  [
1286  'title' => 'Next Year',
1287  // Should be
1288  // 'link' => '/welcome',
1289  // see https://forge.typo3.org/issues/16561
1290  'link' => '/my-acme/forecasts/next-year',
1291  ],
1292  [
1293  'title' => 'Five Years',
1294  // Should be
1295  // 'link' => '/welcome',
1296  // see https://forge.typo3.org/issues/16561
1297  'link' => '/my-acme/forecasts/five-years',
1298  ],
1299  ],
1300  ],
1301  ];
1302  }
1303 
1308  public function ‪directoryMenuToAccessRestrictedPagesIsGenerated(string $hostPrefix, int $sourcePageId, int $directoryMenuParentPage, int $loginPageId, int $backendUserId, int $workspaceId, array $expectation): void
1309  {
1310  $response = $this->executeFrontendSubRequest(
1311  (new InternalRequest($hostPrefix))
1312  ->withPageId($sourcePageId)
1313  ->withInstructions([
1315  'special' => 'directory',
1316  'special.' => [
1317  'value' => $directoryMenuParentPage,
1318  ],
1319  'levels' => 1,
1320  'showAccessRestrictedPages' => $loginPageId,
1321  ]),
1322  ]),
1323  (new InternalRequestContext())
1324  ->withWorkspaceId($backendUserId !== 0 ? $workspaceId : 0)
1325  ->withBackendUserId($backendUserId)
1326  );
1327 
1328  $json = json_decode((string)$response->getBody(), true);
1329  $json = $this->‪filterMenu($json);
1330 
1331  self::assertSame($expectation, $json);
1332  }
1333 
1334  public function ‪listMenuIsGeneratedDataProvider(): array
1335  {
1336  return [
1337  'Live' => [
1338  'https://acme.us/',
1339  1100,
1340  [1600, 1100, 1700, 1800, 1520],
1341  0,
1342  0,
1343  [],
1344  [
1345  [
1346  'title' => 'About us',
1347  'link' => '/about',
1348  ],
1349  [
1350  'title' => 'EN: Welcome',
1351  'link' => '/welcome',
1352  ],
1353  [
1354  'title' => 'Announcements & News',
1355  'link' => '/news',
1356  ],
1357  ],
1358  ],
1359  'Workspaces' => [
1360  'https://acme.us/',
1361  1100,
1362  [1600, 1100, 1700, 1800, 1520],
1363  1,
1364  1,
1365  [],
1366  [
1367  [
1368  'title' => 'About us',
1369  'link' => '/about',
1370  ],
1371  [
1372  'title' => 'EN: Welcome to ACME Inc',
1373  'link' => '/welcome-modified',
1374  ],
1375  [
1376  'title' => 'Announcements & News',
1377  'link' => '/news',
1378  ],
1379  ],
1380  ],
1381  'Folder as base directory, needs to set excludeDoktypes in order to show the folder itself' => [
1382  'https://acme.us/',
1383  1100,
1384  [7000],
1385  0,
1386  0,
1387  [
1388  'levels' => 2,
1389  'expandAll' => 1,
1390  'excludeDoktypes' => ‪PageRepository::DOKTYPE_BE_USER_SECTION,
1391  ],
1392  [
1393  [
1394  'title' => 'Common Collection',
1395  // @todo Folders should not be linked in frontend menus, as they are not accessible there.
1396  // @todo Folder as rootpage - reconsider if this should be a valid use/test case, as marking
1397  // it as root_page is not possible if page is doktype sysfolder first.
1398  'link' => 'https://common.acme.com/common',
1399  'children' => [
1400  [
1401  'title' => 'Announcements & News',
1402  'link' => 'https://common.acme.com/common/news',
1403  ],
1404  ],
1405  ],
1406  ],
1407  ],
1408  'Non-Rootpage Sysfolder, needs to set excludeDoktypes in order to show the folder itself' => [
1409  'https://acme.us/',
1410  1100,
1411  [8000],
1412  0,
1413  0,
1414  [
1415  'levels' => 2,
1416  'expandAll' => 1,
1417  'excludeDoktypes' => ‪PageRepository::DOKTYPE_BE_USER_SECTION,
1418  ],
1419  [
1420  [
1421  'title' => 'Usual Collection Non-Root',
1422  'link' => 'https://usual.acme.com/usual',
1423  'children' => [
1424  [
1425  'title' => 'Announcements & News',
1426  // @todo Folders should not be linked in frontend menus, as they are not accessible there.
1427  'link' => 'https://usual.acme.com/usual/news-folder',
1428  ],
1429  ],
1430  ],
1431  ],
1432  ],
1433  ];
1434  }
1435 
1440  public function ‪listMenuIsGenerated(string $hostPrefix, int $sourcePageId, array $menuPageIds, int $backendUserId, int $workspaceId, array $additionalMenuConfiguration, array $expectation): void
1441  {
1442  $response = $this->executeFrontendSubRequest(
1443  (new InternalRequest($hostPrefix))
1444  ->withPageId($sourcePageId)
1445  ->withInstructions([
1446  $this->‪createHierarchicalMenuProcessorInstruction(array_replace_recursive([
1447  'special' => 'list',
1448  'special.' => [
1449  'value' => implode(',', $menuPageIds),
1450  ],
1451  'titleField' => 'title',
1452  ], $additionalMenuConfiguration)),
1453  ]),
1454  (new InternalRequestContext())
1455  ->withWorkspaceId($backendUserId !== 0 ? $workspaceId : 0)
1456  ->withBackendUserId($backendUserId)
1457  );
1458 
1459  $json = json_decode((string)$response->getBody(), true);
1460  $json = $this->‪filterMenu($json);
1461 
1462  self::assertSame($expectation, $json);
1463  }
1464 
1469  {
1470  return [
1471  'ACME Inc (EN)' => [
1472  'https://acme.us/',
1473  1100,
1474  [
1475  ['title' => 'English', 'link' => '/welcome', 'active' => 1, 'current' => 0, 'available' => 1],
1476  ['title' => 'French', 'link' => 'https://acme.fr/bienvenue', 'active' => 0, 'current' => 0, 'available' => 1],
1477  ['title' => 'Franco-Canadian', 'link' => 'https://acme.ca/bienvenue', 'active' => 0, 'current' => 0, 'available' => 1],
1478  ],
1479  ],
1480  'ACME Inc (FR)' => [
1481  'https://acme.fr/',
1482  1100,
1483  [
1484  ['title' => 'English', 'link' => 'https://acme.us/welcome', 'active' => 0, 'current' => 0, 'available' => 1],
1485  ['title' => 'French', 'link' => '/bienvenue', 'active' => 1, 'current' => 0, 'available' => 1],
1486  ['title' => 'Franco-Canadian', 'link' => 'https://acme.ca/bienvenue', 'active' => 0, 'current' => 0, 'available' => 1],
1487  ],
1488  ],
1489  'ACME Inc (FR-CA)' => [
1490  'https://acme.ca/',
1491  1100,
1492  [
1493  ['title' => 'English', 'link' => 'https://acme.us/welcome', 'active' => 0, 'current' => 0, 'available' => 1],
1494  ['title' => 'French', 'link' => 'https://acme.fr/bienvenue', 'active' => 0, 'current' => 0, 'available' => 1],
1495  ['title' => 'Franco-Canadian', 'link' => '/bienvenue', 'active' => 1, 'current' => 0, 'available' => 1],
1496  ],
1497  ],
1498  'ACME Blog' => [
1499  'https://blog.acme.com/',
1500  2100,
1501  [
1502  ['title' => 'Default', 'link' => '/authors', 'active' => 1, 'current' => 0, 'available' => 1],
1503  ],
1504  ],
1505  'ACME Inc (EN) with a subpage' => [
1506  'https://acme.us/about',
1507  1600,
1508  [
1509  ['title' => 'English', 'link' => '/about', 'active' => 1, 'current' => 0, 'available' => 1],
1510  ['title' => 'French', 'link' => 'https://acme.fr/about', 'active' => 0, 'current' => 0, 'available' => 0],
1511  ['title' => 'Franco-Canadian', 'link' => 'https://acme.ca/about', 'active' => 0, 'current' => 0, 'available' => 0],
1512  ],
1513  ],
1514  ];
1515  }
1516 
1525  public function ‪languageMenuIsGenerated(string $hostPrefix, int $sourcePageId, array $expectation): void
1526  {
1527  $response = $this->executeFrontendSubRequest(
1528  (new InternalRequest($hostPrefix))
1529  ->withPageId($sourcePageId)
1530  ->withInstructions([
1532  'languages' => 'auto',
1533  ]),
1534  ])
1535  );
1536 
1537  $json = json_decode((string)$response->getBody(), true);
1538  $json = $this->‪filterMenu($json, ['title', 'link', 'available', 'active', 'current']);
1539 
1540  self::assertSame($expectation, $json);
1541  }
1542 }
‪TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase\filterMenu
‪array filterMenu(array $menu, array $keepNames=['title', 'link'])
Definition: AbstractTestCase.php:195
‪TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait\buildLanguageConfiguration
‪array buildLanguageConfiguration(string $identifier, string $base, array $fallbackIdentifiers=[], string $fallbackType=null)
Definition: SiteBasedTestTrait.php:144
‪TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait\writeSiteConfiguration
‪writeSiteConfiguration(string $identifier, array $site=[], array $languages=[], array $errorHandling=[])
Definition: SiteBasedTestTrait.php:58
‪TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase\keysFromTemplate
‪array keysFromTemplate(array $array, string $template, callable $callback=null)
Definition: AbstractTestCase.php:102
‪TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase
Definition: AbstractTestCase.php:29
‪TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase\createHierarchicalMenuProcessorInstruction
‪ArrayValueInstruction createHierarchicalMenuProcessorInstruction(array $typoScript)
Definition: AbstractTestCase.php:148
‪TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase\createLanguageMenuProcessorInstruction
‪ArrayValueInstruction createLanguageMenuProcessorInstruction(array $typoScript)
Definition: AbstractTestCase.php:170
‪TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait\buildDefaultLanguageConfiguration
‪array buildDefaultLanguageConfiguration(string $identifier, string $base)
Definition: SiteBasedTestTrait.php:126
‪TYPO3\CMS\Core\Core\Bootstrap\initializeLanguageObject
‪static initializeLanguageObject()
Definition: Bootstrap.php:598
‪TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\AbstractTestCase\createTypoLinkUrlInstruction
‪ArrayValueInstruction createTypoLinkUrlInstruction(array $typoScript)
Definition: AbstractTestCase.php:130
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_BE_USER_SECTION
‪const DOKTYPE_BE_USER_SECTION
Definition: PageRepository.php:113
‪TYPO3\CMS\Core\TypoScript\TemplateService
Definition: TemplateService.php:46
‪TYPO3\CMS\Frontend\Tests\Functional\SiteHandling
Definition: AbstractTestCase.php:18
‪TYPO3\CMS\Core\Core\Bootstrap
Definition: Bootstrap.php:70
‪TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait\buildSiteConfiguration
‪array buildSiteConfiguration(int $rootPageId, string $base='')
Definition: SiteBasedTestTrait.php:111
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:53