[BUGFIX] Pass language mode to QuerySettings in default language too
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Functional / Persistence / QueryLocalizedDataTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
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 PHPUnit\Framework\MockObject\MockObject;
19 use TYPO3\CMS\Core\Context\Context;
20 use TYPO3\CMS\Core\Context\LanguageAspect;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
23 use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
24 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
25 use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
26 use TYPO3\CMS\Extbase\Service\EnvironmentService;
27 use TYPO3\CMS\Frontend\Page\PageRepository;
28
29 class QueryLocalizedDataTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
30 {
31 /**
32 * @var array
33 */
34 protected $testExtensionsToLoad = ['typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example'];
35
36 /**
37 * @var array
38 */
39 protected $coreExtensionsToLoad = ['extbase', 'fluid'];
40
41 /**
42 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface The object manager
43 */
44 protected $objectManager;
45
46 /**
47 * @var \TYPO3\CMS\Extbase\Persistence\Repository
48 */
49 protected $postRepository;
50
51 /**
52 * @var PersistenceManager;
53 */
54 protected $persistenceManager;
55
56 /**
57 * Sets up this test suite.
58 */
59 protected function setUp()
60 {
61 parent::setUp();
62
63 $this->importCSVDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translatedBlogExampleData.csv');
64 $this->setUpBasicFrontendEnvironment();
65
66 $this->objectManager = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
67 $configuration = [
68 'persistence' => [
69 'storagePid' => 20,
70 'classes' => [
71 'TYPO3\CMS\Extbase\Domain\Model\Category' => [
72 'mapping' => ['tableName' => 'sys_category']
73 ]
74 ]
75 ]
76 ];
77 $configurationManager = $this->objectManager->get(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::class);
78 $configurationManager->setConfiguration($configuration);
79 $this->postRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\PostRepository::class);
80 $this->persistenceManager = $this->objectManager->get(PersistenceManager::class);
81 }
82
83 /**
84 * Minimal frontend environment to satisfy Extbase Typo3DbBackend
85 */
86 protected function setUpBasicFrontendEnvironment()
87 {
88 /** @var MockObject|EnvironmentService $environmentServiceMock */
89 $environmentServiceMock = $this->createMock(EnvironmentService::class);
90 $environmentServiceMock
91 ->expects($this->atLeast(1))
92 ->method('isEnvironmentInFrontendMode')
93 ->willReturn(true);
94 GeneralUtility::setSingletonInstance(EnvironmentService::class, $environmentServiceMock);
95
96 $context = GeneralUtility::makeInstance(Context::class);
97 $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_ON, []));
98
99 $pageRepositoryFixture = new PageRepository();
100 $frontendControllerMock = $this->createMock(\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::class);
101 $frontendControllerMock->sys_page = $pageRepositoryFixture;
102 $GLOBALS['TSFE'] = $frontendControllerMock;
103 }
104
105 /**
106 * Test in default language
107 *
108 * With overlays enabled it doesn't make a difference whether you call findByUid with translated record uid or
109 * default language record uid.
110 *
111 * @test
112 */
113 public function findByUidOverlayModeOnDefaultLanguage()
114 {
115 $context = GeneralUtility::makeInstance(Context::class);
116 $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_ON));
117
118 $post2 = $this->postRepository->findByUid(2);
119
120 $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
121 $post2->getTitle(),
122 $post2->getUid(),
123 $post2->_getProperty('_localizedUid'),
124 $post2->getBlog()->getTitle(),
125 $post2->getBlog()->getUid(),
126 $post2->getBlog()->_getProperty('_localizedUid'),
127 $post2->getAuthor()->getFirstname(),
128 $post2->getAuthor()->getUid(),
129 $post2->getAuthor()->_getProperty('_localizedUid')
130 ]);
131
132 //this is needed because of https://forge.typo3.org/issues/59992
133 $this->persistenceManager->clearState();
134
135 $post2translated = $this->postRepository->findByUid(11);
136 $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
137 $post2translated->getTitle(),
138 $post2translated->getUid(),
139 $post2translated->_getProperty('_localizedUid'),
140 $post2translated->getBlog()->getTitle(),
141 $post2translated->getBlog()->getUid(),
142 $post2translated->getBlog()->_getProperty('_localizedUid'),
143 $post2translated->getAuthor()->getFirstname(),
144 $post2translated->getAuthor()->getUid(),
145 $post2translated->getAuthor()->_getProperty('_localizedUid')
146 ]);
147 }
148
149 /**
150 * Test in default language, overlays disabled
151 *
152 * @test
153 */
154 public function findByUidNoOverlaysDefaultLanguage()
155 {
156 $context = GeneralUtility::makeInstance(Context::class);
157 $context->setAspect('language', new LanguageAspect(0, 0, LanguageAspect::OVERLAYS_OFF));
158
159 $post2 = $this->postRepository->findByUid(2);
160 $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
161 $post2->getTitle(),
162 $post2->getUid(),
163 $post2->_getProperty('_localizedUid'),
164 $post2->getBlog()->getTitle(),
165 $post2->getBlog()->getUid(),
166 $post2->getBlog()->_getProperty('_localizedUid'),
167 $post2->getAuthor()->getFirstname(),
168 $post2->getAuthor()->getUid(),
169 $post2->getAuthor()->_getProperty('_localizedUid')
170 ]);
171
172 //this is needed because of https://forge.typo3.org/issues/59992
173 $this->persistenceManager->clearState();
174
175 $post2translated = $this->postRepository->findByUid(11);
176 $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
177 $post2translated->getTitle(),
178 $post2translated->getUid(),
179 $post2translated->_getProperty('_localizedUid'),
180 $post2translated->getBlog()->getTitle(),
181 $post2translated->getBlog()->getUid(),
182 $post2translated->getBlog()->_getProperty('_localizedUid'),
183 $post2translated->getAuthor()->getFirstname(),
184 $post2translated->getAuthor()->getUid(),
185 $post2translated->getAuthor()->_getProperty('_localizedUid')
186 ]);
187 }
188
189 /**
190 * Test in language uid:1, overlays enabled
191 *
192 * With overlays enabled it doesn't make a difference whether you call findByUid with translated record uid or
193 * default language record uid. Of course we're in the &L=1 and record uid 2 has translation (uid 11).
194 *
195 * @test
196 */
197 public function findByUidOverlayModeOnLanguage()
198 {
199 $context = GeneralUtility::makeInstance(Context::class);
200 $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_ON));
201
202 $post2 = $this->postRepository->findByUid(2);
203 //this is needed because of https://forge.typo3.org/issues/59992
204 $this->persistenceManager->clearState();
205 $post2translated = $this->postRepository->findByUid(11);
206
207 foreach ([$post2, $post2translated] as $post) {
208 $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
209 $post->getTitle(),
210 $post->getUid(),
211 $post->_getProperty('_localizedUid'),
212 $post->getBlog()->getTitle(),
213 $post->getBlog()->getUid(),
214 $post->getBlog()->_getProperty('_localizedUid'),
215 $post->getAuthor()->getFirstname(),
216 $post->getAuthor()->getUid(),
217 $post->getAuthor()->_getProperty('_localizedUid')
218 ]);
219 }
220 }
221
222 /**
223 * Test in language uid:1, overlays disabled
224 *
225 * @test
226 */
227 public function findByUidNoOverlaysLanguage()
228 {
229 $context = GeneralUtility::makeInstance(Context::class);
230 $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_OFF));
231
232 $post2 = $this->postRepository->findByUid(2);
233 $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
234 $post2->getTitle(),
235 $post2->getUid(),
236 $post2->_getProperty('_localizedUid'),
237 $post2->getBlog()->getTitle(),
238 $post2->getBlog()->getUid(),
239 $post2->getBlog()->_getProperty('_localizedUid'),
240 $post2->getAuthor()->getFirstname(),
241 $post2->getAuthor()->getUid(),
242 $post2->getAuthor()->_getProperty('_localizedUid')
243 ]);
244
245 //this is needed because of https://forge.typo3.org/issues/59992
246 $this->persistenceManager->clearState();
247
248 $post2translated = $this->postRepository->findByUid(11);
249 $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1 DK', 1, 2, 'Translated John', 1, 2], [
250 $post2translated->getTitle(),
251 $post2translated->getUid(),
252 $post2translated->_getProperty('_localizedUid'),
253 $post2translated->getBlog()->getTitle(),
254 $post2translated->getBlog()->getUid(),
255 $post2translated->getBlog()->_getProperty('_localizedUid'),
256 $post2translated->getAuthor()->getFirstname(),
257 $post2translated->getAuthor()->getUid(),
258 $post2translated->getAuthor()->_getProperty('_localizedUid')
259 ]);
260 }
261
262 /**
263 * This tests shows what query by uid returns depending on the language,
264 * and used uid (default language record or translated record uid).
265 * All with overlay mode enabled.
266 *
267 * The post with uid 2 is translated to language 1, and there has uid 11.
268 *
269 * @test
270 */
271 public function customFindByUidOverlayEnabled()
272 {
273 // we're in default lang and fetching by uid of the record in default language
274 $query = $this->postRepository->createQuery();
275 $querySettings = $query->getQuerySettings();
276 $querySettings->setLanguageUid(0);
277 $querySettings->setLanguageOverlayMode(true);
278 $query->matching($query->equals('uid', 2));
279 $post2 = $query->execute()->getFirst();
280
281 $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
282 $post2->getTitle(),
283 $post2->getUid(),
284 $post2->_getProperty('_localizedUid'),
285 $post2->getBlog()->getTitle(),
286 $post2->getBlog()->getUid(),
287 $post2->getBlog()->_getProperty('_localizedUid'),
288 $post2->getAuthor()->getFirstname(),
289 $post2->getAuthor()->getUid(),
290 $post2->getAuthor()->_getProperty('_localizedUid')
291 ]);
292
293 //this is needed because of https://forge.typo3.org/issues/59992
294 $this->persistenceManager->clearState();
295
296 $query = $this->postRepository->createQuery();
297 $querySettings = $query->getQuerySettings();
298 $querySettings->setLanguageUid(0);
299 $querySettings->setLanguageOverlayMode(true);
300 $query->matching($query->equals('uid', 11));
301 $post2 = $query->execute()->getFirst();
302
303 $this->assertNull($post2);
304
305 //this is needed because of https://forge.typo3.org/issues/59992
306 $this->persistenceManager->clearState();
307
308 $query = $this->postRepository->createQuery();
309 $querySettings = $query->getQuerySettings();
310 $querySettings->setLanguageUid(1);
311 $querySettings->setLanguageOverlayMode(true);
312 $query->matching($query->equals('uid', 2));
313 $post2 = $query->execute()->getFirst();
314
315 $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1', 1, 1, 'John', 1, 1], [
316 $post2->getTitle(),
317 $post2->getUid(),
318 $post2->_getProperty('_localizedUid'),
319 $post2->getBlog()->getTitle(),
320 $post2->getBlog()->getUid(),
321 $post2->getBlog()->_getProperty('_localizedUid'),
322 $post2->getAuthor()->getFirstname(),
323 $post2->getAuthor()->getUid(),
324 $post2->getAuthor()->_getProperty('_localizedUid')
325 ]);
326
327 //this is needed because of https://forge.typo3.org/issues/59992
328 $this->persistenceManager->clearState();
329
330 $query = $this->postRepository->createQuery();
331 $querySettings = $query->getQuerySettings();
332 $querySettings->setLanguageUid(1);
333 $querySettings->setLanguageOverlayMode(true);
334 $query->matching($query->equals('uid', 11));
335 $post2 = $query->execute()->getFirst();
336
337 $this->assertNull($post2);
338 }
339
340 /**
341 * This tests shows what query by uid returns depending on the language,
342 * and used uid (default language record or translated record uid).
343 * All with overlay mode disabled.
344 *
345 * The post with uid 2 is translated to language 1, and there has uid 11.
346 *
347 * @test
348 */
349 public function customFindByUidOverlayDisabled()
350 {
351 $query = $this->postRepository->createQuery();
352 $querySettings = $query->getQuerySettings();
353 $querySettings->setLanguageUid(0);
354 $querySettings->setLanguageOverlayMode(false);
355 $query->matching($query->equals('uid', 2));
356 $post2 = $query->execute()->getFirst();
357
358 $this->assertEquals(['Post 2', 2, 2, 'Blog 1', 1, 1, 'John', 1, 1], [
359 $post2->getTitle(),
360 $post2->getUid(),
361 $post2->_getProperty('_localizedUid'),
362 $post2->getBlog()->getTitle(),
363 $post2->getBlog()->getUid(),
364 $post2->getBlog()->_getProperty('_localizedUid'),
365 $post2->getAuthor()->getFirstname(),
366 $post2->getAuthor()->getUid(),
367 $post2->getAuthor()->_getProperty('_localizedUid')
368 ]);
369
370 //this is needed because of https://forge.typo3.org/issues/59992
371 $this->persistenceManager->clearState();
372
373 $query = $this->postRepository->createQuery();
374 $querySettings = $query->getQuerySettings();
375 $querySettings->setLanguageUid(0);
376 $querySettings->setLanguageOverlayMode(false);
377 $query->matching($query->equals('uid', 11));
378 $post2 = $query->execute()->getFirst();
379
380 $this->assertNull($post2);
381
382 //this is needed because of https://forge.typo3.org/issues/59992
383 $this->persistenceManager->clearState();
384
385 $query = $this->postRepository->createQuery();
386 $querySettings = $query->getQuerySettings();
387 $querySettings->setLanguageUid(1);
388 $querySettings->setLanguageOverlayMode(false);
389 $query->matching($query->equals('uid', 2));
390 $post2 = $query->execute()->getFirst();
391
392 //the john is not translated but it should, see next query in this test
393 $this->assertEquals(['Post 2 - DK', 2, 11, 'Blog 1', 1, 1, 'John', 1, 1], [
394 $post2->getTitle(),
395 $post2->getUid(),
396 $post2->_getProperty('_localizedUid'),
397 $post2->getBlog()->getTitle(),
398 $post2->getBlog()->getUid(),
399 $post2->getBlog()->_getProperty('_localizedUid'),
400 $post2->getAuthor()->getFirstname(),
401 $post2->getAuthor()->getUid(),
402 $post2->getAuthor()->_getProperty('_localizedUid')
403 ]);
404
405 //this is needed because of https://forge.typo3.org/issues/59992
406 $this->persistenceManager->clearState();
407
408 $query = $this->postRepository->createQuery();
409 $querySettings = $query->getQuerySettings();
410 $querySettings->setLanguageUid(1);
411 $querySettings->setLanguageOverlayMode(false);
412 $query->matching($query->equals('uid', 11));
413 $post2 = $query->execute()->getFirst();
414
415 $this->assertNull($post2);
416
417 //We're setting global context here to show that the result is different then one above.
418 //this means that language which is set in global context influences overlays of relations
419 $context = GeneralUtility::makeInstance(Context::class);
420 $context->setAspect('language', new LanguageAspect(1, 1, LanguageAspect::OVERLAYS_ON));
421
422 //this is needed because of https://forge.typo3.org/issues/59992
423 $this->persistenceManager->clearState();
424
425 $query = $this->postRepository->createQuery();
426 $querySettings = $query->getQuerySettings();
427 $querySettings->setLanguageUid(1);
428 $querySettings->setLanguageOverlayMode(false);
429 $query->matching($query->equals('uid', 11));
430 $post2 = $query->execute()->getFirst();
431
432 $this->assertNull($post2);
433 }
434
435 public function queryFirst5PostsDataProvider()
436 {
437 //put it to variable to make cases with the same expected values explicit
438 $lang0Expected = [
439 [
440 'title' => 'Post 4',
441 'uid' => 4,
442 '_localizedUid' => 4,
443 'blog.title' => 'Blog 1',
444 'blog.uid' => 1,
445 'blog._localizedUid' => 1,
446 'author.firstname' => 'John',
447 'author.uid' => 1,
448 'author._localizedUid' => 1,
449 'secondAuthor.firstname' => 'John',
450 'secondAuthor.uid' => 1,
451 'secondAuthor._localizedUid' => 1,
452 'tags' => [],
453 ],
454 [
455 'title' => 'Post 2',
456 'uid' => 2,
457 '_localizedUid' => 2,
458 'blog.title' => 'Blog 1',
459 'blog.uid' => 1,
460 'blog._localizedUid' => 1,
461 'author.firstname' => 'John',
462 'author.uid' => 1,
463 'author._localizedUid' => 1,
464 'secondAuthor.firstname' => 'John',
465 'secondAuthor.uid' => 1,
466 'secondAuthor._localizedUid' => 1,
467 'tags.0.name' => 'Tag2',
468 'tags.0.uid' => 2,
469 'tags.0._localizedUid' => 2,
470 'tags.1.name' => 'Tag3',
471 'tags.1.uid' => 3,
472 'tags.1._localizedUid' => 3,
473 'tags.2.name' => 'Tag4',
474 'tags.2.uid' => 4,
475 'tags.2._localizedUid' => 4,
476 ],
477 [
478 'title' => 'Post 7',
479 'uid' => 7,
480 '_localizedUid' => 7,
481 'blog.title' => 'Blog 1',
482 'blog.uid' => 1,
483 'blog._localizedUid' => 1,
484 'author.firstname' => 'John',
485 'author.uid' => 1,
486 'author._localizedUid' => 1,
487 'secondAuthor.firstname' => 'John',
488 'secondAuthor.uid' => 1,
489 'secondAuthor._localizedUid' => 1,
490 'tags' => [],
491 ],
492 [
493 'title' => 'Post 6',
494 'uid' => 6,
495 '_localizedUid' => 6,
496 'blog.title' => 'Blog 1',
497 'blog.uid' => 1,
498 'blog._localizedUid' => 1,
499 'author.firstname' => 'John',
500 'author.uid' => 1,
501 'author._localizedUid' => 1,
502 'secondAuthor.firstname' => 'John',
503 'secondAuthor.uid' => 1,
504 'secondAuthor._localizedUid' => 1,
505 'tags' => [],
506 ],
507 [
508 'title' => 'Post 1 - not translated',
509 'uid' => 1,
510 '_localizedUid' => 1,
511 'blog.title' => 'Blog 1',
512 'blog.uid' => 1,
513 'blog._localizedUid' => 1,
514 'author.firstname' => 'John',
515 'author.uid' => 1,
516 'author._localizedUid' => 1,
517 'secondAuthor.firstname' => 'Never translate me henry',
518 'secondAuthor.uid' => 3,
519 'secondAuthor._localizedUid' => 3,
520 'tags.0.name' => 'Tag1',
521 'tags.0.uid' => 1,
522 'tags.0._localizedUid' => 1,
523 'tags.1.name' => 'Tag2',
524 'tags.1.uid' => 2,
525 'tags.1._localizedUid' => 2,
526 'tags.2.name' => 'Tag3',
527 'tags.2.uid' => 3,
528 'tags.2._localizedUid' => 3,
529 ],
530 ];
531 return [
532 [
533 'language' => 0,
534 'overlay' => true,
535 'expected' => $lang0Expected
536 ],
537 [
538 'language' => 0,
539 'overlay' => false,
540 'expected' => $lang0Expected
541 ],
542 [
543 'language' => 1,
544 'overlay' => true,
545 'expected' => [
546 [
547 'title' => 'Post 4 - DK',
548 'uid' => 4,
549 '_localizedUid' => 12,
550 'blog.title' => 'Blog 1',
551 'blog.uid' => 1,
552 'blog._localizedUid' => 1,
553 'author.firstname' => 'John',
554 'author.uid' => 1,
555 'author._localizedUid' => 1,
556 'secondAuthor.firstname' => 'John',
557 'secondAuthor.uid' => 1,
558 'secondAuthor._localizedUid' => 1,
559 'tags' => [],
560 ],
561 [
562 'title' => 'Post 2 - DK',
563 'uid' => 2,
564 '_localizedUid' => 11,
565 'blog.title' => 'Blog 1',
566 'blog.uid' => 1,
567 'blog._localizedUid' => 1,
568 'author.firstname' => 'John',
569 'author.uid' => 1,
570 'author._localizedUid' => 1,
571 'secondAuthor.firstname' => 'John',
572 'secondAuthor.uid' => 1,
573 'secondAuthor._localizedUid' => 1,
574 'tags.0.name' => 'Tag2',
575 'tags.0.uid' => 2,
576 'tags.0._localizedUid' => 2,
577 'tags.1.name' => 'Tag3',
578 'tags.1.uid' => 3,
579 'tags.1._localizedUid' => 3,
580 'tags.2.name' => 'Tag4',
581 'tags.2.uid' => 4,
582 'tags.2._localizedUid' => 4,
583 ],
584 [
585 'title' => 'Post DK only',
586 'uid' => 15,
587 '_localizedUid' => 15,
588 'blog.title' => 'Blog 1',
589 'blog.uid' => 1,
590 'blog._localizedUid' => 1,
591 'author.firstname' => 'John',
592 'author.uid' => 1,
593 'author._localizedUid' => 1,
594 'secondAuthor.firstname' => 'John',
595 'secondAuthor.uid' => 1,
596 'secondAuthor._localizedUid' => 1,
597 'tags' => [],
598 ],
599 [
600 'title' => 'Post 7 - DK',
601 'uid' => 7,
602 '_localizedUid' => 14,
603 'blog.title' => 'Blog 1',
604 'blog.uid' => 1,
605 'blog._localizedUid' => 1,
606 'author.firstname' => 'John',
607 'author.uid' => 1,
608 'author._localizedUid' => 1,
609 'secondAuthor.firstname' => 'John',
610 'secondAuthor.uid' => 1,
611 'secondAuthor._localizedUid' => 1,
612 'tags' => [],
613 ],
614 [
615 'title' => 'Post 5 - DK',
616 'uid' => 5,
617 '_localizedUid' => 13,
618 'blog.title' => 'Blog 1',
619 'blog.uid' => 1,
620 'blog._localizedUid' => 1,
621 'author.firstname' => 'John',
622 'author.uid' => 1,
623 'author._localizedUid' => 1,
624 'secondAuthor.firstname' => 'John',
625 'secondAuthor.uid' => 1,
626 'secondAuthor._localizedUid' => 1,
627 'tags' => [],
628 ],
629 ],
630 ],
631 [
632 'language' => 1,
633 'overlay' => 'hideNonTranslated',
634 'expected' => [
635 [
636 'title' => 'Post 4 - DK',
637 'uid' => 4,
638 '_localizedUid' => 12,
639 'blog.title' => 'Blog 1',
640 'blog.uid' => 1,
641 'blog._localizedUid' => 1,
642 'author.firstname' => 'John',
643 'author.uid' => 1,
644 'author._localizedUid' => 1,
645 'secondAuthor.firstname' => 'John',
646 'secondAuthor.uid' => 1,
647 'secondAuthor._localizedUid' => 1,
648 'tags' => [],
649 ],
650 [
651 'title' => 'Post 2 - DK',
652 'uid' => 2,
653 '_localizedUid' => 11,
654 'blog.title' => 'Blog 1',
655 'blog.uid' => 1,
656 'blog._localizedUid' => 1,
657 'author.firstname' => 'John',
658 'author.uid' => 1,
659 'author._localizedUid' => 1,
660 'secondAuthor.firstname' => 'John',
661 'secondAuthor.uid' => 1,
662 'secondAuthor._localizedUid' => 1,
663 'tags.0.name' => 'Tag2',
664 'tags.0.uid' => 2,
665 'tags.0._localizedUid' => 2,
666 'tags.1.name' => 'Tag3',
667 'tags.1.uid' => 3,
668 'tags.1._localizedUid' => 3,
669 'tags.2.name' => 'Tag4',
670 'tags.2.uid' => 4,
671 'tags.2._localizedUid' => 4,
672 ],
673 [
674 'title' => 'Post DK only',
675 'uid' => 15,
676 '_localizedUid' => 15,
677 'blog.title' => 'Blog 1',
678 'blog.uid' => 1,
679 'blog._localizedUid' => 1,
680 'author.firstname' => 'John',
681 'author.uid' => 1,
682 'author._localizedUid' => 1,
683 'secondAuthor.firstname' => 'John',
684 'secondAuthor.uid' => 1,
685 'secondAuthor._localizedUid' => 1,
686 'tags' => [],
687 ],
688 [
689 'title' => 'Post 7 - DK',
690 'uid' => 7,
691 '_localizedUid' => 14,
692 'blog.title' => 'Blog 1',
693 'blog.uid' => 1,
694 'blog._localizedUid' => 1,
695 'author.firstname' => 'John',
696 'author.uid' => 1,
697 'author._localizedUid' => 1,
698 'secondAuthor.firstname' => 'John',
699 'secondAuthor.uid' => 1,
700 'secondAuthor._localizedUid' => 1,
701 'tags' => [],
702
703 ],
704 [
705 'title' => 'Post 5 - DK',
706 'uid' => 5,
707 '_localizedUid' => 13,
708 'blog.title' => 'Blog 1',
709 'blog.uid' => 1,
710 'blog._localizedUid' => 1,
711 'author.firstname' => 'John',
712 'author.uid' => 1,
713 'author._localizedUid' => 1,
714 'secondAuthor.firstname' => 'John',
715 'secondAuthor.uid' => 1,
716 'secondAuthor._localizedUid' => 1,
717 'tags' => [],
718 ],
719 ],
720 ],
721 [
722 'language' => 1,
723 'overlay' => false,
724 'expected' => [
725 [
726 'title' => 'Post 4 - DK',
727 'uid' => 4,
728 '_localizedUid' => 12,
729 'blog.title' => 'Blog 1',
730 'blog.uid' => 1,
731 'blog._localizedUid' => 1,
732 'author.firstname' => 'John',
733 'author.uid' => 1,
734 'author._localizedUid' => 1,
735 'secondAuthor.firstname' => 'John',
736 'secondAuthor.uid' => 1,
737 'secondAuthor._localizedUid' => 1,
738 'tags' => [],
739 ],
740 [
741 'title' => 'Post 2 - DK',
742 'uid' => 2,
743 '_localizedUid' => 11,
744 'blog.title' => 'Blog 1',
745 'blog.uid' => 1,
746 'blog._localizedUid' => 1,
747 'author.firstname' => 'John',
748 'author.uid' => 1,
749 'author._localizedUid' => 1,
750 'secondAuthor.firstname' => 'John',
751 'secondAuthor.uid' => 1,
752 'secondAuthor._localizedUid' => 1,
753 'tags.0.name' => 'Tag2',
754 'tags.0.uid' => 2,
755 'tags.0._localizedUid' => 2,
756 'tags.1.name' => 'Tag3',
757 'tags.1.uid' => 3,
758 'tags.1._localizedUid' => 3,
759 'tags.2.name' => 'Tag4',
760 'tags.2.uid' => 4,
761 'tags.2._localizedUid' => 4,
762 ],
763 [
764 'title' => 'Post DK only',
765 'uid' => 15,
766 '_localizedUid' => 15,
767 'blog.title' => 'Blog 1',
768 'blog.uid' => 1,
769 'blog._localizedUid' => 1,
770 'author.firstname' => 'John',
771 'author.uid' => 1,
772 'author._localizedUid' => 1,
773 'secondAuthor.firstname' => 'John',
774 'secondAuthor.uid' => 1,
775 'secondAuthor._localizedUid' => 1,
776 'tags' => [],
777 ],
778 [
779 'title' => 'Post 7 - DK',
780 'uid' => 7,
781 '_localizedUid' => 14,
782 'blog.title' => 'Blog 1',
783 'blog.uid' => 1,
784 'blog._localizedUid' => 1,
785 'author.firstname' => 'John',
786 'author.uid' => 1,
787 'author._localizedUid' => 1,
788 'secondAuthor.firstname' => 'John',
789 'secondAuthor.uid' => 1,
790 'secondAuthor._localizedUid' => 1,
791 'tags' => [],
792 ],
793 [
794 'title' => 'Post 5 - DK',
795 'uid' => 5,
796 '_localizedUid' => 13,
797 'blog.title' => 'Blog 1',
798 'blog.uid' => 1,
799 'blog._localizedUid' => 1,
800 'author.firstname' => 'John',
801 'author.uid' => 1,
802 'author._localizedUid' => 1,
803 'secondAuthor.firstname' => 'John',
804 'secondAuthor.uid' => 1,
805 'secondAuthor._localizedUid' => 1,
806 'tags' => [],
807 ],
808 ],
809 ],
810 ];
811 }
812
813 /**
814 * This test check posts returned by repository, when changing language and languageOverlayMode
815 * It also sets limit, offset to validate there are no "gaps" in pagination
816 * and sorting (on a posts property)
817 *
818 * @test
819 * @dataProvider queryFirst5PostsDataProvider
820 *
821 * @param int $languageUid
822 * @param bool $overlay
823 * @param array $expected
824 */
825 public function queryFirst5Posts($languageUid, $overlay, $expected)
826 {
827 $query = $this->postRepository->createQuery();
828 $querySettings = $query->getQuerySettings();
829 $querySettings->setLanguageUid($languageUid);
830 $querySettings->setLanguageOverlayMode($overlay);
831
832 $query->setOrderings([
833 'content' => QueryInterface::ORDER_ASCENDING,
834 'uid' => QueryInterface::ORDER_ASCENDING
835 ]);
836 $query->setLimit(5);
837 $query->setOffset(0);
838 $posts = $query->execute()->toArray();
839
840 $this->assertCount(5, $posts);
841 $this->assertObjectsProperties($posts, $expected);
842 }
843
844 public function queryPostsByPropertyDataProvider()
845 {
846 $lang0Expected = [
847 [
848 'title' => 'Post 5',
849 'uid' => 5,
850 '_localizedUid' => 5,
851 'blog.title' => 'Blog 1',
852 'blog.uid' => 1,
853 'blog._localizedUid' => 1,
854 'author.firstname' => 'John',
855 'author.uid' => 1,
856 'author._localizedUid' => 1,
857 'secondAuthor.firstname' => 'John',
858 'secondAuthor.uid' => 1,
859 'secondAuthor._localizedUid' => 1,
860 ],
861 [
862 'title' => 'Post 6',
863 'uid' => 6,
864 '_localizedUid' => 6,
865 'blog.title' => 'Blog 1',
866 'blog.uid' => 1,
867 'blog._localizedUid' => 1,
868 'author.firstname' => 'John',
869 'author.uid' => 1,
870 'author._localizedUid' => 1,
871 'secondAuthor.firstname' => 'John',
872 'secondAuthor.uid' => 1,
873 'secondAuthor._localizedUid' => 1,
874 'tags' => [],
875 ]
876 ];
877 $lang1Expected = [
878 [
879 'title' => 'Post 5 - DK',
880 'uid' => 5,
881 '_localizedUid' => 13,
882 'blog.title' => 'Blog 1',
883 'blog.uid' => 1,
884 'blog._localizedUid' => 1,
885 'author.firstname' => 'John',
886 'author.uid' => 1,
887 'author._localizedUid' => 1,
888 'secondAuthor.firstname' => 'John',
889 'secondAuthor.uid' => 1,
890 'secondAuthor._localizedUid' => 1,
891 ],
892 [
893 'title' => 'Post DK only',
894 'uid' => 15,
895 '_localizedUid' => 15,
896 'blog.title' => 'Blog 1',
897 'blog.uid' => 1,
898 'blog._localizedUid' => 1,
899 'author.firstname' => 'John',
900 'author.uid' => 1,
901 'author._localizedUid' => 1,
902 'secondAuthor.firstname' => 'John',
903 'secondAuthor.uid' => 1,
904 'secondAuthor._localizedUid' => 1,
905 ],
906
907 ];
908 return [
909 [
910 'language' => 0,
911 'overlay' => true,
912 'expected' => $lang0Expected
913 ],
914 [
915 'language' => 0,
916 'overlay' => 'hideNonTranslated',
917 'expected' => $lang0Expected
918 ],
919 [
920 'language' => 0,
921 'overlay' => false,
922 'expected' => $lang0Expected
923 ],
924 [
925 'language' => 1,
926 'overlay' => true,
927 'expected' => $lang1Expected
928 ],
929 [
930 'language' => 1,
931 'overlay' => 'hideNonTranslated',
932 'expected' => $lang1Expected,
933 ],
934 [
935 'language' => 1,
936 'overlay' => false,
937 'expected' => $lang1Expected,
938 ],
939 ];
940 }
941
942 /**
943 * This test check posts returned by repository, when filtering by property
944 *
945 * "Post 6" is not translated
946 * "Post 5" is translated as "Post 5 - DK"
947 * "Post DK only" has no translation parent
948 *
949 *
950 *
951 * @test
952 * @dataProvider queryPostsByPropertyDataProvider
953 *
954 * @param int $languageUid
955 * @param bool $overlay
956 * @param array $expected
957 */
958 public function queryPostsByProperty($languageUid, $overlay, $expected)
959 {
960 $query = $this->postRepository->createQuery();
961 $querySettings = $query->getQuerySettings();
962 $querySettings->setLanguageUid($languageUid);
963 $querySettings->setLanguageOverlayMode($overlay);
964
965 $query->matching(
966 $query->logicalOr(
967 $query->like('title', 'Post 5%'),
968 $query->like('title', 'Post 6%'),
969 $query->like('title', 'Post DK only')
970 )
971 );
972 $query->setOrderings(['uid' => QueryInterface::ORDER_ASCENDING]);
973 $posts = $query->execute()->toArray();
974
975 $this->assertCount(count($expected), $posts);
976 $this->assertObjectsProperties($posts, $expected);
977 }
978
979 public function postsWithoutRespectingSysLanguageDataProvider()
980 {
981 $lang0Expected = [
982 [
983 'title' => 'Blog 1',
984 'uid' => 1,
985 '_localizedUid' => 1,
986 ],
987 [
988 'title' => 'Blog 1',
989 'uid' => 1,
990 '_localizedUid' => 1,
991 ],
992 ];
993 $lang1Expected = [
994 [
995 'title' => 'Blog 1 DK',
996 'uid' => 1,
997 '_localizedUid' => 2,
998 ],
999 [
1000 'title' => 'Blog 1 DK',
1001 'uid' => 1,
1002 '_localizedUid' => 2,
1003 ],
1004 ];
1005 return [
1006 [
1007 'language' => 0,
1008 'overlay' => LanguageAspect::OVERLAYS_ON,
1009 'mode' => null,
1010 'expected' => $lang0Expected
1011 ],
1012 [
1013 'language' => 0,
1014 'overlay' => LanguageAspect::OVERLAYS_ON,
1015 'mode' => 'strict',
1016 'expected' => $lang0Expected
1017 ],
1018 [
1019 'language' => 0,
1020 'overlay' => LanguageAspect::OVERLAYS_OFF,
1021 'mode' => null,
1022 'expected' => $lang0Expected
1023 ],
1024 [
1025 'language' => 0,
1026 'overlay' => LanguageAspect::OVERLAYS_OFF,
1027 'mode' => 'strict',
1028 'expected' => $lang0Expected
1029 ],
1030 [
1031 'language' => 1,
1032 'overlay' => LanguageAspect::OVERLAYS_ON,
1033 'mode' => null,
1034 'expected' => $lang1Expected
1035 ],
1036 [
1037 'language' => 1,
1038 'overlay' => LanguageAspect::OVERLAYS_ON,
1039 'mode' => 'strict',
1040 'expected' => $lang1Expected
1041 ],
1042 [
1043 'language' => 1,
1044 'overlay' => LanguageAspect::OVERLAYS_OFF,
1045 'mode' => null,
1046 'expected' => $lang1Expected
1047 ],
1048 [
1049 'language' => 1,
1050 'overlay' => LanguageAspect::OVERLAYS_OFF,
1051 'mode' => 'strict',
1052 'expected' => $lang1Expected
1053 ],
1054 ];
1055 }
1056
1057 /**
1058 * This test demonstrates how query behaves when setRespectSysLanguage is set to false.
1059 * The test now documents the wrong behaviour described in https://forge.typo3.org/issues/45873
1060 * and is connected with https://forge.typo3.org/issues/59992
1061 *
1062 * The expected state is that when setRespectSysLanguage is false, then both: default language record,
1063 * and translated language record should be returned. Now we're getting same record twice.
1064 *
1065 * @test
1066 * @dataProvider postsWithoutRespectingSysLanguageDataProvider
1067 * @param int $languageUid
1068 * @param string|bool $overlay
1069 * @param string $languageMode
1070 * @param array $expected
1071 */
1072 public function postsWithoutRespectingSysLanguage($languageUid, $overlay, $languageMode, $expected)
1073 {
1074 $context = GeneralUtility::makeInstance(Context::class);
1075 $context->setAspect('language', new LanguageAspect($languageUid, $languageUid, $overlay));
1076
1077 $blogRepository = $this->objectManager->get(\ExtbaseTeam\BlogExample\Domain\Repository\BlogRepository::class);
1078 $query = $blogRepository->createQuery();
1079 $querySettings = $query->getQuerySettings();
1080 $querySettings->setLanguageMode($languageMode);
1081 $querySettings->setRespectSysLanguage(false);
1082
1083 $posts = $query->execute()->toArray();
1084
1085 $this->assertCount(count($expected), $posts);
1086 $this->assertObjectsProperties($posts, $expected);
1087 }
1088
1089 /**
1090 * Compares array of domain objects with array containing properties values
1091 *
1092 * @param array $objects
1093 * @param array $expected array of expected property values [ ['property' => 'value'], ['property' => 'value2']]
1094 */
1095 protected function assertObjectsProperties($objects, $expected)
1096 {
1097 $actual = [];
1098 foreach ($objects as $key => $post) {
1099 $actualPost = [];
1100 $propertiesToCheck = array_keys($expected[$key]);
1101 foreach ($propertiesToCheck as $propertyPath) {
1102 $actualPost[$propertyPath] = self::getPropertyPath($post, $propertyPath);
1103 }
1104 $actual[] = $actualPost;
1105 $this->assertEquals($expected[$key], $actual[$key], 'Assertion of the $expected[' . $key . '] failed');
1106 }
1107 $this->assertEquals($expected, $actual);
1108 }
1109
1110 /**
1111 * This is a copy of the ObjectAccess::getPropertyPath, but with third argument of getPropertyInternal set as true,
1112 * to access protected properties, and iterator_to_array added.
1113 *
1114 * @param mixed $subject Object or array to get the property path from
1115 * @param string $propertyPath
1116 *
1117 * @return mixed Value of the property
1118 */
1119 protected static function getPropertyPath($subject, $propertyPath)
1120 {
1121 $propertyPathSegments = explode('.', $propertyPath);
1122 try {
1123 foreach ($propertyPathSegments as $pathSegment) {
1124 $subject = ObjectAccess::getPropertyInternal($subject, $pathSegment, true);
1125 if ($subject instanceof \SplObjectStorage || $subject instanceof ObjectStorage) {
1126 $subject = iterator_to_array(clone $subject, false);
1127 }
1128 }
1129 } catch (\TYPO3\CMS\Extbase\Reflection\Exception\PropertyNotAccessibleException $error) {
1130 return null;
1131 }
1132 return $subject;
1133 }
1134 }