From e2df6bcf01a0cb501cfb384825191be3a6ce6cd7 Mon Sep 17 00:00:00 2001
From: Morton Jonuschat <m.jonuschat@mojocode.de>
Date: Tue, 9 Aug 2016 15:15:03 +0200
Subject: [PATCH] [TASK] Doctrine: Migrate ContentObjectRenderer

Change-Id: I32ca8fab70cc8bc954aa4af189cab20f70047651
Resolves: #77455
Releases: master
Reviewed-on: https://review.typo3.org/49432
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Bamboo TYPO3com <info@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
---
 .../ContentObject/ContentObjectRenderer.php   | 271 +++++++++++-------
 .../ContentObjectRendererTest.php             |  97 -------
 2 files changed, 170 insertions(+), 198 deletions(-)

diff --git a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
index 2c6517b82e3c..9d35717f8b67 100644
--- a/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
+++ b/typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
@@ -14,9 +14,13 @@ namespace TYPO3\CMS\Frontend\ContentObject;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Doctrine\DBAL\DBALException;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Charset\CharsetConverter;
 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\Database\Query\Restriction\FrontendRestrictionContainer;
 use TYPO3\CMS\Core\FrontendEditing\FrontendEditingController;
 use TYPO3\CMS\Core\Html\HtmlParser;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
@@ -7130,7 +7134,6 @@ class ContentObjectRenderer
         $requestHash = '';
 
         // First level, check id (second level, this is done BEFORE the recursive call)
-        $db = $this->getDatabaseConnection();
         $tsfe = $this->getTypoScriptFrontendController();
         if (!$recursionLevel) {
             // Check tree list cache
@@ -7146,11 +7149,21 @@ class ContentObjectRenderer
                 $tsfe->gr_list
             ];
             $requestHash = md5(serialize($parameters));
-            $cacheEntry = $db->exec_SELECTgetSingleRow(
-                'treelist',
-                'cache_treelist',
-                'md5hash = \'' . $requestHash . '\' AND ( expires > ' . (int)$GLOBALS['EXEC_TIME'] . ' OR expires = 0 )'
-            );
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable('cache_treelist');
+            $cacheEntry = $queryBuilder->select('treelist')
+                ->from('cache_treelist')
+                ->where(
+                    $queryBuilder->expr()->eq('md5hash', $queryBuilder->createNamedParameter($requestHash)),
+                    $queryBuilder->expr()->orX(
+                        $queryBuilder->expr()->gt('expires', (int)$GLOBALS['EXEC_TIME']),
+                        $queryBuilder->expr()->eq('expires', 0)
+                    )
+                )
+                ->setMaxResults(1)
+                ->execute()
+                ->fetch();
+
             if (is_array($cacheEntry)) {
                 // Cache hit
                 return $cacheEntry['treelist'];
@@ -7181,79 +7194,93 @@ class ContentObjectRenderer
         }
         // Select sublevel:
         if ($depth > 0) {
-            $rows = $db->exec_SELECTgetRows(
-                $allFields,
-                'pages',
-                'pid = ' . (int)$id . ' AND deleted = 0 ' . $moreWhereClauses,
-                '',
-                'sorting'
-            );
-            if (is_array($rows)) {
-                foreach ($rows as $row) {
-                    /** @var VersionState $versionState */
-                    $versionState = VersionState::cast($row['t3ver_state']);
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+            $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
+                ->from('pages')
+                ->where($queryBuilder->expr()->eq('pid', (int)$id))
+                ->orderBy('sorting');
+
+            if (!empty($moreWhereClauses)) {
+                $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
+            }
+
+            $result = $queryBuilder->execute();
+            while ($row = $result->fetch()) {
+                /** @var VersionState $versionState */
+                $versionState = VersionState::cast($row['t3ver_state']);
+                $tsfe->sys_page->versionOL('pages', $row);
+                if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
+                    || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
+                    || $versionState->indicatesPlaceholder()
+                ) {
+                    // Doing this after the overlay to make sure changes
+                    // in the overlay are respected.
+                    // However, we do not process pages below of and
+                    // including of type recycler and BE user section
+                    continue;
+                }
+                // Find mount point if any:
+                $next_id = $row['uid'];
+                $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row);
+                // Overlay mode:
+                if (is_array($mount_info) && $mount_info['overlay']) {
+                    $next_id = $mount_info['mount_pid'];
+                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                        ->getQueryBuilderForTable('pages');
+                    $queryBuilder->getRestrictions()
+                        ->removeAll()
+                        ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+                    $queryBuilder->select(...GeneralUtility::trimExplode(',', $allFields, true))
+                        ->from('pages')
+                        ->where($queryBuilder->expr()->eq('uid', (int)$next_id))
+                        ->orderBy('sorting')
+                        ->setMaxResults(1);
+
+                    if (!empty($moreWhereClauses)) {
+                        $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($moreWhereClauses));
+                    }
+
+                    $row = $queryBuilder->execute()->fetch();
                     $tsfe->sys_page->versionOL('pages', $row);
                     if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
                         || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
                         || $versionState->indicatesPlaceholder()
                     ) {
-                        // Doing this after the overlay to make sure changes
-                        // in the overlay are respected.
-                        // However, we do not process pages below of and
-                        // including of type recycler and BE user section
+                        // Doing this after the overlay to make sure
+                        // changes in the overlay are respected.
+                        // see above
                         continue;
                     }
-                    // Find mount point if any:
-                    $next_id = $row['uid'];
-                    $mount_info = $tsfe->sys_page->getMountPointInfo($next_id, $row);
-                    // Overlay mode:
-                    if (is_array($mount_info) && $mount_info['overlay']) {
-                        $next_id = $mount_info['mount_pid'];
-                        $row = $db->exec_SELECTgetSingleRow(
-                            $allFields,
-                            'pages',
-                            'uid = ' . (int)$next_id . ' AND deleted = 0 ' . $moreWhereClauses,
-                            '',
-                            'sorting'
-                        );
-                        $tsfe->sys_page->versionOL('pages', $row);
-                        if ((int)$row['doktype'] === PageRepository::DOKTYPE_RECYCLER
-                            || (int)$row['doktype'] === PageRepository::DOKTYPE_BE_USER_SECTION
-                            || $versionState->indicatesPlaceholder()
-                        ) {
-                            // Doing this after the overlay to make sure
-                            // changes in the overlay are respected.
-                            // see above
-                            continue;
+                }
+                // Add record:
+                if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) {
+                    // Add ID to list:
+                    if ($begin <= 0) {
+                        if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) {
+                            $theList[] = $next_id;
                         }
                     }
-                    // Add record:
-                    if ($dontCheckEnableFields || $tsfe->checkPagerecordForIncludeSection($row)) {
-                        // Add ID to list:
-                        if ($begin <= 0) {
-                            if ($dontCheckEnableFields || $tsfe->checkEnableFields($row)) {
-                                $theList[] = $next_id;
-                            }
+                    // Next level:
+                    if ($depth > 1 && !$row['php_tree_stop']) {
+                        // Normal mode:
+                        if (is_array($mount_info) && !$mount_info['overlay']) {
+                            $next_id = $mount_info['mount_pid'];
                         }
-                        // Next level:
-                        if ($depth > 1 && !$row['php_tree_stop']) {
-                            // Normal mode:
-                            if (is_array($mount_info) && !$mount_info['overlay']) {
-                                $next_id = $mount_info['mount_pid'];
-                            }
-                            // Call recursively, if the id is not in prevID_array:
-                            if (!in_array($next_id, $prevId_array)) {
-                                $theList = array_merge(
-                                    GeneralUtility::intExplode(
-                                        ',',
-                                        $this->getTreeList($next_id, $depth - 1, $begin - 1,
-                                            $dontCheckEnableFields, $addSelectFields, $moreWhereClauses,
-                                            $prevId_array, $recursionLevel + 1),
-                                        true
-                                    ),
-                                    $theList
-                                );
-                            }
+                        // Call recursively, if the id is not in prevID_array:
+                        if (!in_array($next_id, $prevId_array)) {
+                            $theList = array_merge(
+                                GeneralUtility::intExplode(
+                                    ',',
+                                    $this->getTreeList($next_id, $depth - 1, $begin - 1,
+                                        $dontCheckEnableFields, $addSelectFields, $moreWhereClauses,
+                                        $prevId_array, $recursionLevel + 1),
+                                    true
+                                ),
+                                $theList
+                            );
                         }
                     }
                 }
@@ -7286,34 +7313,53 @@ class ContentObjectRenderer
      * Generates a search where clause based on the input search words (AND operation - all search words must be found in record.)
      * Example: The $sw is "content management, system" (from an input form) and the $searchFieldList is "bodytext,header" then the output will be ' AND (bodytext LIKE "%content%" OR header LIKE "%content%") AND (bodytext LIKE "%management%" OR header LIKE "%management%") AND (bodytext LIKE "%system%" OR header LIKE "%system%")'
      *
-     * @param string $sw The search words. These will be separated by space and comma.
+     * @param string $searchWords The search words. These will be separated by space and comma.
      * @param string $searchFieldList The fields to search in
      * @param string $searchTable The table name you search in (recommended for DBAL compliance. Will be prepended field names as well)
      * @return string The WHERE clause.
      */
-    public function searchWhere($sw, $searchFieldList, $searchTable = '')
+    public function searchWhere($searchWords, $searchFieldList, $searchTable = '')
     {
+        if (!$searchWords) {
+            return ' AND 1=1';
+        }
+
+        if (empty($searchTable)) {
+            GeneralUtility::deprecationLog(
+                'Parameter 3 of ContentObjectRenderer::searchWhere() is required can not be omitted anymore. Using Default connection!'
+            );
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
+                ->createQueryBuilder();
+        } else {
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getQueryBuilderForTable($searchTable);
+        }
+
         $prefixTableName = $searchTable ? $searchTable . '.' : '';
-        $where = '';
-        if ($sw) {
-            $searchFields = explode(',', $searchFieldList);
-            $kw = preg_split('/[ ,]/', $sw);
-            $db = $this->getDatabaseConnection();
-            foreach ($kw as $val) {
-                $val = trim($val);
-                $where_p = [];
-                if (strlen($val) >= 2) {
-                    $val = $db->escapeStrForLike($db->quoteStr($val, $searchTable), $searchTable);
-                    foreach ($searchFields as $field) {
-                        $where_p[] = $prefixTableName . $field . ' LIKE \'%' . $val . '%\'';
-                    }
-                }
-                if (!empty($where_p)) {
-                    $where .= ' AND (' . implode(' OR ', $where_p) . ')';
-                }
+
+        $where = $queryBuilder->expr()->andX();
+        $searchFields = explode(',', $searchFieldList);
+        $searchWords = preg_split('/[ ,]/', $searchWords);
+        foreach ($searchWords as $searchWord) {
+            $searchWord = trim($searchWord);
+            if (strlen($searchWord) < 3) {
+                continue;
+            }
+            $searchWordConstraint = $queryBuilder->expr()->orX();
+            $searchWord = $queryBuilder->escapeLikeWildcards($searchWord);
+            foreach ($searchFields as $field) {
+                $searchWordConstraint->add(
+                    $queryBuilder->expr()->like($prefixTableName . $field, $queryBuilder->quote('%' . $searchWord . '%'))
+                );
+            }
+
+            if ($searchWordConstraint->count()) {
+                $where->add($searchWordConstraint);
             }
         }
-        return $where;
+
+        return ' AND ' . (string)$where;
     }
 
     /**
@@ -7723,16 +7769,26 @@ class ContentObjectRenderer
             return [];
         }
         $outArr = [];
-        $db = $this->getDatabaseConnection();
-        $res = $db->exec_SELECTquery('uid', 'pages', 'uid IN (' . implode(',', $listArr) . ')' . $this->enableFields('pages') . ' AND doktype NOT IN (' . $this->checkPid_badDoktypeList . ')');
-        if ($error = $db->sql_error()) {
-            $this->getTimeTracker()->setTSlogMessage($error . ': ' . $db->debug_lastBuiltQuery, 3);
-        } else {
-            while ($row = $db->sql_fetch_assoc($res)) {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
+        $queryBuilder->select('uid')
+            ->from('pages')
+            ->where(
+                $queryBuilder->expr()->in('uid', array_map('intval', $listArr)),
+                $queryBuilder->expr()->notIn(
+                    'doktype',
+                    GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true)
+                )
+            );
+        try {
+            $result = $queryBuilder->execute();
+            while ($row = $result->fetch()) {
                 $outArr[] = $row['uid'];
             }
+        } catch (DBALException $e) {
+            $this->getTimeTracker()->setTSlogMessage($e->getMessage() . ': ' . $queryBuilder->getSQL(), 3);
         }
-        $db->sql_free_result($res);
+
         return $outArr;
     }
 
@@ -7748,7 +7804,20 @@ class ContentObjectRenderer
     {
         $uid = (int)$uid;
         if (!isset($this->checkPid_cache[$uid])) {
-            $count = $this->getDatabaseConnection()->exec_SELECTcountRows('uid', 'pages', 'uid=' . $uid . $this->enableFields('pages') . ' AND doktype NOT IN (' . $this->checkPid_badDoktypeList . ')');
+            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+            $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
+            $count = $queryBuilder->count('*')
+                ->from('pages')
+                ->where(
+                    $queryBuilder->expr()->eq('uid', $uid),
+                    $queryBuilder->expr()->notIn(
+                        'doktype',
+                        GeneralUtility::intExplode(',', $this->checkPid_badDoktypeList, true)
+                    )
+                )
+                ->execute()
+                ->fetchColumn(0);
+
             $this->checkPid_cache[$uid] = (bool)$count;
         }
         return $this->checkPid_cache[$uid];
@@ -7770,7 +7839,7 @@ class ContentObjectRenderer
             return [];
         }
         // Parse markers and prepare their values
-        $db = $this->getDatabaseConnection();
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
         $markerValues = [];
         foreach ($conf['markers.'] as $dottedMarker => $dummy) {
             $marker = rtrim($dottedMarker, '.');
@@ -7814,17 +7883,17 @@ class ContentObjectRenderer
                             } elseif (preg_match('/^\\"([^\\"]*)\\"$/', $listValue, $matches)) {
                                 $listValue = $matches[1];
                             }
-                            $tempArray[] = $db->fullQuoteStr($listValue, $table);
+                            $tempArray[] = $connection->quote($listValue);
                         }
                     }
                     $markerValues[$marker] = implode(',', $tempArray);
                 } else {
                     // Handle remaining values as string
-                    $markerValues[$marker] = $db->fullQuoteStr($tempValue, $table);
+                    $markerValues[$marker] = $connection->quote($tempValue);
                 }
             } else {
                 // Handle remaining values as string
-                $markerValues[$marker] = $db->fullQuoteStr($tempValue, $table);
+                $markerValues[$marker] = $connection->quote($tempValue);
             }
         }
         return $markerValues;
diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
index c7e39d4608ab..2687213140ff 100644
--- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
@@ -14,14 +14,11 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Prophecy\Argument;
 use Psr\Log\LoggerInterface;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface as CacheFrontendInterface;
 use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Core\ApplicationContext;
-use TYPO3\CMS\Core\Database\Connection;
-use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
 use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Resource\File;
@@ -1611,100 +1608,6 @@ class ContentObjectRendererTest extends UnitTestCase
         $this->assertEquals($expectedResult, $cleanedResult);
     }
 
-    /**
-     * @test
-     */
-    public function getTreeListReturnsChildPageUids()
-    {
-        $GLOBALS['TYPO3_DB']->expects($this->any())->method('exec_SELECTgetSingleRow')->with('treelist')->will($this->returnValue(null));
-        $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
-        $connectionProphecy = $this->prophesize(Connection::class);
-        $connectionPoolProphecy->getConnectionForTable('cache_treelist')->willReturn($connectionProphecy->reveal());
-        $connectionProphecy->insert(Argument::cetera())->shouldBeCalled();
-        $GLOBALS['TSFE']->sys_page
-            ->expects($this->any())
-            ->method('getRawRecord')
-            ->will(
-                $this->onConsecutiveCalls(
-                    array('uid' => 17),
-                    array('uid' => 321),
-                    array('uid' => 719),
-                    array('uid' => 42)
-                )
-            );
-
-        $GLOBALS['TSFE']->sys_page->expects($this->any())->method('getMountPointInfo')->will($this->returnValue(null));
-        $GLOBALS['TYPO3_DB']
-            ->expects($this->any())
-            ->method('exec_SELECTgetRows')
-            ->will(
-                $this->onConsecutiveCalls(
-                    array(
-                        array('uid' => 321)
-                    ),
-                    array(
-                        array('uid' => 719)
-                    ),
-                    array(
-                        array('uid' => 42)
-                    )
-                )
-            );
-        // 17 = pageId, 5 = recursionLevel, 0 = begin (entry to recursion, internal), TRUE = do not check enable fields
-        // 17 is positive, we expect 17 NOT to be included in result
-        $result = $this->subject->getTreeList(17, 5, 0, true);
-        $expectedResult = '42,719,321';
-        $this->assertEquals($expectedResult, $result);
-    }
-
-    /**
-     * @test
-     */
-    public function getTreeListReturnsChildPageUidsAndOriginalPidForNegativeValue()
-    {
-        $GLOBALS['TYPO3_DB']->expects($this->any())->method('exec_SELECTgetSingleRow')->with('treelist')->will($this->returnValue(null));
-        $connectionPoolProphecy = $this->prophesize(ConnectionPool::class);
-        GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphecy->reveal());
-        $connectionProphecy = $this->prophesize(Connection::class);
-        $connectionPoolProphecy->getConnectionForTable('cache_treelist')->willReturn($connectionProphecy->reveal());
-        $connectionProphecy->insert(Argument::cetera())->shouldBeCalled();
-        $GLOBALS['TSFE']->sys_page
-            ->expects($this->any())
-            ->method('getRawRecord')
-            ->will(
-                $this->onConsecutiveCalls(
-                    array('uid' => 17),
-                    array('uid' => 321),
-                    array('uid' => 719),
-                    array('uid' => 42)
-                )
-            );
-
-        $GLOBALS['TSFE']->sys_page->expects($this->any())->method('getMountPointInfo')->will($this->returnValue(null));
-        $GLOBALS['TYPO3_DB']
-            ->expects($this->any())
-            ->method('exec_SELECTgetRows')
-            ->will(
-                $this->onConsecutiveCalls(
-                    array(
-                        array('uid' => 321)
-                    ),
-                    array(
-                        array('uid' => 719)
-                    ),
-                    array(
-                        array('uid' => 42)
-                    )
-                )
-            );
-        // 17 = pageId, 5 = recursionLevel, 0 = begin (entry to recursion, internal), TRUE = do not check enable fields
-        // 17 is negative, we expect 17 to be included in result
-        $result = $this->subject->getTreeList(-17, 5, 0, true);
-        $expectedResult = '42,719,321,17';
-        $this->assertEquals($expectedResult, $result);
-    }
-
     /**
      * @test
      */
-- 
GitLab