[TASK] Doctrine: migrate ext:backend/Classes/Domain/Repository/Localization 83/47983/7
authorRichard Haeser <richardhaeser@gmail.com>
Sun, 1 May 2016 12:26:37 +0000 (14:26 +0200)
committerFrank Naegler <frank.naegler@typo3.org>
Tue, 7 Jun 2016 08:22:47 +0000 (10:22 +0200)
Change-Id: I0721312a89cbd034542bb3cbfe9ef1ea014f944b
Resolves: #75760
Resolves: #75759
Releases: master
Reviewed-on: https://review.typo3.org/47983
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Richard Haeser <richardhaeser@gmail.com>
Tested-by: Richard Haeser <richardhaeser@gmail.com>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
typo3/sysext/backend/Classes/Controller/Page/LocalizationController.php
typo3/sysext/backend/Classes/Domain/Repository/Localization/LocalizationRepository.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-75760-ReturnTypeOfGetRecordsToCopyDatabaseResultChanged.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Deprecation-75760-DeprecateMethodsOfLocalizationRepository.rst [new file with mode: 0644]

index 551e2aa..25c4ff7 100644 (file)
@@ -128,16 +128,21 @@ class LocalizationController
         }
 
         $records = [];
-        $databaseConnection = $this->getDatabaseConnection();
-        $res = $this->localizationRepository->getRecordsToCopyDatabaseResult($params['pageId'], $params['colPos'], $params['destLanguageId'], $params['languageId'], '*');
-        while ($row = $databaseConnection->sql_fetch_assoc($res)) {
+        $result = $this->localizationRepository->getRecordsToCopyDatabaseResult(
+            $params['pageId'],
+            $params['colPos'],
+            $params['destLanguageId'],
+            $params['languageId'],
+            '*'
+        );
+
+        while ($row = $result->fetch()) {
             $records[] = [
                 'icon' => $this->iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render(),
                 'title' => $row[$GLOBALS['TCA']['tt_content']['ctrl']['label']],
                 'uid' => $row['uid']
             ];
         }
-        $databaseConnection->sql_free_result($res);
 
         $response->getBody()->write(json_encode($records));
         return $response;
@@ -159,14 +164,12 @@ class LocalizationController
         $pageId = (int)$params['pageId'];
         $colPos = (int)$params['colPos'];
         $languageId = (int)$params['languageId'];
-        $databaseConnection = $this->getDatabaseConnection();
 
-        $res = $this->localizationRepository->getRecordsToCopyDatabaseResult($pageId, $colPos, $languageId, 'uid');
+        $result = $this->localizationRepository->getRecordsToCopyDatabaseResult($pageId, $colPos, $languageId, 'uid');
         $uids = [];
-        while ($row = $databaseConnection->sql_fetch_assoc($res)) {
+        while ($row = $result->fetch()) {
             $uids[] = (int)$row['uid'];
         }
-        $databaseConnection->sql_free_result($res);
 
         $response->getBody()->write(json_encode($uids));
         return $response;
@@ -265,14 +268,4 @@ class LocalizationController
         $dataHandler->start([], $cmd);
         $dataHandler->process_cmdmap();
     }
-
-    /**
-     * Returns the database connection
-     *
-     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
-     */
-    protected function getDatabaseConnection()
-    {
-        return $GLOBALS['TYPO3_DB'];
-    }
 }
index d1c7e03..8c74a08 100644 (file)
@@ -15,6 +15,11 @@ namespace TYPO3\CMS\Backend\Domain\Repository\Localization;
  */
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Repository for record localizations
@@ -27,24 +32,43 @@ class LocalizationRepository
      * @param int $pageId
      * @param int $colPos
      * @param int $localizedLanguage
-     * @return array|false|null
+     * @return array|false
      */
     public function fetchOriginLanguage($pageId, $colPos, $localizedLanguage)
     {
-        $record = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
-            'tt_content_orig.sys_language_uid',
-            'tt_content,tt_content AS tt_content_orig,sys_language',
-            'tt_content.colPos = ' . (int)$colPos
-            . ' 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'
-            . $this->getExcludeQueryPart()
-            . $this->getAllowedLanguagesForBackendUser(),
-            'tt_content_orig.sys_language_uid'
-        );
-
-        return $record;
+        $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
+
+        $constraints = [
+            $queryBuilder->expr()->eq('tt_content.colPos', (int)$colPos),
+            $queryBuilder->expr()->eq('tt_content.pid', (int)$pageId),
+            $queryBuilder->expr()->eq('tt_content.sys_language_uid', (int)$localizedLanguage),
+        ];
+        $constraints += $this->getAllowedLanguageConstraintsForBackendUser();
+
+        $queryBuilder->select('tt_content_orig.sys_language_uid')
+            ->from('tt_content')
+            ->join(
+                'tt_content',
+                'tt_content',
+                'tt_content_orig',
+                $queryBuilder->expr()->eq(
+                    'tt_content.t3_origuid',
+                    $queryBuilder->quoteIdentifier('tt_content_orig.uid')
+                )
+            )
+            ->join(
+                'tt_content_orig',
+                'sys_language',
+                'sys_language',
+                $queryBuilder->expr()->eq(
+                    'tt_content_orig.sys_language_uid',
+                    $queryBuilder->quoteIdentifier('sys_language.uid')
+                )
+            )
+            ->where(...$constraints)
+            ->groupBy('tt_content_orig.sys_language_uid');
+
+        return $queryBuilder->execute()->fetch();
     }
 
     /**
@@ -55,17 +79,20 @@ class LocalizationRepository
      */
     public function getLocalizedRecordCount($pageId, $colPos, $languageId)
     {
-        $rows = (int)$this->getDatabaseConnection()->exec_SELECTcountRows(
-            'uid',
-            'tt_content',
-            'tt_content.sys_language_uid=' . (int)$languageId
-            . ' AND tt_content.colPos = ' . (int)$colPos
-            . ' AND tt_content.pid=' . (int)$pageId
-            . ' AND tt_content.t3_origuid <> 0'
-            . $this->getExcludeQueryPart()
-        );
-
-        return $rows;
+        $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
+
+        $rowCount = $queryBuilder->count('uid')
+            ->from('tt_content')
+            ->where(
+                $queryBuilder->expr()->eq('tt_content.sys_language_uid', (int)$languageId),
+                $queryBuilder->expr()->eq('tt_content.colPos', (int)$colPos),
+                $queryBuilder->expr()->eq('tt_content.pid', (int)$pageId),
+                $queryBuilder->expr()->neq('tt_content.t3_origuid', 0)
+            )
+            ->execute()
+            ->fetchColumn(0);
+
+        return (int)$rowCount;
     }
 
     /**
@@ -78,18 +105,27 @@ class LocalizationRepository
      */
     public function fetchAvailableLanguages($pageId, $colPos, $languageId)
     {
-        $result = $this->getDatabaseConnection()->exec_SELECTgetRows(
-            'sys_language.uid',
-            'tt_content,sys_language',
-            'tt_content.sys_language_uid=sys_language.uid'
-            . ' AND tt_content.colPos = ' . (int)$colPos
-            . ' AND tt_content.pid = ' . (int)$pageId
-            . ' AND sys_language.uid <> ' . (int)$languageId
-            . $this->getExcludeQueryPart()
-            . $this->getAllowedLanguagesForBackendUser(),
-            'sys_language.uid',
-            'sys_language.title'
-        );
+        $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
+
+        $constraints = [
+            $queryBuilder->expr()->eq(
+                'tt_content.sys_language_uid',
+                $queryBuilder->quoteIdentifier('sys_language.uid')
+            ),
+            $queryBuilder->expr()->eq('tt_content.colPos', (int)$colPos),
+            $queryBuilder->expr()->eq('tt_content.pid', (int)$pageId),
+            $queryBuilder->expr()->neq('sys_language.uid', (int)$languageId)
+        ];
+        $constraints += $this->getAllowedLanguageConstraintsForBackendUser();
+
+        $queryBuilder->select('sys_language.uid')
+            ->from('tt_content')
+            ->from('sys_language')
+            ->where(...$constraints)
+            ->groupBy('sys_language.uid')
+            ->orderBy('sys_language.title');
+
+        $result = $queryBuilder->execute()->fetchAll();
 
         return $result;
     }
@@ -98,11 +134,13 @@ class LocalizationRepository
      * Builds an additional where clause to exclude deleted records and setting the versioning placeholders
      *
      * @return string
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
      */
     public function getExcludeQueryPart()
     {
-        return BackendUtility::deleteClause('tt_content')
-            . BackendUtility::versioningPlaceholderClause('tt_content');
+        GeneralUtility::logDeprecatedFunction();
+
+        return BackendUtility::deleteClause('tt_content') . BackendUtility::versioningPlaceholderClause('tt_content');
     }
 
     /**
@@ -110,9 +148,12 @@ class LocalizationRepository
      * if the user is not an admin.
      *
      * @return string
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
      */
     public function getAllowedLanguagesForBackendUser()
     {
+        GeneralUtility::logDeprecatedFunction();
+
         $backendUser = $this->getBackendUser();
         $additionalWhere = '';
         if (!$backendUser->isAdmin()) {
@@ -127,6 +168,37 @@ class LocalizationRepository
     }
 
     /**
+     * Builds additional query constraints to exclude hidden languages and
+     * limit a backend user to its allowed languages (unless the user is an admin)
+     *
+     * @return array
+     */
+    protected function getAllowedLanguageConstraintsForBackendUser(): array
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
+        $constraints = [];
+
+        $backendUser = $this->getBackendUser();
+        if (!$backendUser->isAdmin()) {
+            if (!empty($GLOBALS['TCA']['sys_language']['ctrl']['enablecolumns']['disabled'])) {
+                $constraints[] = $queryBuilder->expr()->eq(
+                    'sys_language.' . $GLOBALS['TCA']['sys_language']['ctrl']['enablecolumns']['disabled'],
+                    0
+                );
+            }
+
+            if (!empty($backendUser->user['allowed_languages'])) {
+                $constraints[] = $queryBuilder->expr()->in(
+                    'sys_language.uid',
+                    GeneralUtility::intExplode(',', $backendUser->user['allowed_languages'])
+                );
+            }
+        }
+
+        return $constraints;
+    }
+
+    /**
      * Get records for copy process
      *
      * @param int $pageId
@@ -134,40 +206,43 @@ class LocalizationRepository
      * @param int $destLanguageId
      * @param int $languageId
      * @param string $fields
-     * @return bool|\mysqli_result|object
+     * @return \Doctrine\DBAL\Driver\Statement
      */
     public function getRecordsToCopyDatabaseResult($pageId, $colPos, $destLanguageId, $languageId, $fields = '*')
     {
-        $db = $this->getDatabaseConnection();
+        $originalUids = [];
 
         // Get original uid of existing elements triggered language / colpos
-        $originalUids = $db->exec_SELECTgetRows(
-            't3_origuid',
-            'tt_content',
-            'sys_language_uid=' . (int)$destLanguageId
-            . ' AND tt_content.colPos = ' . (int)$colPos
-            . ' AND tt_content.pid=' . (int)$pageId
-            . $this->getExcludeQueryPart(),
-            '',
-            '',
-            '',
-            't3_origuid'
-        );
-        $originalUidList = $db->cleanIntList(implode(',', array_keys($originalUids)));
-
-        $res = $db->exec_SELECTquery(
-            $fields,
-            'tt_content',
-            'tt_content.sys_language_uid=' . (int)$languageId
-            . ' AND tt_content.colPos = ' . (int)$colPos
-            . ' AND tt_content.pid=' . (int)$pageId
-            . ' AND tt_content.uid NOT IN (' . $originalUidList . ')'
-            . $this->getExcludeQueryPart(),
-            '',
-            'tt_content.sorting'
-        );
-
-        return $res;
+        $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
+
+        $originalUidsStatement = $queryBuilder
+            ->select('t3_origuid')
+            ->from('tt_content')
+            ->where(
+                $queryBuilder->expr()->eq('sys_language_uid', (int)$destLanguageId),
+                $queryBuilder->expr()->eq('tt_content.colPos', (int)$colPos),
+                $queryBuilder->expr()->eq('tt_content.pid', (int)$pageId)
+            )
+            ->execute();
+
+        while ($origUid = $originalUidsStatement->fetchColumn(0)) {
+            $originalUids[] = (int)$origUid;
+        }
+
+        $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields, true))
+            ->from('tt_content')
+            ->where(
+                $queryBuilder->expr()->eq('tt_content.sys_language_uid', (int)$languageId),
+                $queryBuilder->expr()->eq('tt_content.colPos', (int)$colPos),
+                $queryBuilder->expr()->eq('tt_content.pid', (int)$pageId)
+            )
+            ->orderBy('tt_content.sorting');
+
+        if (!empty($originalUids)) {
+            $queryBuilder->andWhere($queryBuilder->expr()->notIn('tt_content.uid', $originalUids));
+        }
+
+        return $queryBuilder->execute();
     }
 
     /**
@@ -228,38 +303,45 @@ class LocalizationRepository
         $previousLocalizedRecordUid = $uid;
         if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
             $sortRow = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
-            $select = $sortRow . ',pid,uid';
+            $select = [$sortRow, 'pid', 'uid'];
             // For content elements, we also need the colPos
             if ($table === 'tt_content') {
-                $select .= ',colPos';
+                $select[] = 'colPos';
             }
             // Get the sort value of the default language record
-            $row = BackendUtility::getRecord($table, $uid, $select);
+            $row = BackendUtility::getRecord($table, $uid, implode(',', $select));
             if (is_array($row)) {
-                // Find the previous record in default language on the same page
-                $where = 'pid=' . (int)$pid . ' AND ' . 'sys_language_uid=' . (int)$sourceLanguage . ' AND ' . $sortRow . '<' . (int)$row[$sortRow];
+                $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
+
+                $queryBuilder->select(...$select)
+                    ->from($table)
+                    ->where(
+                        $queryBuilder->expr()->eq('pid', (int)$pid),
+                        $queryBuilder->expr()->eq('sys_language_uid', (int)$sourceLanguage),
+                        $queryBuilder->expr()->lt($sortRow, (int)$row[$sortRow])
+                    );
+
                 // Respect the colPos for content elements
                 if ($table === 'tt_content') {
-                    $where .= ' AND colPos=' . (int)$row['colPos'];
+                    $queryBuilder->andWhere($queryBuilder->expr()->eq('colPos', (int)$row['colPos']));
                 }
-                $res = $this->getDatabaseConnection()->exec_SELECTquery(
-                    $select,
-                    $table,
-                    $where . BackendUtility::deleteClause($table),
-                    '',
-                    $sortRow . ' DESC',
-                    '1'
-                );
+
+                $previousRow = $queryBuilder->orderBy($sortRow, 'DESC')->execute()->fetch();
+
                 // If there is an element, find its localized record in specified localization language
-                if ($previousRow = $this->getDatabaseConnection()->sql_fetch_assoc($res)) {
-                    $previousLocalizedRecord = $this->getRecordLocalization($table, $previousRow['uid'], $destinationLanguage);
+                if ($previousRow !== false) {
+                    $previousLocalizedRecord = $this->getRecordLocalization(
+                        $table,
+                        $previousRow['uid'],
+                        $destinationLanguage
+                    );
                     if (is_array($previousLocalizedRecord[0])) {
                         $previousLocalizedRecordUid = $previousLocalizedRecord[0]['uid'];
                     }
                 }
-                $this->getDatabaseConnection()->sql_free_result($res);
             }
         }
+
         return $previousLocalizedRecordUid;
     }
 
@@ -274,12 +356,20 @@ class LocalizationRepository
     }
 
     /**
-     * Returns the database connection
+     * Get a QueryBuilder for the given table with preconfigured restrictions
+     * to not retrieve workspace placeholders or deleted records.
      *
-     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+     * @param string $tableName
+     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
      */
-    protected function getDatabaseConnection()
+    protected function getQueryBuilderWithWorkspaceRestriction(string $tableName): QueryBuilder
     {
-        return $GLOBALS['TYPO3_DB'];
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        return $queryBuilder;
     }
 }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-75760-ReturnTypeOfGetRecordsToCopyDatabaseResultChanged.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-75760-ReturnTypeOfGetRecordsToCopyDatabaseResultChanged.rst
new file mode 100644 (file)
index 0000000..0760401
--- /dev/null
@@ -0,0 +1,35 @@
+================================================================================================
+Breaking: #75760 - Return type of LocalizationRepository::getRecordsToCopyDatabaseResult changed
+================================================================================================
+
+Description
+===========
+
+The return type of the :php:``LocalizationRepository::getRecordsToCopyDatabaseResult``
+has been changed. Instead of returning either :php:``bool``, :php:``\mysqli_result``
+or :php:``object`` the return value always is a :php:``\Doctrine\DBAL\Driver\Statement``.
+
+
+Impact
+======
+
+Using the mentioned method will not yield the expected result type.
+
+
+Affected Installations
+======================
+
+Any installation with a 3rd party extension that uses the named method.
+
+
+Migration
+=========
+
+Change the way the result is being used to conform to the Doctrine API:
+
+.. code-block:: php
+
+    $result = $this->localizationRepository->getRecordsToCopyDatabaseResult(...);
+    while ($row = $result->fetch()) {
+        // Do something here
+    }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-75760-DeprecateMethodsOfLocalizationRepository.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-75760-DeprecateMethodsOfLocalizationRepository.rst
new file mode 100644 (file)
index 0000000..f838fd8
--- /dev/null
@@ -0,0 +1,39 @@
+=================================================================
+Deprecation: #75760 - Deprecate methods of LocalizationRepository
+=================================================================
+
+Description
+===========
+
+The following methods have been marked as deprecated:
+
+- :php:``LocalizationRepository::getExcludeQueryPart()``
+- :php:``LocalizationRepository::getAllowedLanguagesForBackendUser()``
+
+
+Impact
+======
+
+Using the mentioned methods will trigger a deprecation log entry
+
+
+Affected Installations
+======================
+
+Any installation with a 3rd party extension that uses one of the named methods.
+
+
+Migration
+=========
+
+Instead of :php:``LocalizationRepository::getExcludeQueryPart()`` configure the query restrictions yourself:
+
+.. code-block:: php
+
+    $queryBuilder->getRestrictions()
+        ->removeAll()
+        ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+        ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+Instead of :php:``LocalizationRepository::getAllowedLanguagesForBackendUser()`` add
+the required conditions to your query yourself.