[BUGFIX] Make LocalizationRepository handle copied records 28/57628/15
authorMarkus Klein <markus.klein@typo3.org>
Tue, 28 Feb 2017 21:25:52 +0000 (22:25 +0100)
committerTymoteusz Motylewski <t.motylewski@gmail.com>
Thu, 2 Aug 2018 10:13:38 +0000 (12:13 +0200)
Improve LocalizationRepository queries to handle case
when records were copied from another page (thus t3_origuid)
is pointing to records from the other page.

Now LocalizationRepository uses l10n_source field instead of t3_origuid.
Tests for LocalizationRepository covering the case were added.

Resolves: #79443
Resolves: #78599
Releases: master, 7.6
Change-Id: Ibae4a276ea814f0ce3d453cffef1d22afeff1eb9
Reviewed-on: https://review.typo3.org/57628
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Johannes Kasberger <johannes.kasberger@reelworx.at>
Tested-by: Johannes Kasberger <johannes.kasberger@reelworx.at>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Łukasz Uznański <l.uznanski@macopedia.pl>
Tested-by: Łukasz Uznański <l.uznanski@macopedia.pl>
Reviewed-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
Tested-by: Tymoteusz Motylewski <t.motylewski@gmail.com>
typo3/sysext/backend/Classes/Domain/Repository/Localization/LocalizationRepository.php
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/Fixtures/DefaultPagesAndContent.csv [new file with mode: 0644]
typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/LocalizationRepositoryTest.php [new file with mode: 0644]

index d2f5ece..0b53a9e 100644 (file)
@@ -38,11 +38,16 @@ class LocalizationRepository
             . ' AND tt_content.pid = ' . (int)$pageId
             . ' AND tt_content.sys_language_uid = ' . (int)$localizedLanguage
             . ' AND tt_content.t3_origuid = tt_content_orig.uid'
-            . ' AND tt_content_orig.sys_language_uid=sys_language.uid'
+            // workaround, as t3_origuid might point to the source the translation was copied from, the language is identical then
+            . ' AND tt_content_orig.sys_language_uid <> tt_content.sys_language_uid'
+            . ' AND tt_content_orig.sys_language_uid = sys_language.uid'
             . $this->getExcludeQueryPart()
             . $this->getAllowedLanguagesForBackendUser(),
             'tt_content_orig.sys_language_uid'
         );
+        if (empty($record)) {
+            return ['sys_language_uid' => 0];
+        }
 
         return $record;
     }
@@ -55,6 +60,10 @@ class LocalizationRepository
      */
     public function getLocalizedRecordCount($pageId, $colPos, $languageId)
     {
+        // records in default language are never translated
+        if (!$languageId) {
+            return 0;
+        }
         $rows = (int)$this->getDatabaseConnection()->exec_SELECTcountRows(
             'uid',
             'tt_content',
@@ -142,11 +151,13 @@ class LocalizationRepository
 
         // Get original uid of existing elements triggered language / colpos
         $originalUids = $db->exec_SELECTgetRows(
-            't3_origuid',
-            'tt_content',
-            'sys_language_uid=' . (int)$destLanguageId
+            'tt_content.t3_origuid',
+            'tt_content,tt_content as orig',
+            'tt_content.sys_language_uid = ' . (int)$destLanguageId
             . ' AND tt_content.colPos = ' . (int)$colPos
-            . ' AND tt_content.pid=' . (int)$pageId
+            . ' AND tt_content.pid = ' . (int)$pageId
+            . ' AND orig.uid = tt_content.t3_origuid'
+            . ' AND orig.pid = ' . (int)$pageId
             . $this->getExcludeQueryPart(),
             '',
             '',
@@ -174,6 +185,7 @@ class LocalizationRepository
      * Fetches the localization for a given record.
      *
      * @FIXME: This method is a clone of BackendUtility::getRecordLocalization, using origUid instead of transOrigPointerField
+     * Note: This function is unused in the core and has been removed with version 9 - Forge ticket #80700
      *
      * @param string $table Table name present in $GLOBALS['TCA']
      * @param int $uid The uid of the record
@@ -215,6 +227,7 @@ class LocalizationRepository
      * @FIXME: This method is a clone of DataHandler::getPreviousLocalizedRecordUid which is protected there and uses
      * BackendUtility::getRecordLocalization which we also needed to clone in this class. Also, this method takes two
      * language arguments.
+     * Note: This function is unused in the core and has been removed with version 9 - Forge ticket #80700
      *
      * @param string $table Table name
      * @param int $uid Uid of default language record
index 4ca4745..6304791 100644 (file)
@@ -1830,7 +1830,7 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
 
         if (isset($this->contentElementCache[$lP][$colPos]) && is_array($this->contentElementCache[$lP][$colPos])) {
             foreach ($this->contentElementCache[$lP][$colPos] as $record) {
-                $key = array_search($record['t3_origuid'], $defLanguageCount);
+                $key = array_search($record['l18n_parent'] ?: $record['t3_origuid'], $defLanguageCount);
                 if ($key !== false) {
                     unset($defLanguageCount[$key]);
                 }
diff --git a/typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/Fixtures/DefaultPagesAndContent.csv b/typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/Fixtures/DefaultPagesAndContent.csv
new file mode 100644 (file)
index 0000000..d8e0264
--- /dev/null
@@ -0,0 +1,23 @@
+pages,,,,,,,,,
+,uid,pid,sorting,deleted,t3_origuid,title,,,
+,1,0,256,0,0,Connected mode,,,
+,2,0,512,0,0,Free mode,,,
+,3,0,768,0,0,Free mode CE copied from pid 2,,,
+sys_language,,,,,,,,,
+,uid,pid,hidden,title,flag,,,,
+,1,0,0,Dansk,dk,,,,
+,2,0,0,Deutsch,de,,,,
+tt_content,,,,,,,,,
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,t3_origuid,header
+,297,1,256,0,0,0,0,Regular Element #1
+,298,1,512,0,0,0,0,Regular Element #2
+,299,1,768,0,0,0,0,Regular Element #3
+,300,1,1024,0,1,299,299,[Translate to Dansk:] Regular Element #3
+,301,1,384,0,1,297,297,[Translate to Dansk:] Regular Element #1
+,302,1,448,0,2,297,301,[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1
+,310,2,256,0,0,0,0,Regular Element #10
+,311,2,512,0,1,0,310,[Translate to Dansk:] Regular Element #10
+,312,2,768,0,2,0,311,[Translate to Deutsch:] [Translate to Dansk:] Regular Element #10
+,313,3,128,0,0,0,310,Regular Element #10
+,314,3,256,0,1,0,311,[Translate to Dansk:] Regular Element #10
+,315,3,512,0,2,0,312,[Translate to Deutsch:] [Translate to Dansk:] Regular Element #10
diff --git a/typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/LocalizationRepositoryTest.php b/typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/LocalizationRepositoryTest.php
new file mode 100644 (file)
index 0000000..c001525
--- /dev/null
@@ -0,0 +1,264 @@
+<?php
+namespace TYPO3\CMS\Backend\Tests\Functional\Domain\Repository\Localization;
+
+/*
+ * 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 TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Tests\FunctionalTestCase;
+
+/**
+ * Test case for TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository
+ */
+class LocalizationRepositoryTest extends FunctionalTestCase
+{
+    /**
+     * @var LocalizationRepository
+     */
+    protected $subject;
+
+    /**
+     * Sets up this test case.
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->setUpBackendUserFromFixture(1);
+        Bootstrap::getInstance()->initializeLanguageObject();
+
+        $this->importCSVDataSet(ORIGINAL_ROOT . 'typo3/sysext/backend/Tests/Functional/Domain/Repository/Localization/Fixtures/DefaultPagesAndContent.csv');
+
+        $this->subject = new LocalizationRepository();
+    }
+
+    public function fetchOriginLanguageDataProvider()
+    {
+        return [
+            'default language returns 0' => [
+                1,
+                0,
+                0,
+                [
+                    'sys_language_uid' => 0
+                ]
+            ],
+            'connected mode translated from default language' => [
+                1,
+                0,
+                1,
+                [
+                    'sys_language_uid' => 0
+                ]
+            ],
+            'connected mode translated from non default language' => [
+                1,
+                0,
+                2,
+                [
+                    'sys_language_uid' => 1
+                ]
+            ],
+            'free mode translated from default language' => [
+                2,
+                0,
+                1,
+                [
+                    'sys_language_uid' => 0
+                ]
+            ],
+            'free mode translated from non default language' => [
+                2,
+                0,
+                2,
+                [
+                    'sys_language_uid' => 1
+                ]
+            ],
+            'free mode copied from another page translated from default language' => [
+                3,
+                0,
+                1,
+                [
+                    'sys_language_uid' => 0
+                ]
+            ],
+            'free mode copied from another page translated from non default language' => [
+                3,
+                0,
+                2,
+                [
+                    'sys_language_uid' => 0
+                ]
+            ]
+        ];
+    }
+
+    /**
+     * @dataProvider fetchOriginLanguageDataProvider
+     * @test
+     *
+     * @param $pageId
+     * @param $colPos
+     * @param $localizedLanguage
+     * @param $expectedResult
+     */
+    public function fetchOriginLanguage($pageId, $colPos, $localizedLanguage, $expectedResult)
+    {
+        $result = $this->subject->fetchOriginLanguage($pageId, $colPos, $localizedLanguage);
+        $this->assertEquals($expectedResult, $result);
+    }
+
+    public function getLocalizedRecordCountDataProvider()
+    {
+        return [
+            'default language returns 0 always' => [
+                1,
+                0,
+                0,
+                0
+            ],
+            'connected mode translated from default language' => [
+                1,
+                0,
+                1,
+                2
+            ],
+            'connected mode translated from non default language' => [
+                1,
+                0,
+                2,
+                1
+            ],
+            'free mode translated from default language' => [
+                2,
+                0,
+                1,
+                1
+            ],
+            'free mode translated from non default language' => [
+                2,
+                0,
+                2,
+                1
+            ],
+            'free mode copied from another page translated from default language' => [
+                3,
+                0,
+                1,
+                1
+            ],
+            'free mode copied from another page translated from non default language' => [
+                3,
+                0,
+                2,
+                1
+            ]
+        ];
+    }
+
+    /**
+     * @dataProvider getLocalizedRecordCountDataProvider
+     * @test
+     * @param int $pageId
+     * @param int $colPos
+     * @param int $localizedLanguage
+     * @param int $expectedResult
+     */
+    public function getLocalizedRecordCount($pageId, $colPos, $localizedLanguage, $expectedResult)
+    {
+        $result = $this->subject->getLocalizedRecordCount($pageId, $colPos, $localizedLanguage);
+        $this->assertEquals($expectedResult, $result);
+    }
+
+    public function getRecordsToCopyDatabaseResultDataProvider()
+    {
+        return [
+            'from language 0 to 1 connected mode' => [
+                1,
+                0,
+                1,
+                0,
+                [
+                    ['uid' => 298]
+                ]
+            ],
+            'from language 1 to 2 connected mode' => [
+                1,
+                0,
+                2,
+                1,
+                [
+                    ['uid' => 300]
+                ]
+            ],
+            'from language 0 to 1 free mode' => [
+                2,
+                0,
+                1,
+                0,
+                []
+            ],
+            'from language 1 to 2 free mode' => [
+                2,
+                0,
+                2,
+                1,
+                []
+            ],
+            'from language 0 to 1 free mode copied' => [
+                3,
+                0,
+                1,
+                0,
+                [
+                    // this is wrong in terms of usability as we have a translation
+                    // already for this record, but in v7.6 there is no way to find
+                    // this out, since the record has been copied from page 2
+                    // and therefore t3_origuid has been overridden by the copy action
+                    ['uid' => 313]
+                ]
+            ],
+            'from language 1 to 2 free mode  mode copied' => [
+                3,
+                0,
+                2,
+                1,
+                [
+                    // this is wrong in terms of usability as we have a translation
+                    // already for this record (from language 1), but in v7.6 there is no way to find
+                    // this out, since the record has been copied from page 2
+                    // and therefore t3_origuid has been overridden by the copy action
+                    ['uid' => 314]
+                ]
+            ],
+        ];
+    }
+
+    /**
+     * @dataProvider getRecordsToCopyDatabaseResultDataProvider
+     * @test
+     * @param int $pageId
+     * @param int $colPos
+     * @param int $destLanguageId
+     * @param int $languageId
+     * @param array $expectedResult
+     */
+    public function getRecordsToCopyDatabaseResult($pageId, $colPos, $destLanguageId, $languageId, $expectedResult)
+    {
+        $result = $this->subject->getRecordsToCopyDatabaseResult($pageId, $colPos, $destLanguageId, $languageId, 'uid');
+        $result = $result->fetch_all(MYSQLI_ASSOC);
+        $this->assertEquals($expectedResult, $result);
+    }
+}