[BUGFIX] Fix several typos in php comments
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Functional / Rendering / LocalizedSiteContentRenderingTest.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Frontend\Tests\Functional\Rendering;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
20 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
21 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
22
23 /**
24 * Test case checking if localized tt_content is rendered correctly with different language settings
25 * with site configuration.
26 *
27 * Previously the language was given by TypoScript settings which were overridden via GP parameters for language
28 *
29 * config.sys_language_uid = [0,1,2,3,4...]
30 * config.sys_language_mode = [strict, content_fallback;2,3, ignore, '']
31 * config.sys_language_overlay = [0, 1, hideNonTranslated]
32 *
33 * The previous setting config.sys_language_mode the behaviour of the page translation was referred to, and what
34 * should happen if a page translation does not exist.
35 *
36 * the setting config.sys_language_overlay was responsible if records of a target language should be fetched
37 * directly ("free mode" or no-overlays), or if the default language (L=0) should be taken and then overlaid.
38 * In addition the "hideNonTranslated" was a special form of overlays: Take the default language, but if the translation
39 * does not exist, do not render the default language.
40 *
41 * This is what changed with Site Handling:
42 * - General approach is now defined on a site language, in configuration, not evaluated during runtime
43 * - Page fallback concept (also for menu generation) is now valid for page and content.
44 * - Various options which only made sense on specific page configurations have been removed for consistency reasons.
45 *
46 * Pages & Menus:
47 * - When a Page Translation needs to be fetched, it is checked if the page translation exists, otherwise the "fallbackChain"
48 * jumps in and checks if the other languages are available or aren't available.
49 * - If no fallbackChain is given, then the page is not shown / rendered / accessible.
50 * - pages.l18n_cfg is now considered properly with multiple fallback languages for menus and page resolving and URL linking.
51 *
52 * Content Fetching:
53 *
54 * - A new "free" mode only fetches the records that are set in a specific language.
55 * Due to the concept of the database structure, no fallback logic applies currently when selecting records, however
56 * fallbackChains are still valid for identifying the Page Translation.
57 * - The modes "fallback" and "strict" have similarities: They utilize the so-called "overlay" logic: Fetch records in the default
58 * language (= 0) and then overlay with the available language. This ensures that ordering and other connections
59 * are kept the same way as on the default language.
60 * - "fallback" shows content in the language of the page that was selected, does the overlays but keeps the default
61 * language records when no translation is available (= "mixed overlays").
62 * - "strict" shows only content of the page that was selected via overlays (fetch default language and do overlays)
63 * but does not render the ones that have no translation in the specific language.
64 *
65 * General notes regarding content fetching:
66 * - Records marked as "All Languages" (sys_language_uid = -1) are always fetched (this wasn't always the case before!).
67 * - Records without a language parent (l10n_parent) are rendered at any time.
68 *
69 * Relevant parts for site handling:
70 *
71 * SiteLanguage
72 * -> languageId
73 * the language that is requested, usually determined by the base property. If this setting is "0"
74 * no other options are taken into account.
75 * -> fallbackType
76 * - strict:
77 * * for pages: if the page translation does not exist, check fallbackChain
78 * * for record fetching: take Default Language records which have a valid translation for this language + records without default translation
79 * - fallback:
80 * * for pages: if the page translation does not exist, check fallbackChain
81 * * for record fetching: take Default Language records and overlay the language, but keep default language records + records without default translation
82 * - free:
83 * * for pages: if the page translation does not exist, check fallbackChain
84 * * for record fetching: Only fetch records of the current language and "All languages" no overlays are done.
85 *
86 * LanguageAspect
87 * -> doOverlays()
88 * whether the the overlay logic should be applied
89 * -> getLanguageId()
90 * the language that was originally requested
91 * -> getContentId()
92 * if the page translation for e.g. language=5 is not available, but the fallback is "4,3,2", then the content of this language is used instead.
93 * applies to all concepts of fallback types.
94 * -> getFallbackChain()
95 * if the page is not available in a specific language, apply other language Ids in the given order until the page translation can be found.
96 */
97 class LocalizedSiteContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\AbstractDataHandlerActionTestCase
98 {
99 use SiteBasedTestTrait;
100
101 const VALUE_PageId = 89;
102 const TABLE_Content = 'tt_content';
103 const TABLE_Pages = 'pages';
104
105 /**
106 * @var string
107 */
108 protected $scenarioDataSetDirectory = 'typo3/sysext/frontend/Tests/Functional/Rendering/DataSet/';
109
110 /**
111 * @var string[]
112 */
113 protected $coreExtensionsToLoad = ['frontend', 'workspaces'];
114
115 /**
116 * @var array
117 */
118 protected $pathsToLinkInTestInstance = [
119 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/AdditionalConfiguration.php' => 'typo3conf/AdditionalConfiguration.php',
120 'typo3/sysext/frontend/Tests/Functional/Fixtures/Images' => 'fileadmin/user_upload'
121 ];
122
123 /**
124 * If this value is NULL, log entries are not considered.
125 * If it's an integer value, the number of log entries is asserted.
126 *
127 * @var int|null
128 */
129 protected $expectedErrorLogEntries;
130
131 /**
132 * @var array
133 */
134 protected const LANGUAGE_PRESETS = [
135 'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'],
136 'DK' => ['id' => 1, 'title' => 'Dansk', 'locale' => 'dk_DA.UTF8'],
137 'DE' => ['id' => 2, 'title' => 'Deutsch', 'locale' => 'de_DE.UTF8'],
138 'PL' => ['id' => 3, 'title' => 'Polski', 'locale' => 'pl_PL.UTF8'],
139 ];
140
141 protected function setUp(): void
142 {
143 parent::setUp();
144
145 $this->importDataSet('PACKAGE:typo3/testing-framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml');
146 $this->importScenarioDataSet('LiveDefaultPages');
147 $this->importScenarioDataSet('LiveDefaultElements');
148
149 $this->setUpFrontendRootPage(1, [
150 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
151 ]);
152 }
153
154 /**
155 * For the default language all combination of language settings should give the same result,
156 * regardless of Language fallback settings, if the default language is requested then no language settings apply.
157 *
158 * @test
159 */
160 public function onlyEnglishContentIsRenderedForDefaultLanguage()
161 {
162 $this->writeSiteConfiguration(
163 'test',
164 $this->buildSiteConfiguration(1, 'https://website.local/'),
165 [
166 $this->buildDefaultLanguageConfiguration('EN', '/en/'),
167 $this->buildLanguageConfiguration('DK', '/dk/')
168 ],
169 [
170 $this->buildErrorHandlingConfiguration('Fluid', [404])
171 ]
172 );
173
174 $response = $this->executeFrontendRequest(
175 new InternalRequest('https://website.local/en/?id=' . static::VALUE_PageId)
176 );
177 $responseStructure = ResponseContent::fromString((string)$response->getBody());
178
179 $responseSections = $responseStructure->getSections();
180 $visibleHeaders = ['Regular Element #1', 'Regular Element #2', 'Regular Element #3'];
181 $this->assertThat(
182 $responseSections,
183 $this->getRequestSectionHasRecordConstraint()
184 ->setTable(self::TABLE_Content)
185 ->setField('header')
186 ->setValues(...$visibleHeaders)
187 );
188 $this->assertThat(
189 $responseSections,
190 $this->getRequestSectionDoesNotHaveRecordConstraint()
191 ->setTable(self::TABLE_Content)
192 ->setField('header')
193 ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
194 );
195
196 //assert FAL relations
197 $visibleFiles = ['T3BOARD'];
198 $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
199 ->setRecordIdentifier(self::TABLE_Content . ':297')->setRecordField('image')
200 ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFiles));
201
202 $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
203 ->setRecordIdentifier(self::TABLE_Content . ':297')->setRecordField('image')
204 ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFiles)));
205
206 $visibleFiles = ['Kasper2'];
207 $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
208 ->setRecordIdentifier(self::TABLE_Content . ':298')->setRecordField('image')
209 ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFiles));
210
211 $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
212 ->setRecordIdentifier(self::TABLE_Content . ':298')->setRecordField('image')
213 ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFiles)));
214
215 // Assert language settings and page record title
216 $this->assertEquals('Default language Page', $responseStructure->getScopePath('page/title'));
217 $this->assertEquals(0, $responseStructure->getScopePath('languageInfo/id'), 'languageId does not match');
218 $this->assertEquals(0, $responseStructure->getScopePath('languageInfo/contentId'), 'contentId does not match');
219 $this->assertEquals('strict', $responseStructure->getScopePath('languageInfo/fallbackType'), 'fallbackType does not match');
220 $this->assertEquals('pageNotFound', $responseStructure->getScopePath('languageInfo/fallbackChain'), 'fallbackChain does not match');
221 $this->assertEquals('includeFloating', $responseStructure->getScopePath('languageInfo/overlayType'), 'language overlayType does not match');
222 }
223
224 /**
225 * Dutch language has page translation record and some content elements are translated
226 *
227 * @return array
228 */
229 public function dutchDataProvider(): array
230 {
231 return [
232 [
233 // Only records with language=1 are shown
234 'languageConfiguration' => [
235 'fallbackType' => 'free'
236 ],
237 'visibleRecords' => [
238 300 => [
239 'header' => '[Translate to Dansk:] Regular Element #3',
240 'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
241 ],
242 301 => [
243 'header' => '[Translate to Dansk:] Regular Element #1',
244 'image' => [],
245 ],
246 303 => [
247 'header' => '[DK] Without default language',
248 'image' => ['[T3BOARD] Image added to DK element without default language']
249 ],
250 308 => [
251 'header' => '[DK] UnHidden Element #4',
252 'image' => []
253 ],
254 ],
255 'fallbackType' => 'free',
256 'fallbackChain' => 'pageNotFound',
257 'overlayMode' => 'off',
258 ],
259 // Expected behaviour:
260 // Not translated element #2 is shown because "fallback" is enabled, which defaults to L=0 elements
261 [
262 'languageConfiguration' => [
263 'fallbackType' => 'fallback'
264 ],
265 'visibleRecords' => [
266 297 => [
267 'header' => '[Translate to Dansk:] Regular Element #1',
268 'image' => [],
269 ],
270 298 => [
271 'header' => 'Regular Element #2',
272 'image' => ['Kasper2'],
273 ],
274 299 => [
275 'header' => '[Translate to Dansk:] Regular Element #3',
276 'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
277 ],
278 ],
279 'fallbackType' => 'fallback',
280 'fallbackChain' => 'pageNotFound',
281 'overlayMode' => 'mixed',
282 ],
283 // Expected behaviour:
284 // Non translated default language elements are not shown, but the results include the records without default language as well
285 [
286 'languageConfiguration' => [
287 'fallbackType' => 'strict'
288 ],
289 'visibleRecords' => [
290 297 => [
291 'header' => '[Translate to Dansk:] Regular Element #1',
292 'image' => [],
293 ],
294 299 => [
295 'header' => '[Translate to Dansk:] Regular Element #3',
296 'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
297 ],
298 303 => [
299 'header' => '[DK] Without default language',
300 'image' => ['[T3BOARD] Image added to DK element without default language'],
301 ],
302 ],
303 'fallbackType' => 'strict',
304 'fallbackChain' => 'pageNotFound',
305 'overlayMode' => 'includeFloating',
306 ],
307 ];
308 }
309
310 /**
311 * Page is translated to Dutch, so changing fallbackChain does not matter currently.
312 * Page title is always [DK]Page, the content language is always "1"
313 * @test
314 * @dataProvider dutchDataProvider
315 *
316 * @param array $languageConfiguration
317 * @param array $visibleRecords
318 * @param string $fallbackType
319 * @param string $fallbackChain
320 * @param string $overlayType
321 */
322 public function renderingOfDutchLanguage(array $languageConfiguration, array $visibleRecords, string $fallbackType, string $fallbackChain, string $overlayType)
323 {
324 $this->writeSiteConfiguration(
325 'test',
326 $this->buildSiteConfiguration(1, 'https://website.local/'),
327 [
328 $this->buildDefaultLanguageConfiguration('EN', '/en/'),
329 $this->buildLanguageConfiguration('DK', '/dk/', $languageConfiguration['fallbackChain'] ?? [], $languageConfiguration['fallbackType'])
330 ],
331 [
332 $this->buildErrorHandlingConfiguration('Fluid', [404])
333 ]
334 );
335
336 $response = $this->executeFrontendRequest(
337 new InternalRequest('https://website.local/dk/?id=' . static::VALUE_PageId)
338 );
339 $responseStructure = ResponseContent::fromString((string)$response->getBody());
340 $responseSections = $responseStructure->getSections();
341 $visibleHeaders = array_map(function ($element) {
342 return $element['header'];
343 }, $visibleRecords);
344
345 $this->assertThat(
346 $responseSections,
347 $this->getRequestSectionHasRecordConstraint()
348 ->setTable(self::TABLE_Content)
349 ->setField('header')
350 ->setValues(...$visibleHeaders)
351 );
352 $this->assertThat(
353 $responseSections,
354 $this->getRequestSectionDoesNotHaveRecordConstraint()
355 ->setTable(self::TABLE_Content)
356 ->setField('header')
357 ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
358 );
359
360 foreach ($visibleRecords as $ttContentUid => $properties) {
361 $visibleFileTitles = $properties['image'];
362 if (!empty($visibleFileTitles)) {
363 $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
364 ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
365 ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFileTitles));
366 }
367 $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
368 ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
369 ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFileTitles)));
370 }
371
372 $this->assertEquals('[DK]Page', $responseStructure->getScopePath('page/title'));
373 $this->assertEquals(1, $responseStructure->getScopePath('languageInfo/id'), 'languageId does not match');
374 $this->assertEquals(1, $responseStructure->getScopePath('languageInfo/contentId'), 'contentId does not match');
375 $this->assertEquals($fallbackType, $responseStructure->getScopePath('languageInfo/fallbackType'), 'fallbackType does not match');
376 $this->assertEquals($fallbackChain, $responseStructure->getScopePath('languageInfo/fallbackChain'), 'fallbackChain does not match');
377 $this->assertEquals($overlayType, $responseStructure->getScopePath('languageInfo/overlayType'), 'language overlayType does not match');
378 }
379
380 public function contentOnNonTranslatedPageDataProvider(): array
381 {
382 //Expected behaviour:
383 //the page is NOT translated so setting sys_language_mode to different values changes the results
384 //- setting sys_language_mode to empty value makes TYPO3 return default language records
385 //- setting it to strict throws 404, independently from other settings
386 //Setting config.sys_language_overlay = 0
387 return [
388 [
389 'languageConfiguration' => [
390 'fallbackType' => 'free',
391 'fallbackChain' => ['EN']
392 ],
393 'visibleRecords' => [
394 297 => [
395 'header' => 'Regular Element #1',
396 'image' => ['T3BOARD'],
397 ],
398 298 => [
399 'header' => 'Regular Element #2',
400 'image' => ['Kasper2'],
401 ],
402 299 => [
403 'header' => 'Regular Element #3',
404 'image' => ['Kasper'],
405 ],
406 ],
407 'pageTitle' => 'Default language Page',
408 'languageId' => 2,
409 'contentId' => 0,
410 'fallbackType' => 'free',
411 'fallbackChain' => '0,pageNotFound',
412 'overlayMode' => 'off',
413 ],
414 [
415 'languageConfiguration' => [
416 'fallbackType' => 'free',
417 'fallbackChain' => ['EN']
418 ],
419 'visibleRecords' => [
420 297 => [
421 'header' => 'Regular Element #1',
422 'image' => ['T3BOARD'],
423 ],
424 298 => [
425 'header' => 'Regular Element #2',
426 'image' => ['Kasper2'],
427 ],
428 299 => [
429 'header' => 'Regular Element #3',
430 'image' => ['Kasper'],
431 ],
432 ],
433 'pageTitle' => 'Default language Page',
434 'languageId' => 2,
435 'contentId' => 0,
436 'fallbackType' => 'free',
437 'fallbackChain' => '0,pageNotFound',
438 'overlayMode' => 'off',
439 ],
440 [
441 'languageConfiguration' => [
442 'fallbackType' => 'free',
443 'fallbackChain' => ['DK', 'EN']
444 ],
445 'visibleRecords' => [
446 300 => [
447 'header' => '[Translate to Dansk:] Regular Element #3',
448 'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
449 ],
450 301 => [
451 'header' => '[Translate to Dansk:] Regular Element #1',
452 'image' => [],
453 ],
454 303 => [
455 'header' => '[DK] Without default language',
456 'image' => ['[T3BOARD] Image added to DK element without default language'],
457 ],
458 308 => [
459 'header' => '[DK] UnHidden Element #4',
460 'image' => [],
461 ],
462 ],
463 'pageTitle' => '[DK]Page',
464 'languageId' => 2,
465 'contentId' => 1,
466 'fallbackType' => 'free',
467 'fallbackChain' => '1,0,pageNotFound',
468 'overlayMode' => 'off',
469 ],
470 [
471 'languageConfiguration' => [
472 'fallbackType' => 'free'
473 ],
474 'visibleRecords' => [],
475 'pageTitle' => '',
476 'languageId' => 2,
477 'contentId' => 2,
478 'fallbackType' => 'free',
479 'fallbackChain' => 'pageNotFound',
480 'overlayMode' => 'off',
481 'statusCode' => 404,
482 ],
483 [
484 'languageConfiguration' => [
485 'fallbackType' => 'fallback',
486 'fallbackChain' => ['EN']
487 ],
488 'visibleRecords' => [
489 297 => [
490 'header' => 'Regular Element #1',
491 'image' => ['T3BOARD'],
492 ],
493 298 => [
494 'header' => 'Regular Element #2',
495 'image' => ['Kasper2'],
496 ],
497 299 => [
498 'header' => 'Regular Element #3',
499 'image' => ['Kasper'],
500 ],
501 ],
502 'pageTitle' => 'Default language Page',
503 'languageId' => 2,
504 'contentId' => 0,
505 'fallbackType' => 'fallback',
506 'fallbackChain' => '0,pageNotFound',
507 'overlayMode' => 'mixed',
508 ],
509 //falling back to default language
510 [
511 'languageConfiguration' => [
512 'fallbackType' => 'fallback',
513 'fallbackChain' => ['EN']
514 ],
515 'visibleRecords' => [
516 297 => [
517 'header' => 'Regular Element #1',
518 'image' => ['T3BOARD'],
519 ],
520 298 => [
521 'header' => 'Regular Element #2',
522 'image' => ['Kasper2'],
523 ],
524 299 => [
525 'header' => 'Regular Element #3',
526 'image' => ['Kasper'],
527 ],
528 ],
529 'pageTitle' => 'Default language Page',
530 'languageId' => 2,
531 'contentId' => 0,
532 'fallbackType' => 'fallback',
533 'fallbackChain' => '0,pageNotFound',
534 'overlayMode' => 'mixed',
535 ],
536 //Dutch elements are shown because of the content fallback 1,0 - first Dutch, then default language
537 //note that '[DK] Without default language' is NOT shown - due to overlays (fetch default language and overlay it with translations)
538 [
539 'languageConfiguration' => [
540 'fallbackType' => 'fallback',
541 'fallbackChain' => ['DK', 'EN']
542 ],
543 'visibleRecords' => [
544 297 => [
545 'header' => '[Translate to Dansk:] Regular Element #1',
546 'image' => [],
547 ],
548 298 => [
549 'header' => 'Regular Element #2',
550 'image' => ['Kasper2'],
551 ],
552 299 => [
553 'header' => '[Translate to Dansk:] Regular Element #3',
554 'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
555 ],
556 ],
557 'pageTitle' => '[DK]Page',
558 'languageId' => 2,
559 'contentId' => 1,
560 'fallbackType' => 'fallback',
561 'fallbackChain' => '1,0,pageNotFound',
562 'overlayMode' => 'mixed',
563 ],
564 [
565 'languageConfiguration' => [
566 'fallbackType' => 'fallback',
567 'fallbackChain' => []
568 ],
569 'visibleRecords' => [],
570 'pageTitle' => '',
571 'languageId' => 2,
572 'contentId' => 0,
573 'fallbackType' => 'fallback',
574 'fallbackChain' => 'pageNotFound',
575 'overlayMode' => 'mixed',
576 'statusCode' => 404
577 ],
578 [
579 'languageConfiguration' => [
580 'fallbackType' => 'strict',
581 'fallbackChain' => ['EN']
582 ],
583 'visibleRecords' => [
584 297 => [
585 'header' => 'Regular Element #1',
586 'image' => ['T3BOARD'],
587 ],
588 298 => [
589 'header' => 'Regular Element #2',
590 'image' => ['Kasper2'],
591 ],
592 299 => [
593 'header' => 'Regular Element #3',
594 'image' => ['Kasper'],
595 ],
596 ],
597 'pageTitle' => 'Default language Page',
598 'languageId' => 2,
599 'contentId' => 0,
600 'fallbackType' => 'strict',
601 'fallbackChain' => '0,pageNotFound',
602 'overlayMode' => 'includeFloating',
603 ],
604 [
605 'languageConfiguration' => [
606 'fallbackType' => 'strict',
607 'fallbackChain' => ['DK', 'EN']
608 ],
609 'visibleRecords' => [
610 297 => [
611 'header' => '[Translate to Dansk:] Regular Element #1',
612 'image' => [],
613 ],
614 299 => [
615 'header' => '[Translate to Dansk:] Regular Element #3',
616 'image' => ['[Kasper] Image translated to Dansk', '[T3BOARD] Image added in Dansk (without parent)'],
617 ],
618 303 => [
619 'header' => '[DK] Without default language',
620 'image' => ['[T3BOARD] Image added to DK element without default language']
621 ],
622 ],
623 'pageTitle' => '[DK]Page',
624 'languageId' => 2,
625 'contentId' => 1,
626 'fallbackType' => 'strict',
627 'fallbackChain' => '1,0,pageNotFound',
628 'overlayMode' => 'includeFloating',
629 ],
630 [
631 'languageConfiguration' => [
632 'fallbackType' => 'strict',
633 'fallbackChain' => []
634 ],
635 'visibleRecords' => [],
636 'pageTitle' => '',
637 'languageId' => 2,
638 'contentId' => 1,
639 'fallbackType' => 'strict',
640 'fallbackChain' => 'pageNotFound',
641 'overlayMode' => 'includeFloating',
642 'statusCode' => 404,
643 ],
644 ];
645 }
646
647 /**
648 * Page uid 89 is NOT translated to german
649 *
650 * @test
651 * @dataProvider contentOnNonTranslatedPageDataProvider
652 *
653 * @param array $languageConfiguration
654 * @param array $visibleRecords
655 * @param string $pageTitle
656 * @param int $languageId
657 * @param int $contentLanguageId
658 * @param string $fallbackType
659 * @param string $fallbackChain
660 * @param string $overlayMode
661 * @param int $statusCode 200 or 404
662 */
663 public function contentOnNonTranslatedPageGerman(array $languageConfiguration, array $visibleRecords, string $pageTitle, int $languageId, int $contentLanguageId, string $fallbackType, string $fallbackChain, string $overlayMode, int $statusCode = 200)
664 {
665 $this->writeSiteConfiguration(
666 'main',
667 $this->buildSiteConfiguration(1, 'https://website.local/'),
668 [
669 $this->buildDefaultLanguageConfiguration('EN', '/en/'),
670 $this->buildLanguageConfiguration('DK', '/dk/'),
671 $this->buildLanguageConfiguration('DE', '/de/', $languageConfiguration['fallbackChain'] ?? [], $languageConfiguration['fallbackType'])
672 ],
673 [
674 $this->buildErrorHandlingConfiguration('Fluid', [404])
675 ]
676 );
677
678 $response = $this->executeFrontendRequest(
679 new InternalRequest('https://website.local/de/?id=' . static::VALUE_PageId)
680 );
681
682 if ($statusCode === 200) {
683 $visibleHeaders = array_column($visibleRecords, 'header');
684 $responseStructure = ResponseContent::fromString((string)$response->getBody());
685 $responseSections = $responseStructure->getSections();
686
687 $this->assertThat(
688 $responseSections,
689 $this->getRequestSectionHasRecordConstraint()
690 ->setTable(self::TABLE_Content)
691 ->setField('header')
692 ->setValues(...$visibleHeaders)
693 );
694 $this->assertThat(
695 $responseSections,
696 $this->getRequestSectionDoesNotHaveRecordConstraint()
697 ->setTable(self::TABLE_Content)
698 ->setField('header')
699 ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
700 );
701
702 foreach ($visibleRecords as $ttContentUid => $properties) {
703 $visibleFileTitles = $properties['image'];
704 if (!empty($visibleFileTitles)) {
705 $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
706 ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
707 ->setTable('sys_file_reference')->setField('title')->setValues(...$visibleFileTitles));
708 }
709 $this->assertThat($responseSections, $this->getRequestSectionStructureDoesNotHaveRecordConstraint()
710 ->setRecordIdentifier(self::TABLE_Content . ':' . $ttContentUid)->setRecordField('image')
711 ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFileTitles)));
712 }
713
714 $this->assertEquals($pageTitle, $responseStructure->getScopePath('page/title'));
715 $this->assertEquals($languageId, $responseStructure->getScopePath('languageInfo/id'), 'languageId does not match');
716 $this->assertEquals($contentLanguageId, $responseStructure->getScopePath('languageInfo/contentId'), 'contentId does not match');
717 $this->assertEquals($fallbackType, $responseStructure->getScopePath('languageInfo/fallbackType'), 'fallbackType does not match');
718 $this->assertEquals($fallbackChain, $responseStructure->getScopePath('languageInfo/fallbackChain'), 'fallbackChain does not match');
719 $this->assertEquals($overlayMode, $responseStructure->getScopePath('languageInfo/overlayType'), 'language overlayType does not match');
720 }
721 $this->assertEquals($statusCode, $response->getStatusCode());
722 }
723
724 public function contentOnPartiallyTranslatedPageDataProvider(): array
725 {
726
727 //Expected behaviour:
728 //Setting sys_language_mode to different values doesn't influence the result as the requested page is translated to Polish,
729 //Page title is always [PL]Page, and both languageId/contentId are always 3
730 return [
731 [
732 'languageConfiguration' => [
733 'fallbackType' => 'free'
734 ],
735 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
736 'fallbackType' => 'free',
737 'fallbackChain' => 'pageNotFound',
738 'overlayMode' => 'off',
739 ],
740 [
741 'languageConfiguration' => [
742 'fallbackType' => 'free',
743 'fallbackChain' => ['EN']
744 ],
745 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
746 'fallbackType' => 'free',
747 'fallbackChain' => '0,pageNotFound',
748 'overlayMode' => 'off',
749 ],
750 [
751 'languageConfiguration' => [
752 'fallbackType' => 'free',
753 'fallbackChain' => ['DK', 'EN']
754 ],
755 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
756 'fallbackType' => 'free',
757 'fallbackChain' => '1,0,pageNotFound',
758 'overlayMode' => 'off',
759 ],
760 // Expected behaviour:
761 // Not translated element #2 is shown because sys_language_overlay = 1 (with sys_language_overlay = hideNonTranslated, it would be hidden)
762 [
763 'languageConfiguration' => [
764 'fallbackType' => 'fallback',
765 'fallbackChain' => ['EN']
766 ],
767 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
768 'fallbackType' => 'fallback',
769 'fallbackChain' => '0,pageNotFound',
770 'overlayMode' => 'mixed',
771 ],
772 // Expected behaviour:
773 // Element #3 is not translated in PL and it is translated in DK. It's not shown as content_fallback is not related to single CE level
774 // but on page level - and this page is translated to Polish, so no fallback is happening
775 [
776 'languageConfiguration' => [
777 'fallbackType' => 'fallback',
778 'fallbackChain' => ['DK', 'EN']
779 ],
780 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
781 'fallbackType' => 'fallback',
782 'fallbackChain' => '1,0,pageNotFound',
783 'overlayMode' => 'mixed',
784 ],
785 [
786 'languageConfiguration' => [
787 'fallbackType' => 'fallback',
788 'fallbackChain' => []
789 ],
790 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', 'Regular Element #2', 'Regular Element #3'],
791 'fallbackType' => 'fallback',
792 'fallbackChain' => 'pageNotFound',
793 'overlayMode' => 'mixed',
794 ],
795 // Expected behaviour:
796 // Non translated default language elements are not shown, because of hideNonTranslated
797 [
798 'languageConfiguration' => [
799 'fallbackType' => 'strict',
800 'fallbackChain' => ['EN']
801 ],
802 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
803 'fallbackType' => 'strict',
804 'fallbackChain' => '0,pageNotFound',
805 'overlayMode' => 'includeFloating',
806 ],
807 [
808 'languageConfiguration' => [
809 'fallbackType' => 'strict',
810 'fallbackChain' => ['DK', 'EN']
811 ],
812 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
813 'fallbackType' => 'strict',
814 'fallbackChain' => '1,0,pageNotFound',
815 'overlayMode' => 'includeFloating',
816 ],
817 [
818 'languageConfiguration' => [
819 'fallbackType' => 'strict',
820 'fallbackChain' => []
821 ],
822 'visibleRecordHeaders' => ['[Translate to Polski:] Regular Element #1', '[PL] Without default language'],
823 'fallbackType' => 'strict',
824 'fallbackChain' => 'pageNotFound',
825 'overlayMode' => 'includeFloating',
826 ],
827 ];
828 }
829
830 /**
831 * Page uid 89 is translated to to Polish, but not all CE are translated
832 *
833 * @test
834 * @dataProvider contentOnPartiallyTranslatedPageDataProvider
835 *
836 * @param array $languageConfiguration
837 * @param array $visibleHeaders
838 * @param string $fallbackType
839 * @param string $fallbackChain
840 * @param string $overlayType
841 */
842 public function contentOnPartiallyTranslatedPage(array $languageConfiguration, array $visibleHeaders, string $fallbackType, string $fallbackChain, string $overlayType)
843 {
844 $this->writeSiteConfiguration(
845 'test',
846 $this->buildSiteConfiguration(1, 'https://website.local/'),
847 [
848 $this->buildDefaultLanguageConfiguration('EN', '/en/'),
849 $this->buildLanguageConfiguration('DK', '/dk/'),
850 $this->buildLanguageConfiguration('PL', '/pl/', $languageConfiguration['fallbackChain'] ?? [], $languageConfiguration['fallbackType'])
851 ],
852 [
853 $this->buildErrorHandlingConfiguration('Fluid', [404])
854 ]
855 );
856
857 $response = $this->executeFrontendRequest(
858 new InternalRequest('https://website.local/pl/?id=' . static::VALUE_PageId)
859 );
860 $responseStructure = ResponseContent::fromString((string)$response->getBody());
861 $responseSections = $responseStructure->getSections();
862
863 $this->assertEquals(200, $response->getStatusCode());
864
865 $this->assertThat(
866 $responseSections,
867 $this->getRequestSectionHasRecordConstraint()
868 ->setTable(self::TABLE_Content)
869 ->setField('header')
870 ->setValues(...$visibleHeaders)
871 );
872 $this->assertThat(
873 $responseSections,
874 $this->getRequestSectionDoesNotHaveRecordConstraint()
875 ->setTable(self::TABLE_Content)
876 ->setField('header')
877 ->setValues(...$this->getNonVisibleHeaders($visibleHeaders))
878 );
879
880 $this->assertEquals('[PL]Page', $responseStructure->getScopePath('page/title'));
881 $this->assertEquals(3, $responseStructure->getScopePath('languageInfo/id'), 'languageId does not match');
882 $this->assertEquals(3, $responseStructure->getScopePath('languageInfo/contentId'), 'contentId does not match');
883 $this->assertEquals($fallbackType, $responseStructure->getScopePath('languageInfo/fallbackType'), 'fallbackType does not match');
884 $this->assertEquals($fallbackChain, $responseStructure->getScopePath('languageInfo/fallbackChain'), 'fallbackChain does not match');
885 $this->assertEquals($overlayType, $responseStructure->getScopePath('languageInfo/overlayType'), 'language overlayType does not match');
886 }
887
888 /**
889 * Helper function to ease asserting that rest of the data set is not visible
890 *
891 * @param array $visibleHeaders
892 * @return array
893 */
894 protected function getNonVisibleHeaders(array $visibleHeaders): array
895 {
896 $allElements = [
897 'Regular Element #1',
898 'Regular Element #2',
899 'Regular Element #3',
900 'Hidden Element #4',
901 '[Translate to Dansk:] Regular Element #1',
902 '[Translate to Dansk:] Regular Element #3',
903 '[DK] Without default language',
904 '[DK] UnHidden Element #4',
905 '[DE] Without default language',
906 '[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1',
907 '[Translate to Polski:] Regular Element #1',
908 '[PL] Without default language',
909 '[PL] Hidden Regular Element #2'
910 ];
911 return array_diff($allElements, $visibleHeaders);
912 }
913
914 /**
915 * Helper function to ease asserting that rest of the files are not present
916 *
917 * @param array $visibleTitles
918 * @return array
919 */
920 protected function getNonVisibleFileTitles(array $visibleTitles): array
921 {
922 $allElements = [
923 'T3BOARD',
924 'Kasper',
925 '[Kasper] Image translated to Dansk',
926 '[T3BOARD] Image added in Dansk (without parent)',
927 '[T3BOARD] Image added to DK element without default language',
928 '[T3BOARD] image translated to DE from DK',
929 'Kasper2',
930 ];
931 return array_diff($allElements, $visibleTitles);
932 }
933 }