[BUGFIX] Fix Extbase language fallback in query parser 62/31862/2
authorHelmut Hummel <helmut.hummel@typo3.org>
Sun, 27 Jul 2014 08:55:35 +0000 (10:55 +0200)
committerHelmut Hummel <helmut.hummel@typo3.org>
Sun, 27 Jul 2014 10:56:10 +0000 (12:56 +0200)
Currently when TYPO3 is configured to do language fallback
and a record is translated in language A, but not
in language B and language B is requested, then this
record is excluded.

This is the case because of a wrong subselect condition.

Extbase selects records in the requested translation
or if no translation is available in the default language.
However the check if translation is available looks
for *any* translation not only for a translation in the
requested language. Thus the record from the default
language is not selected at all if there are translations
available in any other language.

Solution is to change the subselect condition to check
for the currently requested language.

Releases: 6.1, 6.2, master
Resolves: #60613
Change-Id: I8ebd68e1f5741d3557910ae2f8c2d19474548d01
Reviewed-on: http://review.typo3.org/31862
Reviewed-by: Helmut Hummel <helmut.hummel@typo3.org>
Tested-by: Helmut Hummel <helmut.hummel@typo3.org>
typo3/sysext/extbase/Classes/Persistence/Generic/Storage/Typo3DbQueryParser.php
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translated-posts.xml [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Persistence/TranslationTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php

index ff4167a..9db6ec4 100644 (file)
@@ -609,13 +609,13 @@ class Typo3DbQueryParser implements \TYPO3\CMS\Core\SingletonInterface {
                                                        ' AND ' . $tableName . '.uid IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
                                                        ' FROM ' . $tableName .
                                                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
-                                                       ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=' . (int)$querySettings->getLanguageUid() ;
+                                                       ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=' . (int)$querySettings->getLanguageUid();
                                        } else {
                                                $additionalWhereClause .= ' OR (' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
                                                        ' AND ' . $tableName . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
                                                        ' FROM ' . $tableName .
                                                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
-                                                       ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
+                                                       ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=' . (int)$querySettings->getLanguageUid();
                                        }
 
                                        // Add delete clause to ensure all entries are loaded
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translated-posts.xml b/typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translated-posts.xml
new file mode 100644 (file)
index 0000000..b2e8ca3
--- /dev/null
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <tx_blogexample_domain_model_post>
+               <uid>1</uid>
+               <pid>1</pid>
+               <blog>1</blog>
+               <title>Post1</title>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>2</uid>
+               <pid>1</pid>
+               <l18n_parent>1</l18n_parent>
+               <sys_language_uid>1</sys_language_uid>
+               <blog>1</blog>
+               <title>B EN:Post1</title>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>3</uid>
+               <pid>1</pid>
+               <l18n_parent>1</l18n_parent>
+               <sys_language_uid>2</sys_language_uid>
+               <blog>1</blog>
+               <title>GR:Post1</title>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>4</uid>
+               <pid>1</pid>
+               <blog>1</blog>
+               <title>Post2</title>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>5</uid>
+               <pid>1</pid>
+               <l18n_parent>4</l18n_parent>
+               <sys_language_uid>1</sys_language_uid>
+               <blog>1</blog>
+               <title>A EN:Post2</title>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_post>
+       <tx_blogexample_domain_model_post>
+               <uid>6</uid>
+               <pid>1</pid>
+               <blog>1</blog>
+               <title>Post3</title>
+               <deleted>0</deleted>
+       </tx_blogexample_domain_model_post>
+</dataset>
diff --git a/typo3/sysext/extbase/Tests/Functional/Persistence/TranslationTest.php b/typo3/sysext/extbase/Tests/Functional/Persistence/TranslationTest.php
new file mode 100644 (file)
index 0000000..66877e0
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
+
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use ExtbaseTeam\BlogExample\Domain\Model\Post;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
+class TranslationTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase {
+
+       /**
+        * @var array
+        */
+       protected $testExtensionsToLoad = array('typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/blog_example');
+
+       /**
+        * @var array
+        */
+       protected $coreExtensionsToLoad = array('extbase', 'fluid');
+
+       /**
+        * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface The object manager
+        */
+       protected $objectManager;
+
+       /**
+        * @var \TYPO3\CMS\Extbase\Persistence\Repository
+        */
+       protected $postRepository;
+
+       /**
+        * Sets up this test suite.
+        */
+       public function setUp() {
+               parent::setUp();
+               /*
+                * Posts Dataset for the tests:
+                *
+                * Post1
+                *   -> EN: Post1
+                *   -> GR: Post1
+                * Post2
+                *   -> EN: Post2
+                * Post3
+                */
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/pages.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/blogs.xml');
+               $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/translated-posts.xml');
+
+               $this->objectManager = GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
+               $this->postRepository = $this->objectManager->get('ExtbaseTeam\\BlogExample\\Domain\\Repository\\PostRepository');
+
+               $this->setUpBasicFrontendEnvironment();
+       }
+
+       /**
+        * Minimal frontent environment to satisfy Extbase Typo3DbBackend
+        */
+       protected function setUpBasicFrontendEnvironment() {
+               $environmentServiceMock = $this->getMock('TYPO3\\CMS\\Extbase\\Service\\EnvironmentService');
+               $environmentServiceMock
+                       ->expects($this->any())
+                       ->method('isEnvironmentInFrontendMode')
+                       ->willReturn(TRUE);
+               GeneralUtility::setSingletonInstance('TYPO3\\CMS\\Extbase\\Service\\EnvironmentService', $environmentServiceMock);
+
+               $pageRepositoryFixture = new PageRepository();
+               $frontendControllerMock = $this->getMock('TYPO3\\CMS\\Frontend\\Controller\\TypoScriptFrontendController', array(), array(), '', FALSE);
+               $frontendControllerMock->sys_page = $pageRepositoryFixture;
+               $GLOBALS['TSFE'] = $frontendControllerMock;
+       }
+
+       /**
+        * @test
+        */
+       public function countReturnsCorrectNumberOfPosts() {
+               $query = $this->postRepository->createQuery();
+
+               $querySettings = $query->getQuerySettings();
+               $querySettings->setStoragePageIds(array(1));
+               $querySettings->setRespectSysLanguage(TRUE);
+               $querySettings->setLanguageUid(0);
+
+               $postCount = $query->execute()->count();
+               $this->assertSame(3, $postCount);
+       }
+
+       /**
+        * @test
+        */
+       public function countReturnsCorrectNumberOfPostsInEnglishLanguage() {
+               $query = $this->postRepository->createQuery();
+
+               $querySettings = $query->getQuerySettings();
+               $querySettings->setStoragePageIds(array(1));
+               $querySettings->setRespectSysLanguage(TRUE);
+               $querySettings->setLanguageUid(1);
+
+               $postCount = $query->execute()->count();
+               $this->assertSame(3, $postCount);
+       }
+
+       /**
+        * @test
+        */
+       public function countReturnsCorrectNumberOfPostsInGreekLanguage() {
+               $query = $this->postRepository->createQuery();
+
+               $querySettings = $query->getQuerySettings();
+               $querySettings->setStoragePageIds(array(1));
+               $querySettings->setRespectSysLanguage(TRUE);
+               $querySettings->setLanguageUid(2);
+               $postCount = $query->execute()->count();
+
+               $this->assertSame(3, $postCount);
+       }
+
+       /**
+        * @test
+        */
+       public function fetchingPostsReturnsEnglishPostsWithFallback() {
+               $query = $this->postRepository->createQuery();
+
+               $querySettings = $query->getQuerySettings();
+               $querySettings->setStoragePageIds(array(1));
+               $querySettings->setRespectSysLanguage(TRUE);
+               $querySettings->setLanguageUid(1);
+
+               /** @var Post[] $posts */
+               $posts = $query->execute()->toArray();
+
+               $this->assertCount(3, $posts);
+               $this->assertSame('B EN:Post1', $posts[0]->getTitle());
+               $this->assertSame('A EN:Post2', $posts[1]->getTitle());
+               $this->assertSame('Post3', $posts[2]->getTitle());
+       }
+
+       /**
+        * @test
+        */
+       public function fetchingPostsReturnsGreekPostsWithFallback() {
+               $query = $this->postRepository->createQuery();
+
+               $querySettings = $query->getQuerySettings();
+               $querySettings->setStoragePageIds(array(1));
+               $querySettings->setRespectSysLanguage(TRUE);
+               $querySettings->setLanguageUid(2);
+
+               /** @var Post[] $posts */
+               $posts = $query->execute()->toArray();
+
+               $this->assertCount(3, $posts);
+               $this->assertSame('GR:Post1', $posts[0]->getTitle());
+               $this->assertSame('Post2', $posts[1]->getTitle());
+               $this->assertSame('Post3', $posts[2]->getTitle());
+       }
+
+       /**
+        * @test
+        */
+       public function orderingByTitleRespectsEnglishTitles() {
+               $query = $this->postRepository->createQuery();
+
+               $querySettings = $query->getQuerySettings();
+               $querySettings->setStoragePageIds(array(1));
+               $querySettings->setRespectSysLanguage(TRUE);
+               $querySettings->setLanguageUid(1);
+
+               $query->setOrderings(array('title' => QueryInterface::ORDER_ASCENDING));
+
+               /** @var Post[] $posts */
+               $posts = $query->execute()->toArray();
+
+               $this->assertCount(3, $posts);
+               $this->assertSame('A EN:Post2', $posts[0]->getTitle());
+               $this->assertSame('B EN:Post1', $posts[1]->getTitle());
+               $this->assertSame('Post3', $posts[2]->getTitle());
+       }
+}
index 53d022f..cb23da6 100644 (file)
@@ -115,7 +115,7 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                $querySettings->setLanguageUid(2);
                $mockTypo3DbQueryParser = $this->getAccessibleMock('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Storage\\Typo3DbQueryParser', array('dummy'), array(), '', FALSE);
                $mockTypo3DbQueryParser->_callRef('addSysLanguageStatement', $table, $sql, $querySettings);
-               $expectedSql = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (2,-1) OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (SELECT ' . $table . '.l10n_parent FROM ' . $table . ' WHERE ' . $table . '.l10n_parent>0 AND ' . $table . '.sys_language_uid>0)))'));
+               $expectedSql = array('additionalWhereClause' => array('(' . $table . '.sys_language_uid IN (2,-1) OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (SELECT ' . $table . '.l10n_parent FROM ' . $table . ' WHERE ' . $table . '.l10n_parent>0 AND ' . $table . '.sys_language_uid=2)))'));
                $this->assertSame($expectedSql, $sql);
        }
 
@@ -139,7 +139,7 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                                ' OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (' .
                                'SELECT ' . $table . '.l10n_parent FROM ' . $table .
                                ' WHERE ' . $table . '.l10n_parent>0 AND ' .
-                               $table . '.sys_language_uid>0 AND ' .
+                               $table . '.sys_language_uid=2 AND ' .
                                $table . '.deleted=0)))')
                );
                $this->assertSame($expectedSql, $sql);
@@ -166,7 +166,7 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
                                ' OR (' . $table . '.sys_language_uid=0 AND ' . $table . '.uid NOT IN (' .
                                'SELECT ' . $table . '.l10n_parent FROM ' . $table .
                                ' WHERE ' . $table . '.l10n_parent>0 AND ' .
-                               $table . '.sys_language_uid>0 AND ' .
+                               $table . '.sys_language_uid=2 AND ' .
                                $table . '.deleted=0)))')
                );
                $this->assertSame($expectedSql, $sql);