[TASK] Doctrine: migrate EXT:recordlist/Recordlist/RecordList/AbstractDatabaseRecordList 40/48240/13
authorMarkus Hoelzle <typo3@markus-hoelzle.de>
Sun, 22 May 2016 17:42:46 +0000 (19:42 +0200)
committerJan Helke <typo3@helke.de>
Sun, 10 Jul 2016 09:13:23 +0000 (11:13 +0200)
Resolves: #76259
Releases: master
Change-Id: I2bdc4c56fe9f2804aad857f741c1e68d042fa346
Reviewed-on: https://review.typo3.org/48240
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Bamboo TYPO3com <info@typo3.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Jan Helke <typo3@helke.de>
Tested-by: Jan Helke <typo3@helke.de>
typo3/sysext/backend/Classes/View/PageLayoutView.php
typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-ReturnValueOfAbstractDatabaseRecordListmakeSearchStringChanged.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-SignatureOfMethodGetResultChangedInPageLayoutView.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-SignatureOfMethodSetTotalItemsChangedInAbstractDatabaseRecordList.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-ValuePassedToHookGetTableChanged.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Breaking-76879-RemoveUnusedPropertyPidSelectFromAbstractDatabaseRecordList.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Deprecation-76259-DeprecateMethodMakeQueryArrayOfAbstractDatabaseRecordList.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-76259-IntroduceBuildQueryParametersPostProcessHook.rst [new file with mode: 0644]
typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php [changed mode: 0644->0755]
typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php

index d85c5ed..72b3f04 100644 (file)
@@ -18,7 +18,9 @@ use TYPO3\CMS\Backend\Controller\Page\LocalizationController;
 use TYPO3\CMS\Backend\Controller\PageLayoutController;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
@@ -443,6 +445,9 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
     public function getTable_tt_content($id)
     {
         $backendUser = $this->getBackendUser();
+        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable('tt_content')
+            ->getExpressionBuilder();
         $this->pageinfo = BackendUtility::readPageAccess($this->id, '');
         $this->initializeLanguages();
         $this->initializeClipboard();
@@ -497,9 +502,9 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
             }
 
             if (count($langListArr) === 1 || $lP === 0) {
-                $showLanguage = ' AND sys_language_uid IN (' . $lP . ',-1)';
+                $showLanguage = $expressionBuilder->in('sys_language_uid', [$lP, -1]);
             } else {
-                $showLanguage = ' AND sys_language_uid=' . $lP;
+                $showLanguage = $expressionBuilder->eq('sys_language_uid', $lP);
             }
             $cList = explode(',', $this->tt_contentConfig['cols']);
             $content = array();
@@ -948,15 +953,16 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
     public function makeOrdinaryList($table, $id, $fList, $icon = false, $addWhere = '')
     {
         // Initialize
-        $queryParts = $this->makeQueryArray($table, $id, $addWhere);
-        $this->setTotalItems($queryParts);
+        $addWhere = empty($addWhere) ? [] : [QueryHelper::stripLogicalOperatorPrefix($addWhere)];
+        $queryBuilder = $this->getQueryBuilder($table, $id, $addWhere);
+        $this->setTotalItems($table, $id, $addWhere);
         $dbCount = 0;
         $result = false;
         // Make query for records if there were any records found in the count operation
         if ($this->totalItems) {
-            $result = $this->getDatabase()->exec_SELECT_queryArray($queryParts);
+            $result = $queryBuilder->execute();
             // Will return FALSE, if $result is invalid
-            $dbCount = $this->getDatabase()->sql_num_rows($result);
+            $dbCount = $result->rowCount();
         }
         // If records were found, render the list
         if (!$dbCount) {
@@ -991,7 +997,7 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
         $out .= $this->addElement(1, '', $theData, ' class="c-headLine"', 15, '', 'th');
         // Render Items
         $this->eCounter = $this->firstElementNumber;
-        while ($row = $this->getDatabase()->sql_fetch_assoc($result)) {
+        while ($row = $result->fetch()) {
             BackendUtility::workspaceOL($table, $row);
             if (is_array($row)) {
                 list($flag, $code) = $this->fwd_rwd_nav();
@@ -1026,7 +1032,6 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
                 $this->eCounter++;
             }
         }
-        $this->getDatabase()->sql_free_result($result);
         // Wrap it all in a table:
         $out = '
                        <!--
@@ -1136,8 +1141,19 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
         $columns = array_map('intval', $columns);
         $contentRecordsPerColumn = array_fill_keys($columns, array());
 
-        $queryParts = $this->makeQueryArray('tt_content', $id, 'AND colPos IN (' . implode(',', $columns) . ')' . $additionalWhereClause);
-        $result = $this->getDatabase()->exec_SELECT_queryArray($queryParts);
+        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('tt_content')
+            ->expr();
+
+        $queryBuilder = $this->getQueryBuilder(
+            'tt_content',
+            $id,
+            [
+                $expressionBuilder->in('colPos', $columns),
+                $additionalWhereClause
+            ]
+        );
+        $result = $queryBuilder->execute();
         // Traverse any selected elements and render their display code:
         $rowArr = $this->getResult($result);
 
@@ -1971,15 +1987,15 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
     /**
      * Traverse the result pointer given, adding each record to array and setting some internal values at the same time.
      *
-     * @param bool|\mysqli_result|object $result MySQLi result object / DBAL object
+     * @param \Doctrine\DBAL\Driver\Statement $result MySQLi result object / DBAL object
      * @param string $table Table name defaulting to tt_content
      * @return array The selected rows returned in this array.
      */
-    public function getResult($result, $table = 'tt_content')
+    public function getResult(\Doctrine\DBAL\Driver\Statement $result, string $table = 'tt_content'): array
     {
-        $output = array();
+        $output = [];
         // Traverse the result:
-        while ($row = $this->getDatabase()->sql_fetch_assoc($result)) {
+        while ($row = $result->fetch()) {
             BackendUtility::workspaceOL($table, $row, -99, true);
             if ($row) {
                 // Add the row to the array:
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-ReturnValueOfAbstractDatabaseRecordListmakeSearchStringChanged.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-ReturnValueOfAbstractDatabaseRecordListmakeSearchStringChanged.rst
new file mode 100644 (file)
index 0000000..51a24a0
--- /dev/null
@@ -0,0 +1,33 @@
+=======================================================================================
+Breaking: #76259 - Return value of AbstractDatabaseRecordList::makeSearchString changed
+=======================================================================================
+
+Description
+===========
+
+The value returned by :php:``AbstractDatabaseRecordList::makeSearchString``
+has been adjusted.
+
+The SQL fragment no longer includes the leading ``AND`` SQL operator.
+
+
+Impact
+======
+
+3rd Party extensions need to ensure that valid SQL queries are being built
+using the returned fragment.
+
+
+Affected Installations
+======================
+
+Installations using 3rd party extensions that use :php:``AbstractDatabaseRecordList::makeSearchString``
+and expect the leading ``AND``.
+
+
+Migration
+=========
+
+Migrate your code to use the Doctrine QueryBuilder where the `` AND ``
+is no longer needed or prepend the missing ``AND`` before using the
+return value.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-SignatureOfMethodGetResultChangedInPageLayoutView.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-SignatureOfMethodGetResultChangedInPageLayoutView.rst
new file mode 100644 (file)
index 0000000..2b3383b
--- /dev/null
@@ -0,0 +1,40 @@
+=====================================================================
+Breaking: #76259 - Signature of getResult() in PageLayoutView changed
+=====================================================================
+
+Description
+===========
+
+As part of the migration of the core code to use Doctrine the signature of the method
+:php:``PageLayoutView::getResult()`` was changed.
+
+Instead of accepting :php:``bool``, :php:``\mysqli_result`` or :php:``object`` as a
+result provider only :php:``\Doctrine\DBAL\Driver\Statement`` objects are accepted.
+
+The new signature is:
+
+.. code-block:: php
+
+    public function getResult(\Doctrine\DBAL\Driver\Statement $result, string $table = 'tt_content'): array
+    {
+    }
+
+
+Impact
+======
+
+3rd party extensions using :php:``PageLayoutView::getResult()`` need to provide the correct
+input type, otherwise exceptions of type :php:``InvalidArgumentException`` will be thrown.
+
+
+Affected Installations
+======================
+
+Installations using 3rd party extensions that use :php:``PageLayoutView::getResult()``.
+
+
+Migration
+=========
+
+Migrate all code that works with the :php:``PageLayoutView::getResult()`` to provide the expected
+Doctrine Statement object.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-SignatureOfMethodSetTotalItemsChangedInAbstractDatabaseRecordList.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-SignatureOfMethodSetTotalItemsChangedInAbstractDatabaseRecordList.rst
new file mode 100644 (file)
index 0000000..d2f6a8c
--- /dev/null
@@ -0,0 +1,48 @@
+=====================================================================================
+Breaking: #76259 - Signature of setTotalItems() in AbstractDatabaseRecordList changed
+=====================================================================================
+
+Description
+===========
+
+As part of the migration of the core code to use Doctrine the signature of the method
+:php:``PageLayoutView::getResult()`` was changed.
+
+The new signature is:
+
+.. code-block:: php
+
+    public function setTotalItems(string $table, int $pageId, array $constraints)
+    {
+        $queryBuilder = $this->getQueryBuilder($table, $pageId, $constraints);
+        $this->totalItems = (int)$queryBuilder->count('*')
+            ->execute()
+            ->fetchColumn();
+    }
+
+The parameter ``$constraints`` is expected to be an array of Doctrine Expressions
+or SQL fragments.
+
+In the case of SQL fragments proper quoting needs to be ensured by the invoking method.
+SQL fragments should not have a leading `` AND `` SQL operator.
+
+
+Impact
+======
+
+3rd party extensions using :php:``AbstractDatabaseRecordList::setTotalItems()`` need
+to update the method invokation.
+
+
+Affected Installations
+======================
+
+Installations using 3rd party extensions that use :php:``AbstractDatabaseRecordList::setTotalItems()``.
+
+
+Migration
+=========
+
+Instead of passing in an array of parameters built using the deprecated ::php::``makeQueryArray`` method
+explictly pass in the table name, page id and any additional query restrictions required.
+
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-ValuePassedToHookGetTableChanged.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-76259-ValuePassedToHookGetTableChanged.rst
new file mode 100644 (file)
index 0000000..4621d81
--- /dev/null
@@ -0,0 +1,30 @@
+========================================================
+Breaking: #76259 - Value passed to hook getTable changed
+========================================================
+
+Description
+===========
+
+The value for ``$additionalWhere`` passed to the method :php:``getDBlistQuery``
+as part of the hook ``getTable`` in :php:``\TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList``
+has been changed and no longer includes the leading ``AND``.
+
+
+Impact
+======
+
+3rd Party extensions implementing the hook method need to ensure the leading ``AND`` is no
+longer expected to be present. The leading ``AND`` should also not be returned anymore.
+
+
+Affected Installations
+======================
+
+
+Installations using 3rd party extensions that implement the hook method.
+
+
+Migration
+=========
+
+Migrate the hook method not to expect or prepend the leading ``AND``.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-76879-RemoveUnusedPropertyPidSelectFromAbstractDatabaseRecordList.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-76879-RemoveUnusedPropertyPidSelectFromAbstractDatabaseRecordList.rst
new file mode 100644 (file)
index 0000000..16864b5
--- /dev/null
@@ -0,0 +1,27 @@
+===================================================================================
+Breaking: #76879 - Remove unused property pidSelect from AbstractDatabaseRecordList
+===================================================================================
+
+Description
+===========
+
+The unused public property :php:``pidSelect`` has been removed from the :php:``AbstractDatabaseRecordList` class.
+
+
+Impact
+======
+
+Extensions which use the public property will throw a fatal error.
+
+
+Affected Installations
+======================
+
+All installations with a 3rd party extension using the :php:``pidSelect`` property.
+
+
+Migration
+=========
+
+Use :php:``AbstractDatabaseRecordList::setOverridePageIdList()`` to set an array of page ids
+that should be used to restrict the query.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-76259-DeprecateMethodMakeQueryArrayOfAbstractDatabaseRecordList.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-76259-DeprecateMethodMakeQueryArrayOfAbstractDatabaseRecordList.rst
new file mode 100644 (file)
index 0000000..c0de5f7
--- /dev/null
@@ -0,0 +1,28 @@
+===================================================================================
+Deprecation: #76259 - Deprecate method makeQueryArray of AbstractDatabaseRecordList
+===================================================================================
+
+Description
+===========
+
+The method :php:``AbstractDatabaseRecordList::makeQueryArray`` has been marked
+as deprecated.
+
+Impact
+======
+
+Using the mentioned will trigger a deprecation log entry. The hook ``makeQueryArray``
+provided within this method is no longer called by the core.
+
+
+Affected Installations
+======================
+
+Instances that use the method.
+
+
+Migration
+=========
+
+Migrate your code to the Doctrine based replacement :php:``\TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList::getQueryBuilder``
+and the associated hook ``buildQueryParameters``.
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-76259-IntroduceBuildQueryParametersPostProcessHook.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-76259-IntroduceBuildQueryParametersPostProcessHook.rst
new file mode 100644 (file)
index 0000000..c40e284
--- /dev/null
@@ -0,0 +1,44 @@
+================================================================
+Feature: #76259 - Introduce buildQueryParametersPostProcess Hook
+================================================================
+
+Description
+===========
+
+With the migration to Doctrine the hook ``buildQueryParameters``
+has been introduced in the class :php:``DatabaseRecordList``. This hook
+replaces the hook ``makeQueryArray`` from the deprecated method
+:php:``AbstractDatabaseRecordList::makeQueryArray``.
+
+Using this hook allows modifying the parameters used to query the database
+for records to be shown in the record list view.
+
+The hook-object needs to be registered in php:``$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList::class]['buildQueryParameters'][]``
+and implement the public method php:``buildQueryParametersPostProcess``.
+
+The signature of the ``buildQueryParametersPostProcess`` method is as following:
+
+.. code-block:: php
+
+    public function buildQueryParametersPostProcess(
+        array $parameters,
+        string $table,
+        int $pageId,
+        array $additionalConstraints,
+        array $fieldList,
+        AbstractDatabaseRecordList $parentObject
+    ) {
+    }
+
+The following fields are part of the ``$parameters`` array and can be modified:
+
+==============  ==========  ===========
+Key             Type        Description
+--------------  ----------  -----------
+table           string      The queried tablename
+fields          string[]    The columns to retrieve
+groupBy         string[]    The columns to group the result by
+firstResult     int|null    The offset to start retrieve rows from
+maxResults      int|null    The maximum number of rows to retrieve
+orderBy         array[]     Array of arrays containing fieldname/sorting pairs
+where           string[]    Array of where conditions to apply to the database query.
old mode 100644 (file)
new mode 100755 (executable)
index 011cd0a..81f3289
@@ -20,7 +20,11 @@ use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Tree\View\PageTreeView;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Database\DatabaseConnection;
+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\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
@@ -178,14 +182,6 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     public $showLimit = 0;
 
     /**
-     * Query part for either a list of ids "pid IN (1,2,3)" or a single id "pid = 123" from
-     * which to select/search etc. (when search-levels are set high). See start()
-     *
-     * @var string
-     */
-    public $pidSelect = '';
-
-    /**
      * Page select permissions
      *
      * @var string
@@ -323,6 +319,12 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     protected $overrideUrlParameters = array();
 
     /**
+     * Override the page ids taken into account by getPageIdConstraint()
+     *
+     * @var array
+     */
+    protected $overridePageIdList = [];
+    /**
      * Array with before/after setting for tables
      * Structure:
      * 'tableName' => [
@@ -347,7 +349,6 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     public function start($id, $table, $pointer, $search = '', $levels = 0, $showLimit = 0)
     {
         $backendUser = $this->getBackendUserAuthentication();
-        $db = $this->getDatabaseConnection();
         // Setting internal variables:
         // sets the parent id
         $this->id = (int)$id;
@@ -374,22 +375,40 @@ class AbstractDatabaseRecordList extends AbstractRecordList
         $this->HTMLcode = '';
         // Limits
         if (isset($this->modTSconfig['properties']['itemsLimitPerTable'])) {
-            $this->itemsLimitPerTable = MathUtility::forceIntegerInRange((int)$this->modTSconfig['properties']['itemsLimitPerTable'], 1, 10000);
+            $this->itemsLimitPerTable = MathUtility::forceIntegerInRange(
+                (int)$this->modTSconfig['properties']['itemsLimitPerTable'],
+                1,
+                10000
+            );
         }
         if (isset($this->modTSconfig['properties']['itemsLimitSingleTable'])) {
-            $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange((int)$this->modTSconfig['properties']['itemsLimitSingleTable'], 1, 10000);
+            $this->itemsLimitSingleTable = MathUtility::forceIntegerInRange(
+                (int)$this->modTSconfig['properties']['itemsLimitSingleTable'],
+                1,
+                10000
+            );
         }
-        // Set search levels:
-        $searchLevels = $this->searchLevels;
-        $this->perms_clause = $backendUser->getPagePermsClause(1);
+
+        // $table might be NULL at this point in the code. As the expressionBuilder
+        // is used to limit returned records based on the page permissions and the
+        // uid field of the pages it can hardcoded to work on the pages table.
+        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages')
+            ->expr();
+        $permsClause = $expressionBuilder->andX($backendUser->getPagePermsClause(1));
         // This will hide records from display - it has nothing to do with user rights!!
         if ($pidList = $backendUser->getTSConfigVal('options.hideRecords.pages')) {
-            if ($pidList = $db->cleanIntList($pidList)) {
-                $this->perms_clause .= ' AND pages.uid NOT IN (' . $pidList . ')';
+            $pidList = GeneralUtility::intExplode(',', $pidList, true);
+            if (!empty($pidList)) {
+                $permsClause->add($expressionBuilder->notIn('pages.uid', $pidList));
             }
         }
+        $this->perms_clause = (string)$permsClause;
+
         // Get configuration of collapsed tables from user uc and merge with sanitized GP vars
-        $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list']) ? $backendUser->uc['moduleData']['list'] : array();
+        $this->tablesCollapsed = is_array($backendUser->uc['moduleData']['list'])
+            ? $backendUser->uc['moduleData']['list']
+            : [];
         $collapseOverride = GeneralUtility::_GP('collapse');
         if (is_array($collapseOverride)) {
             foreach ($collapseOverride as $collapseTable => $collapseValue) {
@@ -405,16 +424,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
                 HttpUtility::redirect($returnUrl);
             }
         }
-        if ($searchLevels > 0) {
-            $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
-            $pidList = implode(',', $db->cleanIntArray($allowedMounts));
-            $this->pidSelect = 'pid IN (' . $pidList . ')';
-        } elseif ($searchLevels < 0) {
-            // Search everywhere
-            $this->pidSelect = '1=1';
-        } else {
-            $this->pidSelect = 'pid=' . (int)$id;
-        }
+
         // Initialize languages:
         if ($this->localizationView) {
             $this->initializeLanguages();
@@ -453,7 +463,10 @@ class AbstractDatabaseRecordList extends AbstractRecordList
             if (!$hideTable) {
                 // Don't show table if hidden by TCA ctrl section
                 // Don't show table if hidden by pageTSconfig mod.web_list.hideTables
-                $hideTable = $hideTable || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable']) || in_array($tableName, $hideTablesArray, true) || in_array('*', $hideTablesArray, true);
+                $hideTable = $hideTable
+                    || !empty($GLOBALS['TCA'][$tableName]['ctrl']['hideTable'])
+                    || in_array($tableName, $hideTablesArray, true)
+                    || in_array('*', $hideTablesArray, true);
                 // Override previous selection if table is enabled or hidden by TSconfig TCA override mod.web_list.table
                 if (isset($this->tableTSconfigOverTCA[$tableName . '.']['hideTable'])) {
                     $hideTable = (bool)$this->tableTSconfigOverTCA[$tableName . '.']['hideTable'];
@@ -472,24 +485,34 @@ class AbstractDatabaseRecordList extends AbstractRecordList
         }
         unset($config);
 
-        $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies($tableNames);
+        $orderedTableNames = GeneralUtility::makeInstance(DependencyOrderingService::class)
+            ->orderByDependencies($tableNames);
 
-        $db = $this->getDatabaseConnection();
         foreach ($orderedTableNames as $tableName => $_) {
             // check if we are in single- or multi-table mode
             if ($this->table) {
-                $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'] : $this->itemsLimitSingleTable;
+                $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems'])
+                    ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxSingleDBListItems']
+                    : $this->itemsLimitSingleTable;
             } else {
                 // if there are no records in table continue current foreach
-                $firstRow = $db->exec_SELECTgetSingleRow(
-                    'uid',
-                    $tableName,
-                    $this->pidSelect . BackendUtility::deleteClause($tableName) . BackendUtility::versioningPlaceholderClause($tableName)
-                );
-                if ($firstRow === false) {
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($tableName);
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                    ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+                $firstRow = $queryBuilder->select('uid')
+                    ->from($tableName)
+                    ->where($this->getPageIdConstraint($tableName))
+                    ->execute()
+                    ->fetch();
+                if (!is_array($firstRow)) {
                     continue;
                 }
-                $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']) ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'] : $this->itemsLimitPerTable;
+                $this->iLimit = isset($GLOBALS['TCA'][$tableName]['interface']['maxDBListItems'])
+                    ? (int)$GLOBALS['TCA'][$tableName]['interface']['maxDBListItems']
+                    : $this->itemsLimitPerTable;
             }
             if ($this->showLimit) {
                 $this->iLimit = $this->showLimit;
@@ -509,10 +532,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
             } else {
                 $fields = array();
             }
-            // Find ID to use (might be different for "versioning_followPages" tables)
-            if ($this->searchLevels === 0) {
-                $this->pidSelect = 'pid=' . (int)$this->id;
-            }
+
             // Finally, render the list:
             $this->HTMLcode .= $this->getTable($tableName, $this->id, implode(',', $fields));
         }
@@ -647,9 +667,12 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      * @param string $addWhere Additional part for where clause
      * @param string $fieldList Field list to select, * for all (for "SELECT [fieldlist] FROM ...")
      * @return string[] Returns query array
+     *
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9. Please use getQueryBuilder()
      */
     public function makeQueryArray($table, $id, $addWhere = '', $fieldList = '*')
     {
+        GeneralUtility::logDeprecatedFunction();
         $hookObjectsArr = array();
         if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'])) {
             foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list.inc']['makeQueryArray'] as $classRef) {
@@ -676,7 +699,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
         $queryParts = array(
             'SELECT' => $fieldList,
             'FROM' => $table,
-            'WHERE' => $this->pidSelect . ' ' . $pC . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table) . ' ' . $addWhere . ' ' . $search,
+            'WHERE' => $this->getPageIdConstraint($table) . ' ' . $pC . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table) . ' ' . $addWhere . ' ' . $search,
             'GROUPBY' => '',
             'ORDERBY' => $this->getDatabaseConnection()->stripOrderBy($orderBy),
             'LIMIT' => $limit
@@ -702,15 +725,153 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     }
 
     /**
-     * Based on input query array (query for selecting count(*) from a table) it will select the number of records and set the value in $this->totalItems
+     * Returns a QueryBuilder configured to select $fields from $table where the pid is restricted
+     * depending on the current searchlevel setting.
      *
-     * @param string[] $queryParts Query array
-     * @return void
-     * @see makeQueryArray()
+     * @param string $table Table name
+     * @param int $pageId Page id Only used to build the search constraints, getPageIdConstraint() used for restrictions
+     * @param string[] $additionalConstraints Additional part for where clause
+     * @param string[] $fields Field list to select, * for all
+     * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
+     */
+    protected function getQueryBuilder(
+        string $table,
+        int $pageId,
+        array $additionalConstraints = [],
+        array $fields = ['*']
+    ): QueryBuilder {
+        $queryParameters = $this->buildQueryParameters($table, $pageId, $fields, $additionalConstraints);
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($queryParameters['table']);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+        $queryBuilder
+            ->select(...$queryParameters['fields'])
+            ->from($queryParameters['table'])
+            ->where(...$queryParameters['where']);
+
+        if (!empty($queryParameters['orderBy'])) {
+            foreach ($queryParameters['orderBy'] as $fieldNameAndSorting) {
+                list($fieldName, $sorting) = $fieldNameAndSorting;
+                $queryBuilder->addOrderBy($fieldName, $sorting);
+            }
+        }
+
+        if (!empty($queryParameters['firstResult'])) {
+            $queryBuilder->setFirstResult((int)$queryParameters['firstResult']);
+        }
+
+        if (!empty($queryParameters['maxResults'])) {
+            $queryBuilder->setMaxResults((int)$queryParameters['maxResults']);
+        }
+
+        if (!empty($queryParameters['groupBy'])) {
+            $queryBuilder->groupBy($queryParameters['groupBy']);
+        }
+
+        return $queryBuilder;
+    }
+
+    /**
+     * Return the query parameters to select the records from a table $table with pid = $this->pidList
+     *
+     * @param string $table Table name
+     * @param int $pageId Page id Only used to build the search constraints, $this->pidList is used for restrictions
+     * @param string[] $fieldList List of fields to select from the table
+     * @param string[] $additionalConstraints Additional part for where clause
+     * @return array
+     */
+    protected function buildQueryParameters(
+        string $table,
+        int $pageId,
+        array $fieldList = ['*'],
+        array $additionalConstraints = []
+    ): array {
+        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($table)
+            ->expr();
+
+        $parameters = [
+            'table' => $table,
+            'fields' => $fieldList,
+            'groupBy' => null,
+            'orderBy' => null,
+            'firstResult' => $this->firstElementNumber ?: null,
+            'maxResults' => $this->iLimit ? ($this->iLimit + 1) : null,
+        ];
+
+        if ($this->sortField && in_array($this->sortField, $this->makeFieldList($table, 1))) {
+            $parameters['orderBy'] = $this->sortRev ? [$this->sortField, 'DESC'] : [$this->sortField, 'ASC'];
+        } else {
+            $orderBy = $GLOBALS['TCA'][$table]['ctrl']['sortby'] ?: $GLOBALS['TCA'][$table]['ctrl']['default_sortby'];
+            $parameters['orderBy'] = QueryHelper::parseOrderBy((string)$orderBy);
+        }
+
+        // Build the query constraints
+        $constraints = [
+            'pidSelect' => $this->getPageIdConstraint($table),
+            'search' => $this->makeSearchString($table, $pageId)
+        ];
+
+        // Filtering on displayable pages (permissions):
+        if ($table === 'pages' && $this->perms_clause) {
+            $constraints['pagePermsClause'] = $this->perms_clause;
+        }
+
+        // Filter out records that are translated, if TSconfig mod.web_list.hideTranslations is set
+        if ((GeneralUtility::inList($this->hideTranslations, $table) || $this->hideTranslations === '*')
+            && !empty($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
+            && $table !== 'pages_language_overlay'
+        ) {
+            $constraints['transOrigPointerField'] = $expressionBuilder->eq(
+                $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
+                0
+            );
+        }
+
+        $parameters['where'] = array_merge($constraints, $additionalConstraints);
+
+        $hookName = DatabaseRecordList::class;
+        if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'])) {
+            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][$hookName]['buildQueryParameters'] as $classRef) {
+                $hookObject = GeneralUtility::getUserObj($classRef);
+                if (method_exists($hookObject, 'buildQueryParametersPostProcess')) {
+                    $hookObject->buildQueryParametersPostProcess(
+                        $parameters,
+                        $table,
+                        $pageId,
+                        $additionalConstraints,
+                        $fieldList,
+                        $this
+                    );
+                }
+            }
+        }
+
+        // array_unique / array_filter used to eliminate empty and duplicate constraints
+        // the array keys are eliminated by this as well to facilitate argument unpacking
+        // when used with the querybuilder.
+        $parameters['where'] = array_unique(array_filter(array_values($parameters['where'])));
+
+        return $parameters;
+    }
+
+    /**
+     * Set the total items for the record list
+     *
+     * @param string $table Table name
+     * @param int $pageId Only used to build the search constraints, $this->pidList is used for restrictions
+     * @param array $constraints Additional constraints for where clause
      */
-    public function setTotalItems($queryParts)
+    public function setTotalItems(string $table, int $pageId, array $constraints)
     {
-        $this->totalItems = $this->getDatabaseConnection()->exec_SELECTcountRows('*', $queryParts['FROM'], $queryParts['WHERE']);
+        $queryBuilder = $this->getQueryBuilder($table, $pageId, $constraints);
+        $this->totalItems = (int)$queryBuilder->count('*')
+            ->execute()
+            ->fetchColumn();
     }
 
     /**
@@ -723,67 +884,96 @@ class AbstractDatabaseRecordList extends AbstractRecordList
      */
     public function makeSearchString($table, $currentPid = -1)
     {
-        $result = '';
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+        $expressionBuilder = $queryBuilder->expr();
+        $constraints = [];
         $currentPid = (int)$currentPid;
         $tablePidField = $table === 'pages' ? 'uid' : 'pid';
         // Make query, only if table is valid and a search string is actually defined:
-        if ($this->searchString) {
-            $result = ' AND 0=1';
-            $searchableFields = $this->getSearchFields($table);
-            if (!empty($searchableFields)) {
-                if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
-                    $whereParts = array(
-                        'uid=' . $this->searchString
-                    );
-                    foreach ($searchableFields as $fieldName) {
-                        if (isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
-                            $fieldConfig = &$GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
-                            $condition = $fieldName . '=' . $this->searchString;
-                            if ($fieldConfig['type'] == 'input' && $fieldConfig['eval'] && GeneralUtility::inList($fieldConfig['eval'], 'int')) {
-                                if (is_array($fieldConfig['search']) && in_array('pidonly', $fieldConfig['search']) && $currentPid > 0) {
-                                    $condition = '(' . $condition . ' AND ' . $tablePidField . '=' . $currentPid . ')';
-                                }
-                                $whereParts[] = $condition;
-                            } elseif ($fieldConfig['type'] == 'text' ||
-                                $fieldConfig['type'] == 'flex' ||
-                                ($fieldConfig['type'] == 'input' && (!$fieldConfig['eval'] || !preg_match('/date|time|int/', $fieldConfig['eval'])))) {
-                                $condition = $fieldName . ' LIKE \'%' . $this->searchString . '%\'';
-                                $whereParts[] = $condition;
-                            }
-                        }
+        if (empty($this->searchString)) {
+            return '1=1';
+        }
+
+        $searchableFields = $this->getSearchFields($table);
+        if (!empty($searchableFields)) {
+            if (MathUtility::canBeInterpretedAsInteger($this->searchString)) {
+                $constraints[] = $expressionBuilder->eq('uid', (int)$this->searchString);
+                foreach ($searchableFields as $fieldName) {
+                    if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                        continue;
                     }
-                } else {
-                    $whereParts = array();
-                    $db = $this->getDatabaseConnection();
-                    $like = '\'%' . $db->quoteStr($db->escapeStrForLike($this->searchString, $table), $table) . '%\'';
-                    foreach ($searchableFields as $fieldName) {
-                        if (isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
-                            $fieldConfig = &$GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
-                            $format = 'LOWER(%s) LIKE LOWER(%s)';
-                            if (is_array($fieldConfig['search'])) {
-                                if (in_array('case', $fieldConfig['search'])) {
-                                    $format = '%s LIKE %s';
-                                }
-                                if (in_array('pidonly', $fieldConfig['search']) && $currentPid > 0) {
-                                    $format = '(' . $format . ' AND ' . $tablePidField . '=' . $currentPid . ')';
-                                }
-                                if ($fieldConfig['search']['andWhere']) {
-                                    $format = '((' . $fieldConfig['search']['andWhere'] . ') AND (' . $format . '))';
-                                }
-                            }
-                            if ($fieldConfig['type'] == 'text' || $fieldConfig['type'] == 'flex' || $fieldConfig['type'] == 'input' && (!$fieldConfig['eval'] || !preg_match('/date|time|int/', $fieldConfig['eval']))) {
-                                $whereParts[] = sprintf($format, $fieldName, $like);
-                            }
+                    $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
+                    $fieldType = $fieldConfig['type'];
+                    $evalRules = $fieldConfig['eval'] ?: '';
+                    if ($fieldType === 'input' && $evalRules && GeneralUtility::inList($evalRules, 'int')) {
+                        if (is_array($fieldConfig['search'])
+                            && in_array('pidonly', $fieldConfig['search'], true)
+                            && $currentPid > 0
+                        ) {
+                            $constraints[] = $expressionBuilder->andX(
+                                $expressionBuilder->eq($fieldName, (int)$this->searchString),
+                                $expressionBuilder->eq($tablePidField, (int)$currentPid)
+                            );
                         }
+                    } elseif ($fieldType === 'text'
+                        || $fieldType === 'flex'
+                        || ($fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules)))
+                    ) {
+                        $constraints[] = $expressionBuilder->like(
+                            $fieldName,
+                            $queryBuilder->quote('%' . (int)$this->searchString . '%')
+                        );
                     }
                 }
-                // If search-fields were defined (and there always are) we create the query:
-                if (!empty($whereParts)) {
-                    $result = ' AND (' . implode(' OR ', $whereParts) . ')';
+            } else {
+                $like = $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($this->searchString) . '%');
+                foreach ($searchableFields as $fieldName) {
+                    if (!isset($GLOBALS['TCA'][$table]['columns'][$fieldName])) {
+                        continue;
+                    }
+                    $fieldConfig = $GLOBALS['TCA'][$table]['columns'][$fieldName]['config'];
+                    $fieldType = $fieldConfig['type'];
+                    $evalRules = $fieldConfig['eval'] ?: '';
+                    $searchConstraint = $expressionBuilder->andX();
+                    if (is_array($fieldConfig['search'])) {
+                        $searchConfig = $fieldConfig['search'];
+                        if (in_array('case', $searchConfig)) {
+                            $searchConstraint->add($expressionBuilder->like($fieldName, $like));
+                        } else {
+                            $searchConstraint->add(
+                                $expressionBuilder->comparison(
+                                    'LOWER(' . $queryBuilder->quoteIdentifier($fieldName) . ')',
+                                    'LIKE',
+                                    'LOWER(' . $like . ')'
+                                )
+                            );
+                        }
+                        if (in_array('pidonly', $searchConfig) && $currentPid > 0) {
+                            $searchConstraint->add($expressionBuilder->eq($tablePidField, (int)$currentPid));
+                        }
+                        if ($searchConfig['andWhere']) {
+                            $searchConstraint->add(
+                                QueryHelper::stripLogicalOperatorPrefix($fieldConfig['search']['andWhere'])
+                            );
+                        }
+                    }
+                    if ($fieldType === 'text'
+                        || $fieldType === 'flex'
+                        || $fieldType === 'input' && (!$evalRules || !preg_match('/date|time|int/', $evalRules))
+                    ) {
+                        if ($searchConstraint->count() !== 0) {
+                            $constraints[] = $searchConstraint;
+                        }
+                    }
                 }
             }
+            // If no search field conditions have been build ensure no results are returned
+            if (empty($constraints)) {
+                return '0=1';
+            }
+
+            return $expressionBuilder->orX(...$constraints);
         }
-        return $result;
     }
 
     /**
@@ -1088,15 +1278,33 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     public function localizationRedirect($justLocalized)
     {
         list($table, $orig_uid, $language) = explode(':', $justLocalized);
-        if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
-            $localizedRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', $table, $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$language . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$orig_uid . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table));
-            if (is_array($localizedRecord)) {
+        if ($GLOBALS['TCA'][$table]
+            && $GLOBALS['TCA'][$table]['ctrl']['languageField']
+            && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
+        ) {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+            $localizedRecordUid = $queryBuilder->select('uid')
+                ->from($table)
+                ->where(
+                    $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['languageField'], (int)$language),
+                    $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], (int)$orig_uid)
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetchColumn();
+
+            if ($localizedRecordUid !== false) {
                 // Create parameters and finally run the classic page module for creating a new page translation
                 $url = $this->listURL();
                 $editUserAccountUrl = BackendUtility::getModuleUrl(
                     'record_edit',
                     array(
-                        'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
+                        'edit[' . $table . '][' . $localizedRecordUid . ']' => 'edit',
                         'returnUrl' => $url
                     )
                 );
@@ -1150,18 +1358,61 @@ class AbstractDatabaseRecordList extends AbstractRecordList
     }
 
     /**
-     * @return BackendUserAuthentication
+     * @return array
      */
-    protected function getBackendUserAuthentication()
+    public function getOverridePageIdList(): array
     {
-        return $GLOBALS['BE_USER'];
+        return $this->overridePageIdList;
+    }
+
+    /**
+     * @param int[]|array $overridePageIdList
+     */
+    public function setOverridePageIdList(array $overridePageIdList)
+    {
+        $this->overridePageIdList = array_map('intval', $overridePageIdList);
+    }
+
+    /**
+     * Build SQL fragment to limit a query to a list of page IDs based on
+     * the current search level setting.
+     *
+     * @param string $tableName
+     * @return string
+     */
+    protected function getPageIdConstraint(string $tableName): string
+    {
+        // Set search levels:
+        $searchLevels = $this->searchLevels;
+
+        // Default is to search everywhere
+        $constraint = '1=1';
+
+        $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable($tableName)
+            ->getExpressionBuilder();
+
+        if (!empty($this->getOverridePageIdList())) {
+            $constraint = $expressionBuilder->in(
+                $tableName . '.pid',
+                $this->getOverridePageIdList()
+            );
+        }
+        if ($searchLevels === 0) {
+            $constraint = $expressionBuilder->eq($tableName . '.pid', (int)$this->id);
+        } elseif ($searchLevels > 0) {
+            $allowedMounts = $this->getSearchableWebmounts($this->id, $searchLevels, $this->perms_clause);
+            $constraint = $expressionBuilder->in($tableName . '.pid', array_map('intval', $allowedMounts));
+        }
+
+        return (string)$constraint;
     }
 
     /**
-     * @return DatabaseConnection
+     * @return BackendUserAuthentication
      */
-    protected function getDatabaseConnection()
+    protected function getBackendUserAuthentication()
     {
-        return $GLOBALS['TYPO3_DB'];
+        return $GLOBALS['BE_USER'];
     }
 }
index 5c20dd0..0473e12 100644 (file)
@@ -20,7 +20,8 @@ use TYPO3\CMS\Backend\Template\Components\ButtonBar;
 use TYPO3\CMS\Backend\Template\DocumentTemplate;
 use TYPO3\CMS\Backend\Template\ModuleTemplate;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
@@ -507,9 +508,9 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
         }
         $backendUser = $this->getBackendUserAuthentication();
         $lang = $this->getLanguageService();
-        $db = $this->getDatabaseConnection();
         // Init
         $addWhere = '';
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
         $titleCol = $GLOBALS['TCA'][$table]['ctrl']['label'];
         $thumbsCol = $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
         $l10nEnabled = $GLOBALS['TCA'][$table]['ctrl']['languageField']
@@ -543,11 +544,10 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
         if ($this->localizationView && $l10nEnabled) {
             $this->fieldArray[] = '_LOCALIZATION_';
             $this->fieldArray[] = '_LOCALIZATION_b';
-            $addWhere .= ' AND (
-                               ' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '<=0
-                               OR
-                               ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . ' = 0
-                       )';
+            $addWhere = (string)$queryBuilder->expr()->orX(
+                $queryBuilder->expr()->lte($GLOBALS['TCA'][$table]['ctrl']['languageField'], 0),
+                $queryBuilder->expr()->eq($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], 0)
+            );
         }
         // Cleaning up:
         $this->fieldArray = array_unique(array_merge($this->fieldArray, $rowListArray));
@@ -631,6 +631,9 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
                 $hookObject->getDBlistQuery($table, $id, $addWhere, $selFieldList, $this);
             }
         }
+        $additionalConstraints = empty($addWhere) ? [] : [QueryHelper::stripLogicalOperatorPrefix($addWhere)];
+        $selFieldList = GeneralUtility::trimExplode(',', $selFieldList, true);
+
         // Create the SQL query for selecting the elements in the listing:
         // do not do paging when outputting as CSV
         if ($this->csvOutput) {
@@ -641,23 +644,23 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
             $this->firstElementNumber = $this->firstElementNumber - 2;
             $this->iLimit = $this->iLimit + 2;
             // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
-            $queryParts = $this->makeQueryArray($table, $id, $addWhere, $selFieldList);
+            $queryBuilder = $this->getQueryBuilder($table, $id, $additionalConstraints, $selFieldList);
             $this->firstElementNumber = $this->firstElementNumber + 2;
             $this->iLimit = $this->iLimit - 2;
         } else {
             // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
-            $queryParts = $this->makeQueryArray($table, $id, $addWhere, $selFieldList);
+            $queryBuilder = $this->getQueryBuilder($table, $id, $additionalConstraints, $selFieldList);
         }
 
         // Finding the total amount of records on the page
         // (API function from TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRecordList)
-        $this->setTotalItems($queryParts);
+        $this->setTotalItems($table, $id, $additionalConstraints);
 
         // Init:
+        $queryResult = $queryBuilder->execute();
         $dbCount = 0;
         $out = '';
         $tableHeader = '';
-        $result = null;
         $listOnlyInSingleTableMode = $this->listOnlyInSingleTableMode && !$this->table;
         // If the count query returned any number of records, we perform the real query,
         // selecting records.
@@ -671,8 +674,7 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
                     $this->showLimit = $this->totalItems;
                     $this->iLimit = $this->totalItems;
                 }
-                $result = $db->exec_SELECT_queryArray($queryParts);
-                $dbCount = $db->sql_num_rows($result);
+                $dbCount = $queryResult->rowCount();
             }
         }
         // If any records was selected, render the list:
@@ -718,14 +720,14 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
                 $prevPrevUid = 0;
                 // Get first two rows and initialize prevPrevUid and prevUid if on page > 1
                 if ($this->firstElementNumber > 2 && $this->iLimit > 0) {
-                    $row = $db->sql_fetch_assoc($result);
+                    $row = $queryResult->fetch();
                     $prevPrevUid = -((int)$row['uid']);
-                    $row = $db->sql_fetch_assoc($result);
+                    $row = $queryResult->fetch();
                     $prevUid = $row['uid'];
                 }
                 $accRows = array();
                 // Accumulate rows here
-                while ($row = $db->sql_fetch_assoc($result)) {
+                while ($row = $queryResult->fetch()) {
                     if (!$this->isRowListingConditionFulfilled($table, $row)) {
                         continue;
                     }
@@ -745,7 +747,6 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
                         }
                     }
                 }
-                $db->sql_free_result($result);
                 $this->totalRowCount = count($accRows);
                 // CSV initiated
                 if ($this->csvOutput) {
@@ -1019,11 +1020,18 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
      */
     protected function getReferenceCount($tableName, $uid)
     {
-        $db = $this->getDatabaseConnection();
         if (!isset($this->referenceCount[$tableName][$uid])) {
-            $where = 'ref_table = ' . $db->fullQuoteStr($tableName, 'sys_refindex')
-                . ' AND ref_uid = ' . $uid . ' AND deleted = 0';
-            $numberOfReferences = $db->exec_SELECTcountRows('*', 'sys_refindex', $where);
+            $numberOfReferences = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getConnectionForTable('sys_refindex')
+                ->count(
+                    '*',
+                    'sys_refindex',
+                    [
+                        'ref_table' => $tableName,
+                        'ref_uid' => (int)$uid,
+                        'deleted' => 0
+                    ]
+                );
             $this->referenceCount[$tableName][$uid] = $numberOfReferences;
         }
         return $this->referenceCount[$tableName][$uid];
@@ -1831,14 +1839,22 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
      */
     protected function createReferenceHtml($tableName, $uid)
     {
-        $db = $this->getDatabaseConnection();
-        $referenceCount = $db->exec_SELECTcountRows(
-            '*',
-            'sys_refindex',
-            'ref_table = ' . $db->fullQuoteStr($tableName, 'sys_refindex') .
-            ' AND ref_uid = ' . $uid . ' AND deleted = 0'
+        $referenceCount = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable('sys_refindex')
+            ->count(
+                '*',
+                'sys_refindex',
+                [
+                    'ref_table' => $tableName,
+                    'ref_uid' => (int)$uid,
+                    'deleted' => 0,
+                ]
+            );
+
+        return $this->generateReferenceToolTip(
+            $referenceCount,
+            GeneralUtility::quoteJSvalue($tableName) . ', ' . GeneralUtility::quoteJSvalue($uid)
         );
-        return $this->generateReferenceToolTip($referenceCount, GeneralUtility::quoteJSvalue($tableName) . ', ' . GeneralUtility::quoteJSvalue($uid));
     }
 
     /**
@@ -2281,14 +2297,6 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
     }
 
     /**
-     * @return DatabaseConnection
-     */
-    protected function getDatabaseConnection()
-    {
-        return $GLOBALS['TYPO3_DB'];
-    }
-
-    /**
      * @return BaseScriptClass
      */
     protected function getModule()