From 5462fdfbd5c7fc3418ee93b8106a086b3fd3fa34 Mon Sep 17 00:00:00 2001
From: Manuel Selbach <manuel_selbach@yahoo.de>
Date: Fri, 17 Mar 2017 11:34:06 +0100
Subject: [PATCH] [TASK] Deprecate BackendUtility::getRecordRaw

Since we follow the principle of "prepared statement", the method
getRecordRaw will break this behaviour. Within the "where" parameter
of the function it is possible to pass a malformed query part.

Thus we should remove it, as the queryBuilder should be used everywhere
in the future to increase visiblity of which query will be processed at
a concrete point of code and to force the concept of prepared statements.

Resolves: #80317
Releases: master
Change-Id: If0028bf897ddee4517228a9e399390fe7266215e
Reviewed-on: https://review.typo3.org/52075
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
---
 .../Classes/Utility/BackendUtility.php        | 27 +++++++++-----
 .../core/Classes/DataHandling/DataHandler.php | 31 ++++++++++++++--
 .../core/Classes/Database/ReferenceIndex.php  | 27 ++++++++++++--
 ...-DeprecateBackendUtility::getRecordRaw.rst | 35 +++++++++++++++++++
 .../lowlevel/Classes/CleanerCommand.php       | 11 +++++-
 .../Classes/Command/CleanFlexFormsCommand.php | 13 ++++++-
 .../Command/MissingRelationsCommand.php       | 26 ++++++++++----
 .../WorkspaceVersionRecordsCommand.php        | 16 ++++++++-
 .../Classes/RecordList/DatabaseRecordList.php | 22 ++++++++++--
 9 files changed, 184 insertions(+), 24 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-80317-DeprecateBackendUtility::getRecordRaw.rst

diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
index a5738e9d066d..26ac94960abd 100644
--- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php
+++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
@@ -191,9 +191,11 @@ class BackendUtility
      * @param string $where WHERE clause
      * @param string $fields $fields is a list of fields to select, default is '*'
      * @return array|bool First row found, if any, FALSE otherwise
+     * @deprecated since TYPO3 CMS 8, will be removed in TYPO3 CMS 9.
      */
     public static function getRecordRaw($table, $where = '', $fields = '*')
     {
+        GeneralUtility::logDeprecatedFunction();
         $queryBuilder = static::getQueryBuilderForTable($table);
         $queryBuilder->getRestrictions()->removeAll();
 
@@ -4826,19 +4828,28 @@ class BackendUtility
      */
     public static function ADMCMD_previewCmds($pageInfo)
     {
+        $tableNameFeGroup = 'fe_groups';
         $simUser = '';
         $simTime = '';
-        if ($pageInfo['fe_group'] > 0) {
-            $simUser = '&ADMCMD_simUser=' . $pageInfo['fe_group'];
-        } elseif ((int)$pageInfo['fe_group'] === -2) {
+        if ($pageInfo[$tableNameFeGroup] > 0) {
+            $simUser = '&ADMCMD_simUser=' . $pageInfo[$tableNameFeGroup];
+        } elseif ((int)$pageInfo[$tableNameFeGroup] === -2) {
             // -2 means "show at any login". We simulate first available fe_group.
             /** @var PageRepository $sysPage */
             $sysPage = GeneralUtility::makeInstance(PageRepository::class);
-            $activeFeGroupRow = self::getRecordRaw(
-                'fe_groups',
-                '1=1' . $sysPage->enableFields('fe_groups'),
-                'uid'
-            );
+
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable($tableNameFeGroup);
+            $queryBuilder->getRestrictions()->removeAll();
+
+            $activeFeGroupRow = $queryBuilder->select('uid')
+                ->from($tableNameFeGroup)
+                ->where(
+                    QueryHelper::stripLogicalOperatorPrefix('1=1' . $sysPage->enableFields('fe_groups'))
+                )
+                ->execute()
+                ->fetch();
+
             if (!empty($activeFeGroupRow)) {
                 $simUser = '&ADMCMD_simUser=' . $activeFeGroupRow['uid'];
             }
diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index 3ae0c18b0788..d78d5c9ecade 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -5142,7 +5142,24 @@ class DataHandler
                 switch ($conf['type']) {
                     case 'flex':
                         $flexObj = GeneralUtility::makeInstance(FlexFormTools::class);
-                        $flexObj->traverseFlexFormXMLData($table, $fieldName, BackendUtility::getRecordRaw($table, 'uid=' . (int)$uid), $this, 'deleteRecord_flexFormCallBack');
+
+                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                            ->getQueryBuilderForTable($table);
+                        $queryBuilder->getRestrictions()->removeAll();
+
+                        $files = $queryBuilder
+                            ->select('*')
+                            ->from($table)
+                            ->where(
+                                $queryBuilder->expr()->eq(
+                                    'uid',
+                                    $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
+                                )
+                            )
+                            ->execute()
+                            ->fetch();
+
+                        $flexObj->traverseFlexFormXMLData($table, $fieldName, $files, $this, 'deleteRecord_flexFormCallBack');
                         break;
                 }
             }
@@ -6545,7 +6562,17 @@ class DataHandler
     {
         $id = (int)$id;
         if ($this->bypassAccessCheckForRecords) {
-            return is_array(BackendUtility::getRecordRaw($table, 'uid=' . $id, 'uid'));
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable($table);
+            $queryBuilder->getRestrictions()->removeAll();
+
+            $record = $queryBuilder->select('uid')
+                ->from($table)
+                ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)))
+                ->execute()
+                ->fetch();
+
+            return is_array($record);
         }
         // Processing the incoming $perms (from possible string to integer that can be AND'ed)
         if (!MathUtility::canBeInterpretedAsInteger($perms)) {
diff --git a/typo3/sysext/core/Classes/Database/ReferenceIndex.php b/typo3/sysext/core/Classes/Database/ReferenceIndex.php
index f49d5bd6f319..92edeb3126d9 100644
--- a/typo3/sysext/core/Classes/Database/ReferenceIndex.php
+++ b/typo3/sysext/core/Classes/Database/ReferenceIndex.php
@@ -227,7 +227,19 @@ class ReferenceIndex
         }
 
         // If the table has fields which could contain relations and the record does exist (including deleted-flagged)
-        if ($tableRelationFields !== '' && BackendUtility::getRecordRaw($tableName, 'uid=' . (int)$uid, 'uid')) {
+        $queryBuilder = $connection->createQueryBuilder();
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $exists = $queryBuilder
+            ->select('uid')
+            ->from($tableName)
+            ->where(
+                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
+            )
+            ->execute()
+            ->fetch();
+
+        if ($tableRelationFields !== '' && $exists) {
             // Then, get relations:
             $relations = $this->generateRefIndexData($tableName, $uid);
             if (is_array($relations)) {
@@ -332,7 +344,18 @@ class ReferenceIndex
         }
 
         // Get raw record from DB
-        $record = BackendUtility::getRecordRaw($tableName, 'uid=' . (int)$uid, $selectFields);
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $record = $queryBuilder
+            ->select(...explode(',', $selectFields))
+            ->from($tableName)
+            ->where(
+                $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
+            )
+            ->execute()
+            ->fetch();
+
         if (!is_array($record)) {
             return null;
         }
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80317-DeprecateBackendUtility::getRecordRaw.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80317-DeprecateBackendUtility::getRecordRaw.rst
new file mode 100644
index 000000000000..045184b94c4f
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-80317-DeprecateBackendUtility::getRecordRaw.rst
@@ -0,0 +1,35 @@
+.. include:: ../../Includes.txt
+
+==============================================================
+Deprecation: #80317 - Deprecate BackendUtility::getRecordRaw()
+==============================================================
+
+See :issue:`80317`
+
+Description
+===========
+
+Method :php:`BackendUtility::getRecordRaw()` has been deprecated and should not be
+used any longer.
+
+
+Impact
+======
+
+Extensions using above methods will throw a deprecation warning.
+
+
+Affected Installations
+======================
+
+All installations and extensions using the method :php:`BackendUtility::getRecordRaw()`.
+
+
+Migration
+=========
+
+Use the queryBuilder instead and remove all restrictions.
+For further information follow this link:
+https://docs.typo3.org/typo3cms/CoreApiReference/ApiOverview/Database/QueryBuilder/Index.html
+
+.. index:: Backend, Database, PHP-API
diff --git a/typo3/sysext/lowlevel/Classes/CleanerCommand.php b/typo3/sysext/lowlevel/Classes/CleanerCommand.php
index bcf976e1ffd9..53e32c5f27c8 100644
--- a/typo3/sysext/lowlevel/Classes/CleanerCommand.php
+++ b/typo3/sysext/lowlevel/Classes/CleanerCommand.php
@@ -369,7 +369,16 @@ NOW Running --AUTOFIX on result. OK?' . ($this->cli_isArg('--dryrun') ? ' (--dry
     {
         // Register page:
         $this->recStats['all']['pages'][$rootID] = $rootID;
-        $pageRecord = BackendUtility::getRecordRaw('pages', 'uid=' . (int)$rootID, 'deleted,title,t3ver_count,t3ver_wsid');
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $pageRecord = $queryBuilder->select('deleted', 'title', 't3ver_count', 't3ver_wsid')
+            ->from('pages')
+            ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($rootID, \PDO::PARAM_INT)))
+            ->execute()
+            ->fetch();
+
         $accumulatedPath .= '/' . $pageRecord['title'];
         // Register if page is deleted:
         if ($pageRecord['deleted']) {
diff --git a/typo3/sysext/lowlevel/Classes/Command/CleanFlexFormsCommand.php b/typo3/sysext/lowlevel/Classes/Command/CleanFlexFormsCommand.php
index fa21c720d663..a31fd0e53f81 100644
--- a/typo3/sysext/lowlevel/Classes/Command/CleanFlexFormsCommand.php
+++ b/typo3/sysext/lowlevel/Classes/Command/CleanFlexFormsCommand.php
@@ -219,7 +219,18 @@ class CleanFlexFormsCommand extends Command
         $flexObj = GeneralUtility::makeInstance(FlexFormTools::class);
         foreach ($GLOBALS['TCA'][$tableName]['columns'] as $columnName => $columnConfiguration) {
             if ($columnConfiguration['config']['type'] === 'flex') {
-                $fullRecord = BackendUtility::getRecordRaw($tableName, 'uid=' . (int)$uid);
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($tableName);
+                $queryBuilder->getRestrictions()->removeAll();
+
+                $fullRecord = $queryBuilder->select('*')
+                    ->from($tableName)
+                    ->where(
+                        $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT))
+                    )
+                    ->execute()
+                    ->fetch();
+
                 if ($fullRecord[$columnName]) {
                     // Clean XML and check against the record fetched from the database
                     $newXML = $flexObj->cleanFlexFormXML($tableName, $columnName, $fullRecord);
diff --git a/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php b/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php
index d7d1346ee317..57b6e1b810ad 100644
--- a/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php
+++ b/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php
@@ -20,7 +20,6 @@ use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
@@ -236,11 +235,26 @@ If you want to get more detailed information, use the --verbose option.')
             $idx = $rec['ref_table'] . ':' . $rec['ref_uid'];
             // Get referenced record:
             if (!isset($existingRecords[$idx])) {
-                $existingRecords[$idx] = BackendUtility::getRecordRaw(
-                    $rec['ref_table'],
-                    'uid=' . (int)$rec['ref_uid'],
-                    'uid,pid' . (isset($GLOBALS['TCA'][$rec['ref_table']]['ctrl']['delete']) ? ',' . $GLOBALS['TCA'][$rec['ref_table']]['ctrl']['delete'] : '')
-                );
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($rec['ref_table']);
+                $queryBuilder->getRestrictions()->removeAll();
+
+                $selectFields = ['uid', 'pid'];
+                if (isset($GLOBALS['TCA'][$rec['ref_table']]['ctrl']['delete'])) {
+                    $selectFields[] = $GLOBALS['TCA'][$rec['ref_table']]['ctrl']['delete'];
+                }
+
+                $existingRecords[$idx] = $queryBuilder
+                    ->select(...$selectFields)
+                    ->from($rec['ref_table'])
+                    ->where(
+                        $queryBuilder->expr()->eq(
+                            'uid',
+                            $queryBuilder->createNamedParameter($rec['ref_uid'], \PDO::PARAM_INT)
+                        )
+                    )
+                    ->execute()
+                    ->fetch();
             }
             // Compile info string for location of reference:
             $infoString = $this->formatReferenceIndexEntryToString($rec);
diff --git a/typo3/sysext/lowlevel/Classes/Command/WorkspaceVersionRecordsCommand.php b/typo3/sysext/lowlevel/Classes/Command/WorkspaceVersionRecordsCommand.php
index 350d54bc165b..10b75259dfe5 100644
--- a/typo3/sysext/lowlevel/Classes/Command/WorkspaceVersionRecordsCommand.php
+++ b/typo3/sysext/lowlevel/Classes/Command/WorkspaceVersionRecordsCommand.php
@@ -271,7 +271,21 @@ class WorkspaceVersionRecordsCommand extends Command
      */
     protected function traversePageTreeForVersionedRecords(int $rootID, int $depth, bool $isInsideVersionedPage = false, bool $rootIsVersion = false)
     {
-        $pageRecord = BackendUtility::getRecordRaw('pages', 'uid=' . $rootID, 'deleted,title,t3ver_count,t3ver_wsid');
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $pageRecord = $queryBuilder
+            ->select(
+                'deleted',
+                'title',
+                't3ver_count',
+                't3ver_wsid'
+            )
+            ->from('pages')
+            ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($rootID, \PDO::PARAM_INT)))
+            ->execute()
+            ->fetch();
+
         // If rootIsVersion is set it means that the input rootID is that of a version of a page. See below where the recursive call is made.
         if ($rootIsVersion) {
             $workspaceId = (int)$pageRecord['t3ver_wsid'];
diff --git a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
index 33a9b5d643f1..ebd219b2c16d 100644
--- a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
+++ b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
@@ -22,6 +22,7 @@ use TYPO3\CMS\Backend\Template\ModuleTemplate;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
@@ -776,9 +777,24 @@ class DatabaseRecordList extends AbstractDatabaseRecordList
                                     // $lRow isn't always what we want - if record was moved we've to work with the
                                     // placeholder records otherwise the list is messed up a bit
                                     if ($row['_MOVE_PLH_uid'] && $row['_MOVE_PLH_pid']) {
-                                        $where = 't3ver_move_id="' . (int)$lRow['uid'] . '" AND pid="' . $row['_MOVE_PLH_pid']
-                                            . '" AND t3ver_wsid=' . $row['t3ver_wsid'] . BackendUtility::deleteClause($table);
-                                        $tmpRow = BackendUtility::getRecordRaw($table, $where, $selFieldList);
+                                        $where = 't3ver_move_id="' . (int)$lRow['uid']
+                                            . '" AND pid="' . (int)$row['_MOVE_PLH_pid']
+                                            . '" AND t3ver_wsid=' . (int)$row['t3ver_wsid'];
+
+                                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                                            ->getQueryBuilderForTable($table);
+                                        $queryBuilder->getRestrictions()
+                                            ->removeAll()
+                                            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                                        ;
+
+                                        $tmpRow = $queryBuilder
+                                            ->select(...$selFieldList)
+                                            ->from($table)
+                                            ->where(QueryHelper::stripLogicalOperatorPrefix($where))
+                                            ->execute()
+                                            ->fetch();
+
                                         $lRow = is_array($tmpRow) ? $tmpRow : $lRow;
                                     }
                                     // In offline workspace, look for alternative record:
-- 
GitLab