[TASK] Deprecate BackendUtility::getRecordsByField 78/51078/9
authorMorton Jonuschat <m.jonuschat@mojocode.de>
Fri, 30 Dec 2016 22:33:58 +0000 (14:33 -0800)
committerChristian Kuhn <lolli@schwarzbu.ch>
Fri, 24 Feb 2017 08:26:58 +0000 (09:26 +0100)
Deprecate BackendUtility::getRecordsByField() as it has a flawed design
due to passing SQL fragments. This contradicts the goal of using named
parameters for all queries in the core and requires passing the original
QueryBuilder object in addition to the stringified constraint.

Replace all calls to the method with direct usage of the QueryBuilder and
deprecate the method.

Resolves: #79122
Releases: master
Change-Id: I8b040b98e20271aff84ef16fb89b59a406d54003
Reviewed-on: https://review.typo3.org/51078
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Mona Muzaffar <mona.muzaffar@gmx.de>
Tested-by: Mona Muzaffar <mona.muzaffar@gmx.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
14 files changed:
typo3/sysext/backend/Classes/Controller/BackendController.php
typo3/sysext/backend/Classes/Domain/Repository/Localization/LocalizationRepository.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/backend/Tests/Functional/Utility/BackendUtilityTest.php [new file with mode: 0644]
typo3/sysext/backend/Tests/Functional/Utility/Fixtures/sys_domain.xml [new file with mode: 0644]
typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
typo3/sysext/core/Classes/DataHandling/DataHandler.php
typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
typo3/sysext/core/Classes/Database/SoftReferenceIndex.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-79122-DeprecateBackendUtilitygetRecordsByField.rst [new file with mode: 0644]
typo3/sysext/recordlist/Classes/LinkHandler/PageLinkHandler.php
typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
typo3/sysext/workspaces/Classes/Service/WorkspaceService.php

index 40d76e2..bff9371 100644 (file)
@@ -20,8 +20,12 @@ use TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository;
 use TYPO3\CMS\Backend\Module\ModuleLoader;
 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Type\File\ImageInfo;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -720,16 +724,35 @@ class BackendController
         $beUser = $this->getBackendUser();
         // EDIT page:
         $editId = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('edit'));
-        $editRecord = '';
         if ($editId) {
             // Looking up the page to edit, checking permissions:
             $where = ' AND (' . $beUser->getPagePermsClause(2) . ' OR ' . $beUser->getPagePermsClause(16) . ')';
             if (MathUtility::canBeInterpretedAsInteger($editId)) {
                 $editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
             } else {
-                $records = BackendUtility::getRecordsByField('pages', 'alias', $editId, $where);
-                if (is_array($records)) {
-                    $editRecord = reset($records);
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+                $editRecord = $queryBuilder->select('*')
+                    ->from('pages')
+                    ->where(
+                        $queryBuilder->expr()->eq(
+                            'alias',
+                            $queryBuilder->createNamedParameter($editId, \PDO::PARAM_STR)
+                        ),
+                        $queryBuilder->expr()->orX(
+                            $beUser->getPagePermsClause(Permission::PAGE_EDIT),
+                            $beUser->getPagePermsClause(Permission::CONTENT_EDIT)
+                        )
+                    )
+                    ->setMaxResults(1)
+                    ->execute()
+                    ->fetch();
+
+                if ($editRecord !== false) {
                     BackendUtility::workspaceOL('pages', $editRecord);
                 }
             }
index 10b16e7..dba66f8 100644 (file)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -330,15 +331,32 @@ class LocalizationRepository
             $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
 
             if (isset($tcaCtrl['origUid'])) {
-                $recordLocalization = BackendUtility::getRecordsByField(
-                    $table,
-                    $tcaCtrl['origUid'],
-                    $uid,
-                    'AND ' . $tcaCtrl['languageField'] . '=' . (int)$language . ($andWhereClause ? ' ' . $andWhereClause : ''),
-                    '',
-                    '',
-                    '1'
-                );
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($table);
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+                $queryBuilder->select('*')
+                    ->from($table)
+                    ->where(
+                        $queryBuilder->expr()->eq(
+                            $tcaCtrl['origUid'],
+                            $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                        ),
+                        $queryBuilder->expr()->eq(
+                            $tcaCtrl['languageField'],
+                            $queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
+                        )
+                    )
+                    ->setMaxResults(1);
+
+                if ($andWhereClause) {
+                    $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
+                }
+
+                $recordLocalization = $queryBuilder->execute()->fetchAll();
             }
         }
         return $recordLocalization;
index 3d30e68..7308eda 100644 (file)
@@ -221,6 +221,7 @@ class BackendUtility
      * @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
      * @param null|QueryBuilder $queryBuilder The queryBuilder must be provided, if the parameter $whereClause is given and the concept of prepared statement was used. Example within self::firstDomainRecord()
      * @return mixed Multidimensional array with selected records (if any is selected)
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
      */
     public static function getRecordsByField(
         $theTable,
@@ -233,6 +234,7 @@ class BackendUtility
         $useDeleteClause = true,
         $queryBuilder = null
     ) {
+        GeneralUtility::logDeprecatedFunction();
         if (is_array($GLOBALS['TCA'][$theTable])) {
             if (null === $queryBuilder) {
                 $queryBuilder = static::getQueryBuilderForTable($theTable);
@@ -394,27 +396,32 @@ class BackendUtility
 
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                 ->getQueryBuilderForTable($table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
 
-            $constraint = $queryBuilder->expr()->andX(
-                $queryBuilder->expr()->eq(
-                    $tcaCtrl['languageField'],
-                    $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
-                ),
-                QueryHelper::stripLogicalOperatorPrefix($andWhereClause)
-            );
+            $queryBuilder->select('*')
+                ->from($table)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        $tcaCtrl['transOrigPointerField'],
+                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->eq(
+                        $tcaCtrl['languageField'],
+                        $queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
+                    )
+                )
+                ->setMaxResults(1);
 
-            $recordLocalization = self::getRecordsByField(
-                $table,
-                $tcaCtrl['transOrigPointerField'],
-                $uid,
-                (string)$constraint,
-                '',
-                '',
-                1,
-                true,
-                $queryBuilder
-            );
+            if ($andWhereClause) {
+                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
+            }
+
+            $recordLocalization = $queryBuilder->execute()->fetchAll();
         }
+
         return $recordLocalization;
     }
 
@@ -3970,31 +3977,37 @@ class BackendUtility
     public static function firstDomainRecord($rootLine)
     {
         $queryBuilder = static::getQueryBuilderForTable('sys_domain');
-        $constraint = $queryBuilder->expr()->andX(
-            $queryBuilder->expr()->eq(
-                'redirectTo',
-                $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
-            ),
-            $queryBuilder->expr()->eq(
-                'hidden',
-                $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $queryBuilder->select('domainName')
+            ->from('sys_domain')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    'pid',
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT, ':pid')
+                ),
+                $queryBuilder->expr()->eq(
+                    'redirectTo',
+                    $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
+                ),
+                $queryBuilder->expr()->eq(
+                    'hidden',
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                )
             )
-        );
+            ->setMaxResults(1)
+            ->orderBy('sorting');
+
         foreach ($rootLine as $row) {
-            $dRec = self::getRecordsByField(
-                'sys_domain',
-                'pid',
-                $row['uid'],
-                (string)$constraint,
-                '',
-                'sorting',
-                '',
-                true,
-                $queryBuilder
-            );
-            if (is_array($dRec)) {
-                $dRecord = reset($dRec);
-                return rtrim($dRecord['domainName'], '/');
+            $domainName = $queryBuilder->setParameter('pid', $row['uid'], \PDO::PARAM_INT)
+                ->execute()
+                ->fetchColumn(0);
+
+            if ($domainName) {
+                return rtrim($domainName, '/');
             }
         }
         return null;
index 09951be..19fd03f 100644 (file)
@@ -453,28 +453,32 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
             $userCanEditPage = $this->getBackendUser()->check('tables_modify', 'pages_language_overlay');
         }
         if ($userCanEditPage) {
-            $languageOverlayId = 0;
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                ->getConnectionForTable('pages_language_overlay')
-                ->createQueryBuilder();
-            $constraint = $queryBuilder->expr()->eq(
-                'sys_language_uid',
-                $queryBuilder->createNamedParameter($this->tt_contentConfig['sys_language_uid'], \PDO::PARAM_INT)
-            );
-            $pageOverlayRecord = BackendUtility::getRecordsByField(
-                'pages_language_overlay',
-                'pid',
-                (int)$this->id,
-                $constraint,
-                '',
-                '',
-                '',
-                true,
-                $queryBuilder
-            );
-            if (!empty($pageOverlayRecord[0]['uid'])) {
-                $languageOverlayId = $pageOverlayRecord[0]['uid'];
-            }
+                ->getQueryBuilderForTable('pages_language_overlay');
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+            $queryBuilder->select('uid')
+                ->from('pages_language_overlay')
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'pid',
+                        $queryBuilder->createNamedParameter((int)$this->id, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->eq(
+                        'sys_language_uid',
+                        $queryBuilder->createNamedParameter(
+                            $this->tt_contentConfig['sys_language_uid'],
+                            \PDO::PARAM_INT
+                        )
+                    )
+                )
+                ->setMaxResults(1);
+
+            $languageOverlayId = (int)$queryBuilder->execute()->fetchColumn(0);
+
             $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', 'function(PageActions) {
                 PageActions.setPageId(' . (int)$this->id . ');
                 PageActions.setLanguageOverlayId(' . $languageOverlayId . ');
@@ -853,7 +857,29 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
                 }
                 // Language overlay page header:
                 if ($lP) {
-                    list($lpRecord) = BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $id, 'AND sys_language_uid=' . $lP);
+                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                        ->getQueryBuilderForTable('pages_language_overlay');
+                    $queryBuilder->getRestrictions()
+                        ->removeAll()
+                        ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                        ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+                    $lpRecord = $queryBuilder->select('*')
+                        ->from('pages_language_overlay')
+                        ->where(
+                            $queryBuilder->expr()->eq(
+                                'pid',
+                                $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
+                            ),
+                            $queryBuilder->expr()->eq(
+                                'sys_language_uid',
+                                $queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
+                            )
+                        )
+                        ->setMaxResults(1)
+                        ->execute()
+                        ->fetch();
+
                     BackendUtility::workspaceOL('pages_language_overlay', $lpRecord);
                     $recordIcon = BackendUtility::wrapClickMenuOnIcon(
                         $this->iconFactory->getIconForRecord('pages_language_overlay', $lpRecord, Icon::SIZE_SMALL)->render(),
diff --git a/typo3/sysext/backend/Tests/Functional/Utility/BackendUtilityTest.php b/typo3/sysext/backend/Tests/Functional/Utility/BackendUtilityTest.php
new file mode 100644 (file)
index 0000000..6d0b903
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+namespace TYPO3\CMS\Backend\Tests\Functional\Controller\Page;
+
+/*
+ * 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\Utility\BackendUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\RootlineUtility;
+
+/**
+ * Test case for TYPO3\CMS\Backend\Controller\Page\LocalizationController
+ */
+class BackendUtilityTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
+{
+    /**
+     * Sets up this test case.
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->importDataSet(ORIGINAL_ROOT . 'components/testing_framework/Resources/Core/Functional/Fixtures/pages.xml');
+        $this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/backend/Tests/Functional/Utility/Fixtures/sys_domain.xml');
+    }
+
+    /**
+     * @test
+     */
+    public function determineFirstDomainRecord()
+    {
+        $rootLineUtility = GeneralUtility::makeInstance(RootlineUtility::class, 4);
+        $rootLine = $rootLineUtility->get();
+        $this->assertEquals('example.com', BackendUtility::firstDomainRecord($rootLine));
+    }
+}
diff --git a/typo3/sysext/backend/Tests/Functional/Utility/Fixtures/sys_domain.xml b/typo3/sysext/backend/Tests/Functional/Utility/Fixtures/sys_domain.xml
new file mode 100644 (file)
index 0000000..ba49fcf
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+    <sys_domain>
+        <uid>1</uid>
+        <pid>1</pid>
+        <tstamp>1487563944</tstamp>
+        <hidden>0</hidden>
+        <domainName>example.com</domainName>
+        <forced>1</forced>
+    </sys_domain>
+    <sys_domain>
+        <uid>2</uid>
+        <pid>7</pid>
+        <tstamp>1487563945</tstamp>
+        <hidden>0</hidden>
+        <domainName>www.example.net</domainName>
+        <redirectTo>http://example.com/</redirectTo>
+        <redirectHttpStatusCode>301</redirectHttpStatusCode>
+        <forced>0</forced>
+    </sys_domain>
+</dataset>
index b7c8887..b977620 100644 (file)
@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
@@ -701,15 +702,30 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                 $pointerField = $GLOBALS['TCA'][$l10nTable]['ctrl']['transOrigPointerField'];
                 $pointerValue = $record[$pointerField] > 0 ? $record[$pointerField] : $record['uid'];
             }
-            $recordLocalizations = BackendUtility::getRecordsByField($l10nTable, $pointerField, $pointerValue, '', '', '', '1');
-            if (is_array($recordLocalizations)) {
-                foreach ($recordLocalizations as $localization) {
-                    $recordLocalizationAccess = $recordLocalizationAccess
-                        && $this->checkLanguageAccess($localization[$GLOBALS['TCA'][$l10nTable]['ctrl']['languageField']]);
-                    if (!$recordLocalizationAccess) {
-                        break;
-                    }
-                }
+
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($l10nTable);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+            $recordLocalization = $queryBuilder->select('*')
+                ->from($l10nTable)
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        $pointerField,
+                        $queryBuilder->createNamedParameter($pointerValue, \PDO::PARAM_INT)
+                    )
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetch();
+
+            if (is_array($recordLocalization)) {
+                $languageAccess = $this->checkLanguageAccess(
+                    $recordLocalization[$GLOBALS['TCA'][$l10nTable]['ctrl']['languageField']]
+                );
+                $recordLocalizationAccess = $recordLocalizationAccess && $languageAccess;
             }
         }
         return $recordLocalizationAccess;
index 7e2f102..3ae0c18 100644 (file)
@@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
@@ -2630,12 +2631,44 @@ class DataHandler
     public function getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId = 0)
     {
         $result = [];
-        if (!empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
-            $uid = (int)$uid;
-            $pageId = (int)$pageId;
-            $whereStatement = ' AND uid <> ' . $uid . ' AND ' . ($pageId ? 'pid = ' . $pageId : 'pid >= 0');
-            $result = BackendUtility::getRecordsByField($tableName, $fieldName, $value, $whereStatement);
+        if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
+            return $result;
         }
+
+        $uid = (int)$uid;
+        $pageId = (int)$pageId;
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $queryBuilder->select('*')
+            ->from($tableName)
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $fieldName,
+                    $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)
+                ),
+                $queryBuilder->expr()->neq(
+                    'uid',
+                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                )
+            );
+
+        if ($pageId) {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT))
+            );
+        } else {
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
+            );
+        }
+
+        $result = $queryBuilder->execute()->fetchAll();
+
         return $result;
     }
 
@@ -4112,20 +4145,38 @@ class DataHandler
         if (!BackendUtility::isTableLocalizable($table) ||  $table === 'pages' || $table === 'pages_language_overlay') {
             return;
         }
-        $where = '';
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $queryBuilder->select('*')
+            ->from($table)
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
+                )
+            );
+
         if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
-            $where = ' AND t3ver_oid=0';
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
+            );
         }
         // If $destPid is < 0, get the pid of the record with uid equal to abs($destPid)
         $tscPID = BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
         // Get the localized records to be copied
-        $l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
+        $l10nRecords = $queryBuilder->execute()->fetchAll();
         if (is_array($l10nRecords)) {
             $localizedDestPids = [];
             // If $destPid < 0, then it is the uid of the original language record we are inserting after
             if ($destPid < 0) {
                 // Get the localized records of the record we are inserting after
-                $destL10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], abs($destPid), $where);
+                $queryBuilder->setParameter('pointer', abs($destPid), \PDO::PARAM_INT);
+                $destL10nRecords = $queryBuilder->execute()->fetchAll();
                 // Index the localized record uids by language
                 if (is_array($destL10nRecords)) {
                     foreach ($destL10nRecords as $record) {
@@ -4512,17 +4563,36 @@ class DataHandler
         if (!BackendUtility::isTableLocalizable($table) || $table === 'pages' || $table === 'pages_language_overlay') {
             return;
         }
-        $where = '';
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $queryBuilder->select('*')
+            ->from($table)
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
+                )
+            );
+
         if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
-            $where = ' AND t3ver_oid=0';
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
+            );
         }
-        $l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
+
+        $l10nRecords = $queryBuilder->execute()->fetchAll();
         if (is_array($l10nRecords)) {
             $localizedDestPids = [];
             // If $$originalRecordDestinationPid < 0, then it is the uid of the original language record we are inserting after
             if ($originalRecordDestinationPid < 0) {
                 // Get the localized records of the record we are inserting after
-                $destL10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], abs($originalRecordDestinationPid), $where);
+                $queryBuilder->setParameter('pointer', abs($originalRecordDestinationPid), \PDO::PARAM_INT);
+                $destL10nRecords = $queryBuilder->execute()->fetchAll();
                 // Index the localized record uids by language
                 if (is_array($destL10nRecords)) {
                     foreach ($destL10nRecords as $record) {
@@ -4620,10 +4690,32 @@ class DataHandler
         }
 
         if ($table === 'pages') {
-            $pass = !BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $uid, (' AND ' . $GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'] . '=' . (int)$langRec['uid']));
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable('pages_language_overlay');
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+            $recordCount = $queryBuilder->count('*')
+                ->from('pages_language_overlay')
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'pid',
+                        $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->eq(
+                        $GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'],
+                        $queryBuilder->createNamedParameter((int)$langRec['uid'], \PDO::PARAM_INT)
+                    )
+                )
+                ->execute()
+                ->fetchColumn(0);
+
+            $pass = !$recordCount;
             $Ttable = 'pages_language_overlay';
         } else {
-            $pass = !BackendUtility::getRecordLocalization($table, $uid, $langRec['uid'], ('AND pid=' . (int)$row['pid']));
+            $pass = !BackendUtility::getRecordLocalization($table, $uid, $langRec['uid'], 'AND pid=' . (int)$row['pid']);
             $Ttable = $table;
         }
 
@@ -5472,23 +5564,39 @@ class DataHandler
         if (!BackendUtility::isTableLocalizable($table) || $table === 'pages' || $table === 'pages_language_overlay') {
             return;
         }
-        $where = '';
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $queryBuilder->select('*')
+            ->from($table)
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                )
+            );
+
         if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
-            $where = ' AND t3ver_oid=0';
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
+            );
         }
-        $l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
-        if (is_array($l10nRecords)) {
-            foreach ($l10nRecords as $record) {
-                // Ignore workspace delete placeholders. Those records have been marked for
-                // deletion before - deleting them again in a workspace would revert that state.
-                if ($this->BE_USER->workspace > 0 && BackendUtility::isTableWorkspaceEnabled($table)) {
-                    BackendUtility::workspaceOL($table, $record);
-                    if (VersionState::cast($record['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
-                        continue;
-                    }
+
+        $result = $queryBuilder->execute();
+        while ($record = $result->fetch()) {
+            // Ignore workspace delete placeholders. Those records have been marked for
+            // deletion before - deleting them again in a workspace would revert that state.
+            if ($this->BE_USER->workspace > 0 && BackendUtility::isTableWorkspaceEnabled($table)) {
+                BackendUtility::workspaceOL($table, $record);
+                if (VersionState::cast($record['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
+                    continue;
                 }
-                $this->deleteAction($table, (int)$record['t3ver_oid'] > 0 ? (int)$record['t3ver_oid'] : (int)$record['uid']);
             }
+            $this->deleteAction($table, (int)$record['t3ver_oid'] > 0 ? (int)$record['t3ver_oid'] : (int)$record['uid']);
         }
     }
 
index 5720b42..4b2ab35 100644 (file)
@@ -209,11 +209,11 @@ class QueryBuilder
      *
      * @param string|int $key The parameter position or name.
      * @param mixed $value The parameter value.
-     * @param string|null $type One of the Connection::PARAM_* constants.
+     * @param int|null $type One of the Connection::PARAM_* constants.
      *
      * @return QueryBuilder This QueryBuilder instance.
      */
-    public function setParameter($key, $value, string $type = null): QueryBuilder
+    public function setParameter($key, $value, int $type = null): QueryBuilder
     {
         $this->concreteQueryBuilder->setParameter($key, $value, $type);
 
index b289128..98beccd 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Database;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Service\TypoLinkCodecService;
@@ -616,13 +618,27 @@ class SoftReferenceIndex
     /**
      * Look up and return page uid for alias
      *
-     * @param int $link_param Page alias string value
+     * @param string $link_param Page alias string value
      * @return int Page uid corresponding to alias value.
      */
     public function getPageIdFromAlias($link_param)
     {
-        $pRec = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField('pages', 'alias', $link_param);
-        return $pRec[0]['uid'];
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $pageUid = $queryBuilder->select('uid')
+            ->from('pages')
+            ->where(
+                $queryBuilder->expr()->eq('alias', $queryBuilder->createNamedParameter($link_param, \PDO::PARAM_STR))
+            )
+            ->setMaxResults(1)
+            ->execute()
+            ->fetchColumn(0);
+
+        return (int)$pageUid;
     }
 
     /**
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-79122-DeprecateBackendUtilitygetRecordsByField.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-79122-DeprecateBackendUtilitygetRecordsByField.rst
new file mode 100644 (file)
index 0000000..07098d0
--- /dev/null
@@ -0,0 +1,32 @@
+.. include:: ../../Includes.txt
+
+========================================================
+Deprecation: #79122 - Deprecate method getRecordsByField
+========================================================
+
+See :issue:`79122`
+
+Description
+===========
+
+The method :php:`TYPO3\CMS\Backend\Utility\BackendUtility::getRecordsByField()` has been deprecated and should not be used any longer.
+
+
+Impact
+======
+
+Calling the deprecated :php:`getRecordsByField()` method will trigger a deprecation log entry.
+
+
+Affected Installations
+======================
+
+Any installation using the mentioned method :php:`getRecordsByField()`.
+
+
+Migration
+=========
+
+Use the `ConnectionPool` and the `QueryBuilder` classes directly to query the database from your code.
+
+.. index:: Backend
index 48f2179..7f9231c 100644 (file)
@@ -62,11 +62,29 @@ class PageLinkHandler extends AbstractLinkHandler implements LinkHandlerInterfac
         $data = $linkParts['url'];
         // Checking if the id-parameter is an alias.
         if (isset($data['pagealias'])) {
-            $records = BackendUtility::getRecordsByField('pages', 'alias', $data['pagealias']);
-            if (empty($records)) {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable('pages');
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+            $pageUid = $queryBuilder->select('uid')
+                ->from('pages')
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'alias',
+                        $queryBuilder->createNamedParameter($data['pagealias'], \PDO::PARAM_STR)
+                    )
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetchColumn(0);
+
+            if ($pageUid === false) {
                 return false;
             }
-            $data['pageuid'] = (int)$records[0]['uid'];
+            $data['pageuid'] = (int)$pageUid;
         }
         // Check if the page still exists
         if ((int)$data['pageuid'] > 0) {
index 7ad5708..ed1c047 100644 (file)
@@ -16,6 +16,8 @@ namespace TYPO3\CMS\Recycler\Domain\Model;
 
 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\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -89,22 +91,22 @@ class DeletedRecords
         // set the limit
         $this->limit = trim($limit);
         if ($table) {
-            if (in_array($table, RecyclerUtility::getModifyableTables())) {
+            if (in_array($table, RecyclerUtility::getModifyableTables(), true)) {
                 $this->table[] = $table;
-                $this->setData($id, $table, $depth, $GLOBALS['TCA'][$table]['ctrl'], $filter);
+                $this->setData($id, $table, $depth, $filter);
             }
         } else {
-            foreach ($GLOBALS['TCA'] as $tableKey => $tableValue) {
+            foreach (array_keys($GLOBALS['TCA']) as $tableKey) {
                 // only go into this table if the limit allows it
                 if ($this->limit !== '') {
-                    $parts = GeneralUtility::trimExplode(',', $this->limit);
+                    $parts = GeneralUtility::intExplode(',', $this->limit, true);
                     // abort loop if LIMIT 0,0
-                    if ((int)$parts[0] === 0 && (int)$parts[1] === 0) {
+                    if ($parts[0] === 0 && $parts[1] === 0) {
                         break;
                     }
                 }
                 $this->table[] = $tableKey;
-                $this->setData($id, $tableKey, $depth, $tableValue['ctrl'], $filter);
+                $this->setData($id, $tableKey, $depth, $filter);
             }
         }
         return $this;
@@ -135,67 +137,41 @@ class DeletedRecords
      * @param int $id UID from record
      * @param string $table Tablename from record
      * @param int $depth How many levels recursive
-     * @param array $tcaCtrl TCA CTRL array
      * @param string $filter Filter text
      * @return void
      */
-    protected function setData($id, $table, $depth, $tcaCtrl, $filter)
+    protected function setData($id, $table, $depth, $filter)
     {
-        $id = (int)$id;
-        if (!array_key_exists('delete', $tcaCtrl)) {
+        if (!array_key_exists('delete', $GLOBALS['TCA'][$table]['ctrl'])) {
             return;
         }
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-        $queryBuilder->getRestrictions()->removeAll();
 
-        // find the 'deleted' field for this table
+        $id = (int)$id;
+        $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
         $deletedField = RecyclerUtility::getDeletedField($table);
-
-        // create the filter WHERE-clause
-        $filterConstraint = null;
-        if (trim($filter) !== '') {
-            $labelConstraint = $queryBuilder->expr()->like(
-                $tcaCtrl['label'],
-                $queryBuilder->createNamedParameter(
-                    $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($filter) . '%'),
-                    \PDO::PARAM_STR
-                )
-            );
-            if (MathUtility::canBeInterpretedAsInteger($filter)) {
-                $filterConstraint = $queryBuilder->expr()->orX(
-                    $queryBuilder->expr()->eq(
-                        'uid',
-                        $queryBuilder->createNamedParameter($filter, \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->eq(
-                        'pid',
-                        $queryBuilder->createNamedParameter($filter, \PDO::PARAM_INT)
-                    ),
-                    $labelConstraint
-                );
-            } else {
-                $filterConstraint = $labelConstraint;
-            }
-        }
+        $firstResult = 0;
+        $maxResults = 0;
 
         // get the limit
         if (!empty($this->limit)) {
             // count the number of deleted records for this pid
-            $deletedCount = $queryBuilder
+            $queryBuilder = $this->getFilteredQueryBuilder($table, $id, $filter);
+            $queryBuilder->getRestrictions()->removeAll();
+
+            $deletedCount = (int)$queryBuilder
                 ->count('*')
                 ->from($table)
-                ->where(
-                    $queryBuilder->expr()->neq($deletedField, $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
-                    $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
-                    $filterConstraint
+                ->andWhere(
+                    $queryBuilder->expr()->neq(
+                        $deletedField,
+                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    )
                 )
                 ->execute()
-                ->fetchColumn();
+                ->fetchColumn(0);
 
             // split the limit
-            $parts = GeneralUtility::trimExplode(',', $this->limit);
-            $offset = $parts[0];
-            $rowCount = $parts[1];
+            list($offset, $rowCount) = GeneralUtility::intExplode(',', $this->limit, true);
             // subtract the number of deleted records from the limit's offset
             $result = $offset - $deletedCount;
             // if the result is >= 0
@@ -206,14 +182,12 @@ class DeletedRecords
                 // do NOT query this depth; limit also does not need to be set, we set it anyways
                 $allowQuery = false;
                 $allowDepth = true;
-                $limit = '';
             } else {
                 // the offset for the temporary limit has to remain like the original offset
                 // in case the original offset was just crossed by the amount of deleted records
+                $tempOffset = 0;
                 if ($offset !== 0) {
                     $tempOffset = $offset;
-                } else {
-                    $tempOffset = 0;
                 }
                 // set the offset in the limit to 0
                 $newOffset = 0;
@@ -222,7 +196,8 @@ class DeletedRecords
                 // if the result now is > limit's row count
                 if ($absResult > $rowCount) {
                     // use the limit's row count as the temporary limit
-                    $limit = implode(',', [$tempOffset, $rowCount]);
+                    $firstResult = $tempOffset;
+                    $maxResults = $rowCount;
                     // set the limit's row count to 0
                     $this->limit = implode(',', [$newOffset, 0]);
                     // do not go into new depth
@@ -230,7 +205,8 @@ class DeletedRecords
                 } else {
                     // if the result now is <= limit's row count
                     // use the result as the temporary limit
-                    $limit = implode(',', [$tempOffset, $absResult]);
+                    $firstResult = $tempOffset;
+                    $maxResults = $absResult;
                     // subtract the result from the row count
                     $newCount = $rowCount - $absResult;
                     // store the new result in the limit's row count
@@ -249,31 +225,35 @@ class DeletedRecords
                 $allowQuery = true;
             }
         } else {
-            $limit = '';
             $allowDepth = true;
             $allowQuery = true;
         }
         // query for actual deleted records
         if ($allowQuery) {
-            $where = $queryBuilder->expr()->andX(
-                $queryBuilder->expr()->eq(
-                    'pid',
-                    $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
-                ),
-                $filterConstraint
-            );
-            $recordsToCheck = BackendUtility::getRecordsByField(
-                $table,
-                $deletedField,
-                '1',
-                ' AND ' . $where,
-                '',
-                'uid',
-                $limit,
-                false,
-                $queryBuilder
-            );
-            if ($recordsToCheck) {
+            $queryBuilder = $this->getFilteredQueryBuilder($table, $id, $filter);
+            if ($firstResult) {
+                $queryBuilder->setFirstResult($firstResult);
+            }
+            if ($maxResults) {
+                $queryBuilder->setMaxResults($maxResults);
+            }
+            $recordsToCheck = $queryBuilder->select('*')
+                ->from($table)
+                ->andWhere(
+                    $queryBuilder->expr()->eq(
+                        'pid',
+                        $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->eq(
+                        $deletedField,
+                        $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)
+                    )
+                )
+                ->orderBy('uid')
+                ->execute()
+                ->fetchAll();
+
+            if ($recordsToCheck !== false) {
                 $this->checkRecordAccess($table, $recordsToCheck);
             }
         }
@@ -290,12 +270,12 @@ class DeletedRecords
                 ->execute();
 
             while ($row = $resPages->fetch()) {
-                $this->setData($row['uid'], $table, $depth - 1, $tcaCtrl, $filter);
+                $this->setData($row['uid'], $table, $depth - 1, $filter);
                 // some records might have been added, check if we still have the limit for further queries
                 if (!empty($this->limit)) {
-                    $parts = GeneralUtility::trimExplode(',', $this->limit);
+                    $parts = GeneralUtility::intExplode(',', $this->limit, true);
                     // abort loop if LIMIT 0,0
-                    if ((int)$parts[0] === 0 && (int)$parts[1] === 0) {
+                    if ($parts[0] === 0 && $parts[1] === 0) {
                         $resPages->closeCursor();
                         break;
                     }
@@ -306,6 +286,55 @@ class DeletedRecords
         $this->title[$table] = $tcaCtrl['title'];
     }
 
+    /**
+     * Helper method for setData() to create a QueryBuilder that filters the records by default.
+     *
+     * @param string $table
+     * @param int $pid
+     * @param string $filter
+     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
+     * @throws \InvalidArgumentException
+     */
+    protected function getFilteredQueryBuilder(string $table, int $pid, string $filter): QueryBuilder
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        // create the filter WHERE-clause
+        $filterConstraint = null;
+        if (trim($filter) !== '') {
+            $filterConstraint = $queryBuilder->expr()->like(
+                $GLOBALS['TCA'][$table]['ctrl']['label'],
+                $queryBuilder->createNamedParameter(
+                    $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($filter) . '%'),
+                    \PDO::PARAM_STR
+                )
+            );
+            if (MathUtility::canBeInterpretedAsInteger($filter)) {
+                $filterConstraint = $queryBuilder->expr()->orX(
+                    $queryBuilder->expr()->eq(
+                        'uid',
+                        $queryBuilder->createNamedParameter($filter, \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->eq(
+                        'pid',
+                        $queryBuilder->createNamedParameter($filter, \PDO::PARAM_INT)
+                    ),
+                    $filterConstraint
+                );
+            }
+        }
+
+        $queryBuilder->where(
+            $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
+            $filterConstraint
+        );
+
+        return $queryBuilder;
+    }
+
     /**
      * Checks whether the current backend user has access to the given records.
      *
index b1b7fe0..4f8cbf6 100644 (file)
@@ -14,11 +14,13 @@ namespace TYPO3\CMS\Workspaces\Service;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Restriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
 use TYPO3\CMS\Core\SingletonInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -64,7 +66,7 @@ class WorkspaceService implements SingletonInterface
 
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
         $queryBuilder->getRestrictions()
-            ->add(GeneralUtility::makeInstance(Restriction\RootLevelRestriction::class));
+            ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
 
         $result = $queryBuilder
             ->select('uid', 'title', 'adminusers', 'members')
@@ -1102,24 +1104,36 @@ class WorkspaceService implements SingletonInterface
     {
         $languageOptions = [];
         /** @var \TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider $translationConfigurationProvider */
-        $translationConfigurationProvider = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider::class);
+        $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
         $systemLanguages = $translationConfigurationProvider->getSystemLanguages($pageId);
 
         if ($GLOBALS['BE_USER']->checkLanguageAccess(0)) {
             // Use configured label for default language
             $languageOptions[0] = $systemLanguages[0]['title'];
         }
-        $pages = BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $pageId);
 
-        if (!is_array($pages)) {
-            return $languageOptions;
-        }
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages_language_overlay');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $result = $queryBuilder->select('sys_language_uid')
+            ->from('pages_language_overlay')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    'pid',
+                    $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
+                )
+            )
+            ->execute();
 
-        foreach ($pages as $page) {
-            $languageId = (int)$page['sys_language_uid'];
+        while ($row = $result->fetch()) {
+            $languageId = (int)$row['sys_language_uid'];
             // Only add links to active languages the user has access to
             if (isset($systemLanguages[$languageId]) && $GLOBALS['BE_USER']->checkLanguageAccess($languageId)) {
-                $languageOptions[$page['sys_language_uid']] = $systemLanguages[$languageId]['title'];
+                $languageOptions[$languageId] = $systemLanguages[$languageId]['title'];
             }
         }