[!!!][TASK] Doctrine: Simplify restriction handling 49/48049/29
authorHelmut Hummel <info@helhum.io>
Fri, 3 Jun 2016 05:46:45 +0000 (07:46 +0200)
committerHelmut Hummel <helmut.hummel@typo3.org>
Fri, 3 Jun 2016 10:47:43 +0000 (12:47 +0200)
To make the set of restrictions that are in effect when creating queries
with the QueryBuilder becomes more understandable,
we need to remove the QueryContext and the magic applied to that and replace it
with the following behavior:

delete, hidden, starttime, endtime restrictions are always enabled
and need to be removed if needed.
Other restrictions need to be provided manually depending on the desired result.

Resolves: #76167
Resolves: #76264
Releases: master
Change-Id: Iaf0cb08475ed44966838c3fbdd5756d3ba6ebcc1
Reviewed-on: https://review.typo3.org/48049
Tested-by: Helmut Hummel <helmut.hummel@typo3.org>
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Jan Helke <typo3@helke.de>
Tested-by: Jan Helke <typo3@helke.de>
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>
Reviewed-by: Nicole Cordes <typo3@cordes.co>
Tested-by: Nicole Cordes <typo3@cordes.co>
Reviewed-by: Helmut Hummel <helmut.hummel@typo3.org>
57 files changed:
typo3/sysext/backend/Classes/Backend/Avatar/DefaultAvatarProvider.php
typo3/sysext/backend/Classes/Clipboard/Clipboard.php
typo3/sysext/backend/Classes/Configuration/TranslationConfigurationProvider.php
typo3/sysext/backend/Classes/History/RecordHistory.php
typo3/sysext/backend/Classes/Tree/View/AbstractTreeView.php
typo3/sysext/backend/Classes/Tree/View/PagePositionMap.php
typo3/sysext/belog/Classes/Controller/SystemInformationController.php
typo3/sysext/beuser/Classes/Domain/Repository/BackendUserSessionRepository.php
typo3/sysext/core/Classes/Authentication/AbstractUserAuthentication.php
typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
typo3/sysext/core/Classes/Database/Connection.php
typo3/sysext/core/Classes/Database/Query/Expression/CompositeExpression.php
typo3/sysext/core/Classes/Database/Query/QueryBuilder.php
typo3/sysext/core/Classes/Database/Query/QueryContext.php [deleted file]
typo3/sysext/core/Classes/Database/Query/QueryContextType.php [deleted file]
typo3/sysext/core/Classes/Database/Query/QueryRestrictionBuilder.php [deleted file]
typo3/sysext/core/Classes/Database/Query/Restriction/AbstractRestrictionContainer.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/BackendWorkspaceRestriction.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/DefaultRestrictionContainer.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/DeletedRestriction.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/EndTimeRestriction.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/FrontendGroupRestriction.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/FrontendRestrictionContainer.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/FrontendWorkspaceRestriction.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/HiddenRestriction.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionContainerInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/RootLevelRestriction.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/StartTimeRestriction.php [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/8.1/Feature-75454-DoctrineDBALForDatabaseConnections.rst
typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
typo3/sysext/core/Tests/Unit/Database/Query/QueryContextTest.php [deleted file]
typo3/sysext/core/Tests/Unit/Database/Query/QueryRestrictionBuilderTest.php [deleted file]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/AbstractRestrictionTestCase.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/BackendWorkspaceRestrictionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DefaultRestrictionContainerTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DeletedRestrictionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/EndTimeRestrictionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendGroupRestrictionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendRestrictionContainerTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendWorkspaceRestrictionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/HiddenRestrictionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/RootLevelRestrictionTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/StartTimeRestrictionTest.php [new file with mode: 0644]
typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php
typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php
typo3/sysext/filelist/Classes/FileFacade.php
typo3/sysext/filelist/Classes/FileList.php
typo3/sysext/lowlevel/Classes/MissingRelationsCommand.php
typo3/sysext/recycler/Classes/Domain/Model/DeletedRecords.php
typo3/sysext/recycler/Classes/Domain/Model/Tables.php
typo3/sysext/recycler/Classes/Task/CleanerTask.php
typo3/sysext/recycler/Classes/Utility/RecyclerUtility.php
typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php
typo3/sysext/setup/Classes/Controller/SetupModuleController.php
typo3/sysext/sys_note/Classes/Core/Bootstrap.php

index a7551b7..b5ff3d1 100644 (file)
@@ -13,9 +13,7 @@ namespace TYPO3\CMS\Backend\Backend\Avatar;
  *
  * The TYPO3 project - inspiring people to share!
  */
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
@@ -67,15 +65,16 @@ class DefaultAvatarProvider implements AvatarProviderInterface
      */
     protected function getAvatarFileUid($beUserId)
     {
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
         $file = $queryBuilder
             ->select('uid_local')
             ->from('sys_file_reference')
-            ->where($queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')))
-            ->andWhere($queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')))
-            ->andWhere($queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('uid_foreign', (int)$beUserId))
+            ->where(
+                $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')),
+                $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')),
+                $queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')),
+                $queryBuilder->expr()->eq('uid_foreign', (int)$beUserId)
+            )
             ->execute()
             ->fetchColumn();
 
index 30848c5..8a907c0 100644 (file)
@@ -16,7 +16,7 @@ namespace TYPO3\CMS\Backend\Clipboard;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider;
@@ -496,23 +496,24 @@ class Clipboard
     {
         $lines = array();
         $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
-        if ($table != 'pages' && BackendUtility::isTableLocalizable($table) && !$tcaCtrl['transOrigPointerTable']) {
+        if ($table !== 'pages' && BackendUtility::isTableLocalizable($table) && !$tcaCtrl['transOrigPointerTable']) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-            $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
             $queryBuilder
                 ->select('*')
                 ->from($table)
-                ->where($queryBuilder->expr()->eq($tcaCtrl['transOrigPointerField'], (int)$parentRec['uid']))
-                ->andWhere($queryBuilder->expr()->neq($tcaCtrl['languageField'], 0));
-            if (isset($tcaCtrl['delete']) && $tcaCtrl['delete']) {
-                $queryBuilder->andWhere($queryBuilder->expr()->eq($tcaCtrl['delete'], 0));
-            }
+                ->where(
+                    $queryBuilder->expr()->eq($tcaCtrl['transOrigPointerField'], (int)$parentRec['uid']),
+                    $queryBuilder->expr()->neq($tcaCtrl['languageField'], 0)
+                );
+
             if (isset($tcaCtrl['versioningWS']) && $tcaCtrl['versioningWS']) {
-                $queryBuilder->andWhere($queryBuilder->expr()->eq('t3ver_wsid', $parentRec['t3ver_wsid']));
+                $queryBuilder->andWhere($queryBuilder->expr()->eq('t3ver_wsid', (int)$parentRec['t3ver_wsid']));
             }
-            $rows = $queryBuilder
-                ->execute()
-                ->fetchAll();
+            $rows = $queryBuilder->execute()->fetchAll();
             if (is_array($rows)) {
                 $modeData = '';
                 if ($pad == 'normal') {
@@ -1120,5 +1121,4 @@ class Clipboard
     {
         return $GLOBALS['BE_USER'];
     }
-
 }
index ece68f5..c621fe6 100644 (file)
@@ -16,7 +16,8 @@ namespace TYPO3\CMS\Backend\Configuration;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Lang\LanguageService;
@@ -123,18 +124,21 @@ class TranslationConfigurationProvider
             $selFieldList = 'uid,' . $GLOBALS['TCA'][$translationTable]['ctrl']['languageField'];
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($translationTable);
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
         $queryBuilder
             ->select(...GeneralUtility::trimExplode(',', $selFieldList))
             ->from($translationTable)
-            ->where($queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['transOrigPointerField'], (int)$uid))
-            ->andWhere($queryBuilder->expr()->eq('pid', (int)($table === 'pages' ? $row['uid'] : $row['pid'])));
+            ->where(
+                $queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['transOrigPointerField'], (int)$uid),
+                $queryBuilder->expr()->eq('pid', (int)($table === 'pages' ? $row['uid'] : $row['pid']))
+            );
         if (!$languageUid) {
-            $queryBuilder
-                ->andWhere($queryBuilder->expr()->gt($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], 0));
+            $queryBuilder->andWhere($queryBuilder->expr()->gt($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], 0));
         } else {
-            $queryBuilder
-                ->andWhere($queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], (int)$languageUid));
+            $queryBuilder->andWhere($queryBuilder->expr()->eq($GLOBALS['TCA'][$translationTable]['ctrl']['languageField'], (int)$languageUid));
         }
         $translationRecords = $queryBuilder
             ->execute()
index f54c763..28ced0a 100644 (file)
@@ -16,8 +16,6 @@ namespace TYPO3\CMS\Backend\History;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
@@ -171,12 +169,11 @@ class RecordHistory
     public function toggleHighlight($uid)
     {
         $uid = (int)$uid;
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
         $row = $queryBuilder
             ->select('snapshot')
             ->from('sys_history')
-            ->where($queryBuilder->expr()->eq('uid', $uid))
+            ->where($queryBuilder->expr()->eq('uid', (int)$uid))
             ->execute()
             ->fetch();
 
@@ -185,7 +182,7 @@ class RecordHistory
             $queryBuilder
                 ->update('sys_history')
                 ->set('snapshot', (int)!$row['snapshot'])
-                ->where($queryBuilder->expr()->eq('uid', $uid))
+                ->where($queryBuilder->expr()->eq('uid', (int)$uid))
                 ->execute();
         }
     }
@@ -661,17 +658,15 @@ class RecordHistory
         if ($elParts[0] == 'pages' && $this->showSubElements && $this->hasPageAccess('pages', $elParts[1])) {
             foreach ($GLOBALS['TCA'] as $tablename => $value) {
                 // check if there are records on the page
-                /** @var QueryBuilder $queryBuilder */
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tablename);
-                $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+                $queryBuilder->getRestrictions()->removeAll();
 
                 $rows = $queryBuilder
                     ->select('uid')
                     ->from($tablename)
                     ->where($queryBuilder->expr()->eq('pid', (int)$elParts[1]))
-                    ->execute()
-                    ->fetchAll();
-                if (empty($rows)) {
+                    ->execute();
+                if ($rows->rowCount() === 0) {
                     continue;
                 }
                 foreach ($rows as $row) {
@@ -709,7 +704,6 @@ class RecordHistory
         // If table is found in $GLOBALS['TCA']:
         $uid = $this->resolveElement($table, $uid);
         // Selecting the $this->maxSteps most recent states:
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
         $rows = $queryBuilder
             ->select('sys_history.*', 'sys_log.userid', 'sys_log.log_data')
@@ -719,10 +713,10 @@ class RecordHistory
                 $queryBuilder->expr()->eq(
                     'sys_history.sys_log_uid',
                     $queryBuilder->quoteIdentifier('sys_log.uid')
-                )
+                ),
+                $queryBuilder->expr()->eq('sys_history.tablename', $queryBuilder->createNamedParameter($table)),
+                $queryBuilder->expr()->eq('sys_history.recuid', (int)$uid)
             )
-            ->andWhere($queryBuilder->expr()->eq('sys_history.tablename', $queryBuilder->createNamedParameter($table)))
-            ->andWhere($queryBuilder->expr()->eq('sys_history.recuid', (int)$uid))
             ->orderBy('sys_log.uid', 'DESC')
             ->setMaxResults((int)$this->maxSteps)
             ->execute()
@@ -759,28 +753,28 @@ class RecordHistory
         // SELECT INSERTS/DELETES
         if ($this->showInsertDelete) {
             // Select most recent inserts and deletes // WITHOUT snapshots
-            /** @var QueryBuilder $queryBuilder */
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
-            $rows = $queryBuilder
+            $result = $queryBuilder
                 ->select('uid', 'userid', 'action', 'tstamp', 'log_data')
                 ->from('sys_log')
-                ->where($queryBuilder->expr()->eq('type', 1))
-                ->andWhere($queryBuilder->expr()->orX(
-                    $queryBuilder->expr()->eq('action', 1),
-                    $queryBuilder->expr()->eq('action', 3)
-                ))
-                ->andWhere($queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($table)))
-                ->andWhere($queryBuilder->expr()->eq('recuid', (int)$uid))
+                ->where(
+                    $queryBuilder->expr()->eq('type', 1),
+                    $queryBuilder->expr()->orX(
+                        $queryBuilder->expr()->eq('action', 1),
+                        $queryBuilder->expr()->eq('action', 3)
+                    ),
+                    $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter($table)),
+                    $queryBuilder->expr()->eq('recuid', (int)$uid)
+                )
                 ->orderBy('uid', 'DESC')
                 ->setMaxResults((int)$this->maxSteps)
-                ->execute()
-                ->fetchAll();
+                ->execute();
 
             // If none are found, nothing more to do
-            if (empty($rows)) {
+            if ($result->rowCount() === 0) {
                 return $changeLog;
             }
-            foreach ($rows as $row) {
+            foreach ($result as $row) {
                 if ($this->lastSyslogId && $row['uid'] < $this->lastSyslogId) {
                     continue;
                 }
@@ -914,7 +908,6 @@ class RecordHistory
         if (empty($shUid)) {
             return;
         }
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_history');
         $record = $queryBuilder
             ->select('*')
index 200e640..9aae143 100644 (file)
@@ -20,6 +20,8 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 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\Utility\GeneralUtility;
@@ -834,11 +836,17 @@ abstract class AbstractTreeView
             return $this->getDataCount($res);
         } else {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
             $count = $queryBuilder
                 ->count('uid')
                 ->from($this->table)
-                ->where($queryBuilder->expr()->eq($this->parentField, $queryBuilder->createNamedParameter($uid)))
-                ->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->clause))
+                ->where(
+                    $queryBuilder->expr()->eq($this->parentField, (int)$uid),
+                    QueryHelper::stripLogicalOperatorPrefix($this->clause)
+                )
                 ->execute()
                 ->fetchColumn();
 
@@ -894,11 +902,17 @@ abstract class AbstractTreeView
             return $parentId;
         } else {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
             $queryBuilder
                 ->select(...$this->fieldArray)
                 ->from($this->table)
-                ->where($queryBuilder->expr()->eq($this->parentField, $queryBuilder->createNamedParameter($parentId)))
-                ->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->clause));
+                ->where(
+                    $queryBuilder->expr()->eq($this->parentField, (int)$parentId),
+                    QueryHelper::stripLogicalOperatorPrefix($this->clause)
+                );
 
             foreach (QueryHelper::parseOrderBy($this->orderByFields) as $orderPair) {
                 list($fieldName, $order) = $orderPair;
index 1d2b28c..758fded 100644 (file)
@@ -17,7 +17,10 @@ namespace TYPO3\CMS\Backend\Tree\View;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
@@ -341,15 +344,20 @@ class PagePositionMap
         $lines = array();
         foreach ($colPosArray as $kk => $vv) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
-            $queryBuilder->getQueryContext()
-                ->setContext(QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS)
-                ->setIgnoreEnableFields($showHidden);
-
+            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+            if ($showHidden) {
+                $queryBuilder->getRestrictions()
+                    ->removeByType(HiddenRestriction::class)
+                    ->removeByType(StartTimeRestriction::class)
+                    ->removeByType(EndTimeRestriction::class);
+            }
             $queryBuilder
                 ->select('*')
                 ->from('tt_content')
-                ->where($queryBuilder->expr()->eq('pid', (int)$pid))
-                ->andWhere($queryBuilder->expr()->eq('colPos', (int)$vv))
+                ->where(
+                    $queryBuilder->expr()->eq('pid', (int)$pid),
+                    $queryBuilder->expr()->eq('colPos', (int)$vv)
+                )
                 ->orderBy('sorting');
 
             if ((string)$this->cur_sys_language !== '') {
index c018a24..6c9b498 100644 (file)
@@ -19,7 +19,6 @@ use TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Belog\Domain\Model\Constraint;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
 
@@ -51,12 +50,13 @@ class SystemInformationController extends AbstractController
 
         $this->setStartAndEndTimeFromTimeSelector($constraint);
         // we can't use the extbase repository here as the required TypoScript may not be parsed yet
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_log');
         $count = $queryBuilder->count('error')
             ->from('sys_log')
-            ->where($queryBuilder->expr()->gte('tstamp', $timestamp))
-            ->andWhere($queryBuilder->expr()->in('error', [-1, 1, 2]))
+            ->where(
+                $queryBuilder->expr()->gte('tstamp', $timestamp),
+                $queryBuilder->expr()->in('error', [-1, 1, 2])
+            )
             ->execute()
             ->fetchColumn(0);
 
index f8a694f..f676c60 100644 (file)
@@ -72,9 +72,11 @@ class BackendUserSessionRepository extends Repository
             ->update('be_sessions')
             ->set('ses_userid', $authentication->user['ses_backuserid'])
             ->set('ses_backuserid', 0)
-            ->where($queryBuilder->expr()->eq('ses_id', $queryBuilder->createNamedParameter($GLOBALS['BE_USER']->id)))
-            ->andWhere($queryBuilder->expr()->eq('ses_name', $queryBuilder->createNamedParameter(BackendUserAuthentication::getCookieName())))
-            ->andWhere($queryBuilder->expr()->eq('ses_userid', (int)$GLOBALS['BE_USER']->user['uid']))
+            ->where(
+                $queryBuilder->expr()->eq('ses_id', $queryBuilder->createNamedParameter($GLOBALS['BE_USER']->id)),
+                $queryBuilder->expr()->eq('ses_name', $queryBuilder->createNamedParameter(BackendUserAuthentication::getCookieName())),
+                $queryBuilder->expr()->eq('ses_userid', (int)$GLOBALS['BE_USER']->user['uid'])
+            )
             ->execute();
     }
 }
index dab73e9..a2fe2b1 100644 (file)
@@ -19,8 +19,14 @@ use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\DatabaseConnection;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
+use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
+use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
 use TYPO3\CMS\Core\Exception;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -1050,6 +1056,7 @@ abstract class AbstractUserAuthentication
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getQueryBuilderForTable($this->session_table);
+        $queryBuilder->setRestrictions($this->userConstraints());
         $queryBuilder->select('*')
             ->from($this->session_table)
             ->from($this->user_table)
@@ -1057,28 +1064,21 @@ abstract class AbstractUserAuthentication
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_id',
                     $queryBuilder->createNamedParameter($this->id)
-                )
-            )
-            ->andWhere(
+                ),
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_name',
                     $queryBuilder->createNamedParameter($this->name)
-                )
-            )
-            // Condition on which to join the session and user table
-            ->andWhere(
+                ),
+                // Condition on which to join the session and user table
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_userid',
                     $queryBuilder->quoteIdentifier($this->user_table . '.' . $this->userid_column)
-                )
-            )
-            ->andWhere(
+                ),
                 $queryBuilder->expr()->eq(
                     $this->session_table . '.ses_hashlock',
                     $queryBuilder->createNamedParameter($this->hashLockClause_getHashInt())
                 )
-            )
-            ->andWhere($this->userConstraints($queryBuilder->expr()));
+            );
 
         if ($this->lockIP) {
             $queryBuilder->andWhere(
@@ -1097,50 +1097,38 @@ abstract class AbstractUserAuthentication
     }
 
     /**
-     * @param ExpressionBuilder $expressionBuilder
-     * @param string $tableAlias
-     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
+     * This returns the restrictions needed to select the user respecting
+     * enable columns and flags like deleted, hidden, starttime, endtime
+     * and rootLevel
+     *
+     * @return \TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface
      * @internal
      */
-    protected function userConstraints(
-        ExpressionBuilder $expressionBuilder,
-        string $tableAlias = ''
-    ): \Doctrine\DBAL\Query\Expression\CompositeExpression {
-        if ($tableAlias === '') {
-            $tableAlias = $this->user_table;
-        }
+    protected function userConstraints(): QueryRestrictionContainerInterface
+    {
+        $restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
 
-        $constraints = $expressionBuilder->andX();
-        if ($this->enablecolumns['rootLevel']) {
-            $constraints->add(
-                $expressionBuilder->eq($tableAlias . '.pid', 0)
-            );
+        if (empty($this->enablecolumns['disabled'])) {
+            $restrictionContainer->removeByType(HiddenRestriction::class);
         }
-        if ($this->enablecolumns['disabled']) {
-            $constraints->add(
-                $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['disabled'], 0)
-            );
+
+        if (empty($this->enablecolumns['deleted'])) {
+            $restrictionContainer->removeByType(DeletedRestriction::class);
         }
-        if ($this->enablecolumns['deleted']) {
-            $constraints->add(
-                $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['deleted'], 0)
-            );
+
+        if (empty($this->enablecolumns['starttime'])) {
+            $restrictionContainer->removeByType(StartTimeRestriction::class);
         }
-        if ($this->enablecolumns['starttime']) {
-            $constraints->add(
-                $expressionBuilder->lte($tableAlias . '.' . $this->enablecolumns['starttime'], $GLOBALS['EXEC_TIME'])
-            );
+
+        if (empty($this->enablecolumns['endtime'])) {
+            $restrictionContainer->removeByType(EndTimeRestriction::class);
         }
-        if ($this->enablecolumns['endtime']) {
-            $constraints->add(
-                $expressionBuilder->orX(
-                    $expressionBuilder->eq($tableAlias . '.' . $this->enablecolumns['endtime'], 0),
-                    $expressionBuilder->gt($tableAlias . '.' . $this->enablecolumns['endtime'], $GLOBALS['EXEC_TIME'])
-                )
-            );
+
+        if (!empty($this->enablecolumns['rootLevel'])) {
+            $restrictionContainer->add(GeneralUtility::makeInstance(RootLevelRestriction::class, [$this->user_table]));
         }
 
-        return $constraints;
+        return $restrictionContainer;
     }
 
     /**
@@ -1479,7 +1467,10 @@ abstract class AbstractUserAuthentication
         $authInfo['db_user']['username_column'] = $this->username_column;
         $authInfo['db_user']['userident_column'] = $this->userident_column;
         $authInfo['db_user']['usergroup_column'] = $this->usergroup_column;
-        $authInfo['db_user']['enable_clause'] = $this->userConstraints($expressionBuilder);
+        $authInfo['db_user']['enable_clause'] = $this->userConstraints()->buildExpression(
+            [$this->user_table],
+            $expressionBuilder
+        );
         if ($this->checkPid && $this->checkPid_value !== null) {
             $authInfo['db_user']['checkPidList'] = $this->checkPid_value;
             $authInfo['db_user']['check_pid_clause'] = $expressionBuilder->in(
@@ -1521,9 +1512,7 @@ abstract class AbstractUserAuthentication
                 $query->expr()->lt(
                     'ses_tstamp',
                     $query->createNamedParameter((int)($GLOBALS['EXEC_TIME'] - $this->gc_time))
-                )
-            )
-            ->andWhere(
+                ),
                 $query->expr()->eq(
                     'ses_name',
                     $query->createNamedParameter($this->name)
@@ -1604,10 +1593,10 @@ abstract class AbstractUserAuthentication
     public function getRawUserByUid($uid)
     {
         $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
+        $query->setRestrictions($this->userConstraints());
         $query->select('*')
             ->from($this->user_table)
-            ->where($query->expr()->eq('uid', $query->createNamedParameter($uid)))
-            ->andWhere($this->userConstraints($query->expr()));
+            ->where($query->expr()->eq('uid', (int)$uid));
 
         return $query->execute()->fetch();
     }
@@ -1623,10 +1612,10 @@ abstract class AbstractUserAuthentication
     public function getRawUserByName($name)
     {
         $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->user_table);
+        $query->setRestrictions($this->userConstraints());
         $query->select('*')
             ->from($this->user_table)
-            ->where($query->expr()->eq('username', $query->createNamedParameter($name)))
-            ->andWhere($this->userConstraints($query->expr()));
+            ->where($query->expr()->eq('username', $query->createNamedParameter($name)));
 
         return $query->execute()->fetch();
     }
@@ -1650,6 +1639,7 @@ abstract class AbstractUserAuthentication
         $user = false;
         if ($username || $extraWhere) {
             $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($dbUser['table']);
+            $query->getRestrictions()->removeAll();
 
             $constraints = array_filter([
                 QueryHelper::stripLogicalOperatorPrefix($dbUser['check_pid_clause']),
index ca0ab84..909f293 100644 (file)
@@ -17,6 +17,9 @@ namespace TYPO3\CMS\Core\Authentication;
 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\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
 use TYPO3\CMS\Core\Resource\ResourceStorage;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
@@ -1339,17 +1342,20 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                 // Explode mounts
                 // Selecting all webmounts with permission clause for reading
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
                 $MProws = $queryBuilder->select('uid')
                     ->from('pages')
-                    ->where($queryBuilder->expr()->eq('deleted', 0))
-                    ->andWhere(
+                    // @todo DOCTRINE: check how to make getPagePermsClause() portable
+                    ->where(
+                        $this->getPagePermsClause(1),
                         $queryBuilder->expr()->in(
                             'uid',
                             $queryBuilder->createNamedParameter($this->groupData['webmounts'])
                         )
                     )
-                    // @todo DOCTRINE: check how to make getPagePermsClause() portable
-                    ->andWhere($this->getPagePermsClause(1))
                     ->execute()
                     ->fetchAll();
                 $MProws = array_column(($MProws ?: []), 'uid', 'uid');
@@ -1382,8 +1388,6 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->usergroup_table);
         $expressionBuilder = $queryBuilder->expr();
         $constraints = $expressionBuilder->andX(
-            $expressionBuilder->eq('deleted', 0),
-            $expressionBuilder->eq('hidden', 0),
             $expressionBuilder->eq('pid', 0),
             $expressionBuilder->in('uid', GeneralUtility::intExplode(',', $grList)),
             $expressionBuilder->orX(
@@ -1582,11 +1586,15 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $orderBy = $GLOBALS['TCA']['sys_filemounts']['ctrl']['default_sortby'] ?? 'sorting';
 
             $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_filemounts');
+            $queryBuilder->getRestrictions()
+                ->removeAll()
+                ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+                ->add(GeneralUtility::makeInstance(HiddenRestriction::class))
+                ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
+
             $queryBuilder->select('*')
                 ->from('sys_filemounts')
-                ->where($queryBuilder->expr()->eq('hidden', 0))
-                ->andWhere($queryBuilder->expr()->eq('pid', 0))
-                ->andWhere($queryBuilder->expr()->in('uid', $fileMounts));
+                ->where($queryBuilder->expr()->in('uid', array_map('intval', $fileMounts)));
 
             foreach (QueryHelper::parseOrderBy($orderBy) as $fieldAndDirection) {
                 $queryBuilder->addOrderBy(...$fieldAndDirection);
@@ -2024,13 +2032,13 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
                 default:
                     if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
                         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
+                        $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
                         $wsRec = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields))
                             ->from('sys_workspace')
-                            ->where($queryBuilder->expr()->eq('pid', 0))
-                            ->andWhere(
+                            ->where(
                                 $queryBuilder->expr()->eq(
                                     'uid',
-                                    $queryBuilder->createNamedParameter((int)$wsRec)
+                                    (int)$wsRec
                                 )
                             )
                             ->orderBy('title')
@@ -2195,9 +2203,9 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
         } elseif (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
             // Traverse custom workspaces:
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
+            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
             $workspaces = $queryBuilder->select('uid', 'title', 'adminusers', 'reviewers')
                 ->from('sys_workspace')
-                ->where($queryBuilder->expr()->eq('pid', 0))
                 ->orderBy('title')
                 ->execute()
                 ->fetchAll(\PDO::FETCH_ASSOC);
@@ -2322,9 +2330,11 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
             $queryBuilder->select('tstamp')
                 ->from('sys_log')
-                ->where($queryBuilder->expr()->eq('type', 255))
-                ->andWhere($queryBuilder->expr()->eq('action', 4))
-                ->andWhere($queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter((int)$theTimeBack)))
+                ->where(
+                    $queryBuilder->expr()->eq('type', 255),
+                    $queryBuilder->expr()->eq('action', 4),
+                    $queryBuilder->expr()->gt('tstamp', (int)$theTimeBack)
+                )
                 ->orderBy('tstamp', 'DESC')
                 ->setMaxResults(1);
             if ($testRow = $queryBuilder->execute()->fetch(\PDO::FETCH_ASSOC)) {
@@ -2334,10 +2344,12 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
             $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_log');
             $result = $queryBuilder->select('*')
                 ->from('sys_log')
-                ->where($queryBuilder->expr()->eq('type', 255))
-                ->andWhere($queryBuilder->expr()->eq('action', 3))
-                ->andWhere($queryBuilder->expr()->neq('error', 0))
-                ->andWhere($queryBuilder->expr()->gt('tstamp', $queryBuilder->createNamedParameter((int)$theTimeBack)))
+                ->where(
+                    $queryBuilder->expr()->eq('type', 255),
+                    $queryBuilder->expr()->eq('action', 3),
+                    $queryBuilder->expr()->neq('error', 0),
+                    $queryBuilder->expr()->gt('tstamp', (int)$theTimeBack)
+                )
                 ->orderBy('tstamp')
                 ->execute();
 
@@ -2600,7 +2612,7 @@ This is a dump of the failures:
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
             $isUserAllowedToLogin = (bool)$queryBuilder->count('uid')
                 ->from('be_users')
-                ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($backendUserId)))
+                ->where($queryBuilder->expr()->eq('uid', (int)$backendUserId))
                 ->andWhere($queryBuilder->expr()->eq('admin', 1))
                 ->execute()
                 ->fetchColumn(0);
index c23f886..0f78d80 100644 (file)
@@ -21,7 +21,6 @@ use Doctrine\DBAL\Driver;
 use Doctrine\DBAL\Driver\Statement;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryContext;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 class Connection extends \Doctrine\DBAL\Connection
@@ -75,12 +74,11 @@ class Connection extends \Doctrine\DBAL\Connection
     /**
      * Creates a new instance of a SQL query builder.
      *
-     * @param \TYPO3\CMS\Core\Database\Query\QueryContext $queryContext
      * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
      */
-    public function createQueryBuilder(QueryContext $queryContext = null): QueryBuilder
+    public function createQueryBuilder(): QueryBuilder
     {
-        return GeneralUtility::makeInstance(QueryBuilder::class, $this, $queryContext);
+        return GeneralUtility::makeInstance(QueryBuilder::class, $this);
     }
 
     /**
index 14affb6..ea87c2f 100644 (file)
@@ -23,6 +23,8 @@ class CompositeExpression extends \Doctrine\DBAL\Query\Expression\CompositeExpre
 {
     /**
      * Retrieves the string representation of this composite expression.
+     * If expression is empty, just return an empty string.
+     * Native Doctrine expression would return () instead.
      *
      * @return string
      */
@@ -31,7 +33,26 @@ class CompositeExpression extends \Doctrine\DBAL\Query\Expression\CompositeExpre
         if ($this->count() === 0) {
             return '';
         }
-
         return parent::__toString();
     }
+
+    /**
+     * Adds an expression to composite expression.
+     *
+     * @param mixed $part
+     *
+     * @return \Doctrine\DBAL\Query\Expression\CompositeExpression
+     */
+    public function add($part)
+    {
+        // Due to a bug in Doctrine DBAL, we must add our own check here,
+        // which we luckily can, as we use a subclass anyway.
+        // @see https://github.com/doctrine/dbal/issues/2388
+        $isEmpty = $part instanceof self ? $part->count() === 0 : empty($part);
+        if (!$isEmpty) {
+            parent::add($part);
+        }
+
+        return $this;
+    }
 }
index 1530b21..7cdf3ed 100644 (file)
@@ -18,6 +18,8 @@ namespace TYPO3\CMS\Core\Database\Query;
 use Doctrine\DBAL\Query\Expression\CompositeExpression;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
+use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -51,52 +53,49 @@ class QueryBuilder
     protected $concreteQueryBuilder;
 
     /**
-     * @var QueryContext
+     * @var QueryRestrictionContainerInterface
      */
-    protected $queryContext;
+    protected $restrictionContainer;
 
     /**
      * Initializes a new QueryBuilder.
      *
      * @param Connection $connection The DBAL Connection.
-     * @param QueryContext $queryContext
+     * @param QueryRestrictionContainerInterface $restrictionContainer
      * @param \Doctrine\DBAL\Query\QueryBuilder $concreteQueryBuilder
      */
     public function __construct(
         Connection $connection,
-        QueryContext $queryContext = null,
+        QueryRestrictionContainerInterface $restrictionContainer = null,
         \Doctrine\DBAL\Query\QueryBuilder $concreteQueryBuilder = null
     ) {
         $this->connection = $connection;
+        $this->restrictionContainer = $restrictionContainer ?: GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
+        $this->concreteQueryBuilder = $concreteQueryBuilder ?: GeneralUtility::makeInstance(\Doctrine\DBAL\Query\QueryBuilder::class, $connection);
+    }
 
-        if ($queryContext === null) {
-            $queryContext = GeneralUtility::makeInstance(QueryContext::class);
-        }
-        $this->queryContext = $queryContext;
-
-        if ($concreteQueryBuilder === null) {
-            $concreteQueryBuilder = GeneralUtility::makeInstance(
-                \Doctrine\DBAL\Query\QueryBuilder::class,
-                $connection
-            );
-        }
-        $this->concreteQueryBuilder = $concreteQueryBuilder;
+    /**
+     * @return QueryRestrictionContainerInterface
+     */
+    public function getRestrictions()
+    {
+        return $this->restrictionContainer;
     }
 
     /**
-     * @return QueryContext
+     * @param QueryRestrictionContainerInterface $restrictionContainer
      */
-    public function getQueryContext(): QueryContext
+    public function setRestrictions(QueryRestrictionContainerInterface $restrictionContainer)
     {
-        return $this->queryContext;
+        $this->restrictionContainer = $restrictionContainer;
     }
 
     /**
-     * @param QueryContext $queryContext
+     * Re-apply default restrictions
      */
-    public function setQueryContext(QueryContext $queryContext)
+    public function resetRestrictions()
     {
-        $this->queryContext = $queryContext;
+        $this->restrictionContainer = GeneralUtility::makeInstance(DefaultRestrictionContainer::class);
     }
 
     /**
@@ -167,12 +166,12 @@ class QueryBuilder
             return $this->concreteQueryBuilder->execute();
         }
 
-        // set additional query restrictions based on context & TCA config
-        $originalWhereConditions = $this->addAdditonalWhereConditions();
+        // Set additional query restrictions
+        $originalWhereConditions = $this->addAdditionalWhereConditions();
 
         $result = $this->concreteQueryBuilder->execute();
 
-        // restore the original query conditions in case the user keeps
+        // Restore the original query conditions in case the user keeps
         // on modifying the state.
         $this->concreteQueryBuilder->add('where', $originalWhereConditions, false);
 
@@ -193,12 +192,12 @@ class QueryBuilder
             return $this->concreteQueryBuilder->getSQL();
         }
 
-        // set additional query restrictions based on context & TCA config
-        $originalWhereConditions = $this->addAdditonalWhereConditions();
+        // Set additional query restrictions
+        $originalWhereConditions = $this->addAdditionalWhereConditions();
 
         $sql = $this->concreteQueryBuilder->getSQL();
 
-        // restore the original query conditions in case the user keeps
+        // Restore the original query conditions in case the user keeps
         // on modifying the state.
         $this->concreteQueryBuilder->add('where', $originalWhereConditions, false);
 
@@ -901,6 +900,18 @@ class QueryBuilder
     }
 
     /**
+     * Quotes like wildcards for given string value.
+     *
+     * @param string $value The value to be quoted.
+     *
+     * @return string The quoted value.
+     */
+    public function escapeLikeWildcards(string $value): string
+    {
+        return addcslashes($value, '_%');
+    }
+
+    /**
      * Quotes a given input parameter.
      *
      * @param mixed $input The parameter to be quoted.
@@ -1054,28 +1065,24 @@ class QueryBuilder
      *
      * @return \Doctrine\DBAL\Query\Expression\CompositeExpression|mixed
      */
-    protected function addAdditonalWhereConditions()
+    protected function addAdditionalWhereConditions()
     {
-        $queryRestrictionBuilder = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            $this->getQueriedTables(),
-            $this->expr(),
-            $this->getQueryContext()
-        );
-
         $originalWhereConditions = $this->concreteQueryBuilder->getQueryPart('where');
-        if ($originalWhereConditions instanceof CompositeExpression) {
-            $originalWhereConditions = clone($originalWhereConditions);
+        $expression = $this->restrictionContainer->buildExpression($this->getQueriedTables(), $this->expr());
+        // This check would be obsolete, as the composite expression would not add empty expressions anyway
+        // But we keep it here to only clone the previous state, in case we really will change it.
+        // Once we remove this state preserving functionality, we can remove the count check here
+        // and just add the expression to the query builder.
+        if ($expression->count() > 0) {
+            if ($originalWhereConditions instanceof CompositeExpression) {
+                // Save the original query conditions so we can restore
+                // them after the query has been built.
+                $originalWhereConditions = clone($originalWhereConditions);
+            }
+            $this->concreteQueryBuilder->andWhere($expression);
         }
 
-        $additionalQueryRestrictions = $queryRestrictionBuilder->getVisibilityConstraints();
-
-        if ($additionalQueryRestrictions->count() !== 0) {
-            // save the original query conditions so we can restore
-            // them after the query has been built.
-
-            $this->concreteQueryBuilder->andWhere($additionalQueryRestrictions);
-        }
+        // @todo add hook to be able to add additional restrictions
 
         return $originalWhereConditions;
     }
diff --git a/typo3/sysext/core/Classes/Database/Query/QueryContext.php b/typo3/sysext/core/Classes/Database/Query/QueryContext.php
deleted file mode 100644 (file)
index caf0e4d..0000000
+++ /dev/null
@@ -1,596 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Database\Query;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Frontend\Page\PageRepository;
-
-/**
- * TYPO3 / TCA specific query settings that deal with enable/hidden fields,
- * frontend groups and start-/endtimes.
- */
-class QueryContext
-{
-    /**
-     * The context for which the restraints are to be built.
-     *
-     * @var QueryContextType
-     */
-    protected $context;
-
-    /**
-     * @var int[]
-     */
-    protected $memberGroups = null;
-
-    /**
-     * @var int
-     */
-    protected $currentWorkspace = null;
-
-    /**
-     * @var int
-     */
-    protected $accessTime = null;
-
-    /**
-     * Global flag if hidden records are to be included in the query result.
-     *
-     * In PageRepository::enableFields() this is called showHidden
-     *
-     * @var bool
-     */
-    protected $includeHidden = null;
-
-    /**
-     * Global flag if deleted records are to be included in the query result.
-     *
-     * @var bool
-     */
-    protected $includeDeleted = false;
-
-    /**
-     * Per table flag if deleted records are to be included in the query result.
-     *
-     * @var array
-     */
-    protected $includeDeletedForTable = [];
-
-    /**
-     * Global flag if records in a non-default versioned state should be
-     * included in the query results.
-     *
-     * In PageRepository the flag is called versioningPreview
-     *
-     * @var bool
-     */
-    protected $includePlaceholders = null;
-
-    /**
-     * Global flag if versioned records are to be included in the query result.
-     * Also influences if enable fields are respected for the query.
-     *
-     * In PageRepository the flag is called noVersionPreview
-     *
-     * @var bool
-     */
-    protected $includeVersionedRecords = false;
-
-    /**
-     * Global flag if enable fields are going to be checked for the query.
-     *
-     * @var bool
-     */
-    protected $ignoreEnableFields = false;
-
-    /**
-     * Global list of enable columns that are not checked for the query.
-     * This list is only checked if $ignoreEnableFields is enabled.
-     *
-     * @var string[]
-     */
-    protected $ignoredEnableFields = [];
-
-    /**
-     * Per table list of enable columns that are not checked for the query.
-     * This list is only checked if $ignoreEnableFields is enabled.
-     *
-     * @var string[]
-     */
-    protected $ignoredEnableFieldsForTable = []; // Per Table list of ignored columns
-
-    /**
-     * Associative array of table configs to override the TCA definition. If a table
-     * is not configured here the setup information from the TCA will be used.
-     *
-     * The array key is the table name, the value is in the format
-     * [
-     *   'deleted' => 'fieldName',
-     *   'versioningWS' => true,
-     *   'enablecolumns' => [ 'disabled' => hidden, ... ]
-     * ]
-     *
-     * @var array
-     */
-    protected $tableConfigs = [];
-
-    /**
-     * QueryContext constructor.
-     *
-     * @param string $context A valid QueryContextType
-     */
-    public function __construct(string $context = QueryContextType::AUTO)
-    {
-        $this->context = GeneralUtility::makeInstance(QueryContextType::class, $context);
-    }
-
-    /**
-     * @return string
-     */
-    public function getContext(): string
-    {
-        if ($this->context->equals(QueryContextType::AUTO)) {
-            if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_FE) {
-                return QueryContextType::FRONTEND;
-            } elseif (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_BE) {
-                return QueryContextType::BACKEND;
-            } else {
-                return QueryContextType::UNRESTRICTED;
-            }
-        }
-
-        return (string)$this->context;
-    }
-
-    /**
-     * Set the context in which the query is going to be run.
-     * Used by the QueryRestrictionBuilder to determine the restrictions to be placed.
-     *
-     * @param string $context
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setContext(string $context): QueryContext
-    {
-        $this->context = GeneralUtility::makeInstance(QueryContextType::class, $context);
-
-        return $this;
-    }
-
-    /**
-     * Get a list of member groups (fe_groups) that will be used in when building
-     * query restrictions in FE context.
-     *
-     * @return int[]
-     */
-    public function getMemberGroups(): array
-    {
-        // If the member groups have not been explicitly set
-        // the group list from the frontend controller context
-        // will be inherited
-        if ($this->memberGroups === null) {
-            $this->memberGroups = GeneralUtility::intExplode(
-                ',',
-                $this->getTypoScriptFrontendController()->gr_list,
-                true
-            );
-        }
-
-        return (array)$this->memberGroups;
-    }
-
-    /**
-     * Set the member groups that will be checked in frontend context.
-     *
-     * @param int[] $memberGroups
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setMemberGroups(array $memberGroups): QueryContext
-    {
-        $this->memberGroups = $memberGroups;
-
-        return $this;
-    }
-
-    /**
-     * Get the current workspace. If not actively defined it will fall back
-     * to the current workspace set in the PageRepository.
-     *
-     * @return int
-     */
-    public function getCurrentWorkspace(): int
-    {
-        return $this->currentWorkspace ?? (int)$this->getPageRepository()->versioningWorkspaceId;
-    }
-
-    /**
-     * Set the current workspace id.
-     *
-     * @param int $currentWorkspace
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setCurrentWorkspace(int $currentWorkspace): QueryContext
-    {
-        $this->currentWorkspace = $currentWorkspace;
-
-        return $this;
-    }
-
-    /**
-     * Return the current accesstime. If not explictly set fall back to the
-     * value of $GLOBALS['SIM_ACCESS_TIME']
-     *
-     * @return int
-     */
-    public function getAccessTime(): int
-    {
-        if ($this->accessTime === null) {
-            return empty($GLOBALS['SIM_ACCESS_TIME']) ? 0 : (int)$GLOBALS['SIM_ACCESS_TIME'];
-        }
-
-        return $this->accessTime;
-    }
-
-    /**
-     * Set the current access time.
-     *
-     * @param int $accessTime
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setAccessTime(int $accessTime): QueryContext
-    {
-        $this->accessTime = $accessTime;
-
-        return $this;
-    }
-
-    /**
-     * Returns the global setting wether hidden records should be included
-     * in the query result. Preferrably getIncludeHiddenForTable() should
-     * be used as the proper information from TSFE can be inherited by
-     * using the table name information.
-     *
-     * Defaults to false in case the flag has not been explictly set.
-     *
-     * @return bool
-     * @internal
-     */
-    public function getIncludeHidden(): bool
-    {
-        // Casting to bool to accomodate for the legacy fallback:
-        // When showHidden has not been explicitly set it's going to
-        // be determined by the settings in the TyposcriptFrontendController.
-        // As we don't now the table being queried here it's better to use
-        // getIncludeHiddenForTable()
-        return (bool)$this->includeHidden;
-    }
-
-    /**
-     * Flag if hidden records for the given table should be included in the query result.
-     * If $includeHidden has not been explictly set the information from TSFE will be
-     * used to determine the setting.
-     *
-     * @param string $table
-     * @return bool
-     */
-    public function getIncludeHiddenForTable(string $table): bool
-    {
-        if ($this->includeHidden === null && is_object($this->getTypoScriptFrontendController())) {
-            $showHidden = $table === 'pages' || $table === 'pages_language_overlay'
-                ? $this->getTypoScriptFrontendController()->showHiddenPage
-                : $this->getTypoScriptFrontendController()->showHiddenRecords;
-
-            if ($showHidden === -1) {
-                $showHidden = false;
-            }
-
-            $this->includeHidden = (bool)$showHidden;
-        }
-
-        return (bool)$this->includeHidden;
-    }
-
-    /**
-     * Set if hidden records should be part of the query result set.
-     *
-     * @param bool $includeHidden
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeHidden(bool $includeHidden): QueryContext
-    {
-        $this->includeHidden = $includeHidden;
-
-        return $this;
-    }
-
-    /**
-     * Get if deleted records should be part of the query result set at all.
-     *
-     * @return bool
-     */
-    public function getIncludeDeleted(): bool
-    {
-        return $this->includeDeleted;
-    }
-
-    /**
-     * Set wether deleted records shoult be part of the query result.
-     *
-     * @param bool $includeDeleted
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeDeleted(bool $includeDeleted): QueryContext
-    {
-        $this->includeDeleted = $includeDeleted;
-
-        return $this;
-    }
-
-    /**
-     * Get if records in a non-default versioning state should be part of the query result set.
-     *
-     * @return bool
-     */
-    public function getIncludePlaceholders(): bool
-    {
-        if ($this->includePlaceholders === null) {
-            $this->includePlaceholders = $this->getPageRepository()->versioningPreview;
-        }
-
-        return (bool)$this->includePlaceholders;
-    }
-
-    /**
-     * Set if records in a non-default versioning state should be part of the query result set.
-     *
-     * @param bool $includePlaceholders
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludePlaceholders(bool $includePlaceholders): QueryContext
-    {
-        $this->includePlaceholders = $includePlaceholders;
-
-        return $this;
-    }
-
-    /**
-     * Get if versioned records shoult be part of the query result set.
-     *
-     * @return bool
-     */
-    public function getIncludeVersionedRecords(): bool
-    {
-        return $this->includeVersionedRecords;
-    }
-
-    /**
-     * Set if versioned records should be part of the query result set.
-     *
-     * @param bool $includeVersionedRecords
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeVersionedRecords(bool $includeVersionedRecords): QueryContext
-    {
-        $this->includeVersionedRecords = $includeVersionedRecords;
-
-        return $this;
-    }
-
-    /**
-     * Get if enable fields should be ignored for this query.
-     *
-     * @return bool
-     */
-    public function getIgnoreEnableFields(): bool
-    {
-        return $this->ignoreEnableFields;
-    }
-
-    /**
-     * Set if enable fields should be ignored for this query.
-     *
-     * @param bool $ignoreEnableFields
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIgnoreEnableFields(bool $ignoreEnableFields): QueryContext
-    {
-        $this->ignoreEnableFields = $ignoreEnableFields;
-
-        return $this;
-    }
-
-    /**
-     * Return global list of ignored enable columns for the query.
-     * Can be overridden per table. Only checked if $ignoreEnableFields is enabled.
-     *
-     * @return string[]
-     */
-    public function getIgnoredEnableFields(): array
-    {
-        return $this->ignoredEnableFields;
-    }
-
-    /**
-     * Set the global list of ignored enable columns.
-     *
-     * @param string[] $ignoredEnableFields
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIgnoredEnableFields(array $ignoredEnableFields): QueryContext
-    {
-        $this->ignoredEnableFields = $ignoredEnableFields;
-
-        return $this;
-    }
-
-    /**
-     * Get the ignored enable columns for this table.
-     * If no specific list has been defined the global list will be returned.
-     *
-     * @param string $table
-     * @return string[]
-     */
-    public function getIgnoredEnableFieldsForTable(string $table): array
-    {
-        if (isset($this->ignoredEnableFieldsForTable[$table])) {
-            return $this->ignoredEnableFieldsForTable[$table];
-        } elseif (!empty($this->ignoredEnableFields)) {
-            return $this->ignoredEnableFields;
-        }
-
-        return [];
-    }
-
-    /**
-     * @param string $table
-     * @param string[] $ignoredEnableFieldsForTable
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIgnoredEnableFieldsForTable(string $table, array $ignoredEnableFieldsForTable): QueryContext
-    {
-        $this->ignoredEnableFieldsForTable[$table] = $ignoredEnableFieldsForTable;
-
-        return $this;
-    }
-
-    /**
-     * Get if deleted records for this table should be included in the query result set.
-     *
-     * @param string $table
-     * @return bool
-     */
-    public function getIncludeDeletedForTable(string $table): bool
-    {
-        return $this->includeDeletedForTable[$table] ?? false;
-    }
-
-    /**
-     * Set if deleted records for this table should be included in the query result.
-     *
-     * @param string $table
-     * @param bool $includeDeletedForTable
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setIncludeDeletedForTable(string $table, bool $includeDeletedForTable): QueryContext
-    {
-        $this->includeDeletedForTable[$table] = $includeDeletedForTable;
-
-        return $this;
-    }
-
-    /**
-     * Get the table configuration information for all tables.
-     *
-     * @return array
-     */
-    public function getTableConfigs(): array
-    {
-        return $this->tableConfigs;
-    }
-
-    /**
-     * Set the table configuration for all tables.
-     *
-     * @param array $tableConfigs
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function setTableConfigs(array $tableConfigs): QueryContext
-    {
-        $this->tableConfigs = $tableConfigs;
-
-        return $this;
-    }
-
-    /**
-     * Get the table configuration for a single table.
-     *
-     * @param string $table
-     * @return array
-     */
-    public function getTableConfig(string $table): array
-    {
-        return $this->tableConfigs[$table] ?? $this->getTcaDefiniton($table);
-    }
-
-    /**
-     * Get the TCA definition for a tables and extract the relevant parts
-     * of the table configuration.
-     *
-     * @param string $table
-     * @return array
-     */
-    protected function getTcaDefiniton(string $table): array
-    {
-        $ctrlDefiniton = $GLOBALS['TCA'][$table]['ctrl'] ?? [];
-        return array_intersect_key(
-            $ctrlDefiniton,
-            ['delete' => true, 'versioningWS' => true, 'enablecolumns' => true]
-        );
-    }
-
-    /**
-     * Add a table configuration entry to the table config array.
-     *
-     * @param string $table
-     * @param string $deletedField
-     * @param bool $versioningSupport
-     * @param array $enableColumns
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function addTableConfig(
-        string $table,
-        string $deletedField = null,
-        bool $versioningSupport = false,
-        array $enableColumns = []
-    ): QueryContext {
-        $this->tableConfigs[$table] = [
-            'deleted' => $deletedField,
-            'versioningWS' => $versioningSupport,
-            'enablecolumns' => $enableColumns
-        ];
-    }
-
-    /**
-     * Remove a table override from the config array.
-     *
-     * @param string $table
-     * @return \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    public function removeTableConfig(string $table): QueryContext
-    {
-        unset($this->tableConfigs[$table]);
-
-        return $this;
-    }
-
-    /**
-     * @return \TYPO3\CMS\Frontend\Page\PageRepository
-     */
-    protected function getPageRepository(): PageRepository
-    {
-        if ($this->getContext() === QueryContextType::FRONTEND && is_object($this->getTypoScriptFrontendController())) {
-            return $this->getTypoScriptFrontendController()->sys_page;
-        } else {
-            return GeneralUtility::makeInstance(PageRepository::class);
-        }
-    }
-
-    /**
-     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
-     */
-    protected function getTypoScriptFrontendController()
-    {
-        return $GLOBALS['TSFE'];
-    }
-}
diff --git a/typo3/sysext/core/Classes/Database/Query/QueryContextType.php b/typo3/sysext/core/Classes/Database/Query/QueryContextType.php
deleted file mode 100644 (file)
index 8b20d79..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Database\Query;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-/**
- * Enumeration object for query context type
- *
- */
-class QueryContextType extends \TYPO3\CMS\Core\Type\Enumeration
-{
-    const __default = self::AUTO;
-
-    /**
-     * Constants reflecting the query context type
-     */
-    const AUTO = 'AUTO';
-    const UNRESTRICTED = 'UNRESTRICTED';
-    const FRONTEND = 'FRONTEND';
-    const BACKEND = 'BACKEND';
-    const BACKEND_NO_VERSIONING_PLACEHOLDERS = 'BACKEND_NO_VERSIONING_PLACEHOLDERS';
-
-    /**
-     * @param mixed $type
-     */
-    public function __construct($type = null)
-    {
-        if ($type !== null) {
-            $type = strtoupper((string)$type);
-        }
-
-        parent::__construct($type);
-    }
-}
diff --git a/typo3/sysext/core/Classes/Database/Query/QueryRestrictionBuilder.php b/typo3/sysext/core/Classes/Database/Query/QueryRestrictionBuilder.php
deleted file mode 100644 (file)
index 66ba323..0000000
+++ /dev/null
@@ -1,383 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Database\Query;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Versioning\VersionState;
-
-/**
- * Builder for SQL query constraints based on TCA settings.
- * The resulting composite expressions can be added to a query
- * being built using the QueryBuilder object.
- *
- * The restrictions being built by this class are to be used for all
- * select queries done by the QueryBuilder to avoid returning data
- * that should not be available to the caller based on the current
- * TYPO3 context.
- *
- * Restrictions that will be created can be configured using the
- * QuerySettings on the main QueryBuilder object.
- *
- * WARNING: This code has cross cutting concerns as it requires access
- * to the TypoScriptFrontEndController and $GLOBALS['TCA'] to build the
- * right queries.
- */
-class QueryRestrictionBuilder
-{
-    /**
-     * @var \TYPO3\CMS\Frontend\Page\PageRepository
-     */
-    protected $pageRepository;
-
-    /**
-     * @var \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder
-     */
-    protected $expressionBuilder;
-
-    /**
-     * @var \TYPO3\CMS\Core\Database\Query\QueryContext
-     */
-    protected $queryContext;
-
-    /**
-     * @var string[]
-     */
-    protected $queriedTables = [];
-
-    /**
-     * Initializes a new QueryBuilder.
-     *
-     * @param string[] $queriedTables
-     * @param ExpressionBuilder $expressionBuilder The ExpressionBuilder with which to create restrictions
-     * @param \TYPO3\CMS\Core\Database\Query\QueryContext $queryContext
-     */
-    public function __construct(
-        array $queriedTables,
-        ExpressionBuilder $expressionBuilder,
-        QueryContext $queryContext = null
-    ) {
-        $this->queriedTables = $queriedTables;
-        $this->expressionBuilder = $expressionBuilder;
-        $this->queryContext = $queryContext ?? GeneralUtility::makeInstance(QueryContext::class);
-    }
-
-    /**
-     * Returns a composite expression to add visibility restrictions for
-     * the selected tables based on the current context (FE/BE).
-     *
-     * You need to check if any conditions are added to the CompositeExpression
-     * before adding it to your query using `->count()`.
-     *
-     * @return CompositeExpression
-     */
-    public function getVisibilityConstraints(): CompositeExpression
-    {
-        switch ($this->queryContext->getContext()) {
-            case QueryContextType::FRONTEND:
-                return $this->getFrontendVisibilityRestrictions();
-            case QueryContextType::BACKEND:
-            case QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS:
-                return $this->getBackendVisibilityConstraints();
-            case QueryContextType::UNRESTRICTED:
-                return $this->expressionBuilder->andX();
-            default:
-                throw new \RuntimeException(
-                    'Unknown TYPO3 Context / Request type: "' . TYPO3_REQUESTTYPE . '".',
-                    1459708283
-                );
-        }
-    }
-
-    /**
-     * Returns a composite expression takeing into account visibility restrictions
-     * imposed by enableFields, versioning/workspaces and deletion.
-     *
-     * @return \TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression
-     * @throws \LogicException
-     */
-    protected function getFrontendVisibilityRestrictions(): CompositeExpression
-    {
-        $queryContext = $this->queryContext;
-        $ignoreEnableFields = $queryContext->getIgnoreEnableFields();
-        $includeDeleted = $queryContext->getIncludeDeleted();
-
-        if (!$ignoreEnableFields && $includeDeleted) {
-            throw new \LogicException(
-                'The query settings "ignoreEnableFields=FALSE" and "includeDeleted=TRUE" can not be used together '
-                . 'in frontend context.',
-                1459690516
-            );
-        }
-
-        $constraints = [];
-        foreach ($this->queriedTables as $tableName => $tableAlias) {
-            $tableConfig = $queryContext->getTableConfig($tableName);
-            if (!$ignoreEnableFields && !$includeDeleted) {
-                $constraint = $this->getEnableFieldConstraints(
-                    $tableName,
-                    $tableAlias,
-                    $queryContext->getIncludeHiddenForTable($tableName),
-                    [],
-                    $queryContext->getIncludeVersionedRecords()
-                );
-                if ($constraint->count() !== 0) {
-                    $constraints[] = $constraint;
-                }
-            } elseif ($ignoreEnableFields && !$includeDeleted) {
-                if (!empty($queryContext->getIgnoredEnableFieldsForTable($tableName))) {
-                    $constraint = $this->getEnableFieldConstraints(
-                        $tableName,
-                        $tableAlias,
-                        $queryContext->getIncludeHiddenForTable($tableName),
-                        $queryContext->getIgnoredEnableFieldsForTable($tableName),
-                        $queryContext->getIncludeVersionedRecords()
-                    );
-                    if ($constraint->count() !== 0) {
-                        $constraints[] = $constraint;
-                    }
-                } elseif (!empty($tableConfig['delete'])) {
-                    $tablePrefix = empty($tableAlias) ? $tableName : $tableAlias;
-                    $constraints[] = $this->expressionBuilder->eq(
-                        $tablePrefix . '.' . $tableConfig['delete'],
-                        0
-                    );
-                }
-            }
-        }
-
-        return $this->expressionBuilder->andX(...$constraints);
-    }
-
-    /**
-     * Returns a composite expression to restrict access to records for the backend context.
-     *
-     * @return CompositeExpression
-     * @todo: Lots of code duplication, check how/if this can be merged with the "getEnableFieldConstraints"
-     * @todo: after the test cases are done for backend and frontend.
-     */
-    protected function getBackendVisibilityConstraints(): CompositeExpression
-    {
-        $queryContext = $this->queryContext;
-        $ignoreEnableFields = $queryContext->getIgnoreEnableFields();
-        $includeDeleted = $queryContext->getIncludeDeleted();
-
-        $constraints = [];
-        $expressionBuilder = $this->expressionBuilder;
-
-        foreach ($this->queriedTables as $tableName => $tableAlias) {
-            $tableConfig = $queryContext->getTableConfig($tableName);
-            $tablePrefix = empty($tableAlias) ? $tableName : $tableAlias;
-
-            if (empty($tableConfig)) {
-                // No restrictions for this table, not configured by TCA
-                continue;
-            }
-
-            if (!$ignoreEnableFields && is_array($tableConfig['enablecolumns'])) {
-                $enableColumns = $tableConfig['enablecolumns'];
-
-                if (isset($enableColumns['disabled'])) {
-                    $constraints[] = $expressionBuilder->eq(
-                        $tablePrefix . '.' . $enableColumns['disabled'],
-                        0
-                    );
-                }
-                if ($enableColumns['starttime']) {
-                    $constraints[] = $expressionBuilder->lte(
-                        $tablePrefix . '.' . $enableColumns['starttime'],
-                        $queryContext->getAccessTime()
-                    );
-                }
-                if ($enableColumns['endtime']) {
-                    $fieldName = $tablePrefix . '.' . $enableColumns['endtime'];
-                    $constraints[] = $expressionBuilder->orX(
-                        $expressionBuilder->eq($fieldName, 0),
-                        $expressionBuilder->gt($fieldName, $queryContext->getAccessTime())
-                    );
-                }
-            }
-
-            if (!$includeDeleted && !empty($tableConfig['delete'])) {
-                $constraints[] = $this->expressionBuilder->eq(
-                    $tablePrefix . '.' . $tableConfig['delete'],
-                    0
-                );
-            }
-
-            if ($queryContext->getContext() === QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS
-                && !empty($tableConfig['versioningWS'])
-            ) {
-                $constraints[] = $this->expressionBuilder->orX(
-                    $expressionBuilder->lte(
-                        $tablePrefix . '.t3ver_state',
-                        new VersionState(VersionState::DEFAULT_STATE)
-                    ),
-                    $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', $queryContext->getCurrentWorkspace())
-                );
-            }
-        }
-
-        return $expressionBuilder->andX(...$constraints);
-    }
-
-    /**
-     * @param string $tableName The table name to query
-     * @param string|null $tableAlias The table alias to use for constraints. $tableName used when empty.
-     * @param bool $showHidden Select hidden records
-     * @param string[] $ignoreFields Names of enable columns to be ignored
-     * @param bool $noVersionPreview If set, enableFields will be applied regardless of any versioning preview
-     *                               settings which might otherwise disable enableFields
-     * @return \TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression
-     */
-    protected function getEnableFieldConstraints(
-        string $tableName,
-        string $tableAlias = null,
-        bool $showHidden = false,
-        array $ignoreFields = [],
-        bool $noVersionPreview = false
-    ): CompositeExpression {
-        $queryContext = $this->queryContext;
-        $tableConfig = $queryContext->getTableConfig($tableName);
-
-        if (empty($tableConfig)) {
-            // No restrictions for this table, not configured by TCA
-            return $this->expressionBuilder->andX();
-        }
-
-        $tablePrefix = empty($tableAlias) ? $tableName : $tableAlias;
-
-        $constraints = [];
-        $expressionBuilder = $this->expressionBuilder;
-
-        // Restrict based on deleted flag of records
-        if (!empty($tableConfig['delete'])) {
-            $constraints[] = $expressionBuilder->eq($tablePrefix . '.deleted', 0);
-        }
-
-        // Restrict based on Workspaces / Versioning
-        if (!empty($tableConfig['versioningWS'])) {
-            if (!$queryContext->getIncludePlaceholders()) {
-                // Filter out placeholder records (new/moved/deleted items) in case we are NOT in a versioning preview
-                // (This means that means we are online!)
-                $constraints[] = $expressionBuilder->lte(
-                    $tablePrefix . '.t3ver_state',
-                    new VersionState(VersionState::DEFAULT_STATE)
-                );
-            } elseif ($tableName !== 'pages') {
-                // Show only records of the live and current workspace in case we are in a versioning preview
-                $constraints[] = $expressionBuilder->orX(
-                    $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', 0),
-                    $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', $queryContext->getCurrentWorkspace())
-                );
-            }
-
-            // Filter out versioned records
-            if (!$noVersionPreview && !in_array('pid', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->neq($tablePrefix . '.pid', -1);
-            }
-        }
-
-        // Restrict based on enable fields. In case of versioning-preview, enableFields are ignored
-        // and later checked in versionOL().
-        if (is_array($tableConfig['enablecolumns'])
-            && (!$queryContext->getIncludePlaceholders() || empty($tableConfig['versioningWS']) || $noVersionPreview)
-        ) {
-            $enableColumns = $tableConfig['enablecolumns'];
-
-            // Filter out disabled records
-            if (isset($enableColumns['disabled']) && !$showHidden && !in_array('disabled', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->eq(
-                    $tablePrefix . '.' . $enableColumns['disabled'],
-                    0
-                );
-            }
-
-            // Filter out records where the starttime has not yet been reached.
-            if (isset($enableColumns['starttime']) && !in_array('starttime', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->lte(
-                    $tablePrefix . '.' . $enableColumns['starttime'],
-                    $queryContext->getAccessTime()
-                );
-            }
-
-            // Filter out records with a set endtime where the time is in the past.
-            if (isset($enableColumns['endtime']) && !in_array('endtime', $ignoreFields)) {
-                $constraints[] = $expressionBuilder->orX(
-                    $expressionBuilder->eq($tablePrefix . '.' . $enableColumns['endtime'], 0),
-                    $expressionBuilder->gt(
-                        $tablePrefix . '.' . $enableColumns['endtime'],
-                        $queryContext->getAccessTime()
-                    )
-                );
-            }
-
-            // Filter out records based on the frondend user groups
-            if ($enableColumns['fe_group'] && !in_array('fe_group', $ignoreFields)) {
-                $constraints[] = $this->getFrontendUserGroupConstraints(
-                    $tablePrefix,
-                    $enableColumns['fe_group']
-                );
-            }
-
-            // Call hook functions for additional enableColumns
-            if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'])) {
-                $_params = [
-                    'table' => $tableName,
-                    'tableAlias' => $tableAlias,
-                    'tablePrefix' => $tablePrefix,
-                    'show_hidden' => $showHidden,
-                    'ignore_array' => $ignoreFields,
-                    'ctrl' => $tableConfig
-                ];
-                foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'] as $_funcRef) {
-                    $constraint = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
-
-                    $constraints[] = preg_replace('/^(?:AND[[:space:]]*)+/i', '', trim($constraint));
-                }
-            }
-        }
-
-        return $expressionBuilder->andX(...$constraints);
-    }
-
-    /**
-     * @param string $tableName The table name to build constraints for
-     * @param string $fieldName The field name to build constraints for
-     *
-     * @return CompositeExpression
-     */
-    protected function getFrontendUserGroupConstraints(string $tableName, string $fieldName): CompositeExpression
-    {
-        $expressionBuilder = $this->expressionBuilder;
-        // Allow records where no group access has been configured (field values NULL, 0 or empty string)
-        $constraints = [
-            $expressionBuilder->isNull($tableName . '.' . $fieldName),
-            $expressionBuilder->eq($tableName . '.' . $fieldName, $expressionBuilder->literal('')),
-            $expressionBuilder->eq($tableName . '.' . $fieldName, $expressionBuilder->literal('0')),
-        ];
-
-        foreach ($this->queryContext->getMemberGroups() as $value) {
-            $constraints[] = $expressionBuilder->inSet(
-                $tableName . '.' . $fieldName,
-                $expressionBuilder->literal((string)$value)
-            );
-        }
-
-        return $expressionBuilder->orX(...$constraints);
-    }
-}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/AbstractRestrictionContainer.php b/typo3/sysext/core/Classes/Database/Query/Restriction/AbstractRestrictionContainer.php
new file mode 100644 (file)
index 0000000..5da68dc
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Base class for query restriction collections
+ */
+abstract class AbstractRestrictionContainer implements QueryRestrictionContainerInterface
+{
+    /**
+     * @var QueryRestrictionInterface[]
+     */
+    protected $restrictions = [];
+
+    /**
+     * Main method to build expressions for given tables.
+     * Iterating over all registered expressions and combine them with AND
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($this->restrictions as $restriction) {
+            $constraints[] = $restriction->buildExpression($queriedTables, $expressionBuilder);
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+
+    /**
+     * Removes all restrictions stored within this container
+     *
+     * @return QueryRestrictionContainerInterface
+     */
+    public function removeAll()
+    {
+        $this->restrictions = [];
+        return $this;
+    }
+
+    /**
+     * Remove restriction of a given type
+     *
+     * @param string $restrictionType Class name of the restriction to be removed
+     * @return QueryRestrictionContainerInterface
+     */
+    public function removeByType(string $restrictionType)
+    {
+        unset($this->restrictions[$restrictionType]);
+        return $this;
+    }
+
+    /**
+     * Add a new restriction instance to this collection
+     *
+     * @param QueryRestrictionInterface $restriction
+     * @return QueryRestrictionContainerInterface
+     */
+    public function add(QueryRestrictionInterface $restriction)
+    {
+        $this->restrictions[get_class($restriction)] = $restriction;
+        return $this;
+    }
+
+    /**
+     * Factory method for restrictions.
+     * Currently only instantiates the class.
+     *
+     * @param string $restrictionClass
+     * @return QueryRestrictionInterface
+     */
+    protected function createRestriction($restrictionClass)
+    {
+        return GeneralUtility::makeInstance($restrictionClass);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/BackendWorkspaceRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/BackendWorkspaceRestriction.php
new file mode 100644 (file)
index 0000000..0223e9a
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Versioning\VersionState;
+
+/**
+ * Restriction to make queries in TYPO3 backend context versioning/ workspace aware
+ */
+class BackendWorkspaceRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var int
+     */
+    protected $workspaceId;
+
+    /**
+     * @var bool
+     */
+    protected $includeRowsForWorkspaceOverlay;
+
+    /**
+     * @param int $workspaceId
+     * @param bool $includeRowsForWorkspaceOverlay
+     */
+    public function __construct(int $workspaceId = null, $includeRowsForWorkspaceOverlay = true)
+    {
+        $this->workspaceId = $workspaceId === null ? $GLOBALS['BE_USER']->workspace : $workspaceId;
+        $this->includeRowsForWorkspaceOverlay = $includeRowsForWorkspaceOverlay;
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $workspaceEnabled = $GLOBALS['TCA'][$tableName]['ctrl']['versioningWS'] ?? null;
+            if (!empty($workspaceEnabled)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                $workspaceIdExpression = $expressionBuilder->eq(
+                    $tablePrefix . '.t3ver_wsid',
+                    (int)$this->workspaceId
+                );
+                if ($this->includeRowsForWorkspaceOverlay) {
+                    $constraints[] = $expressionBuilder->orX(
+                        $workspaceIdExpression,
+                        $expressionBuilder->lte(
+                            $tablePrefix . '.t3ver_state',
+                            new VersionState(VersionState::DEFAULT_STATE)
+                        )
+                    );
+                } else {
+                    $comparisonExpression = $this->workspaceId === 0 ? 'neq' : 'eq';
+                    $constraints[] = $workspaceIdExpression;
+                    $constraints[] = $expressionBuilder->{$comparisonExpression}(
+                        $tablePrefix . '.pid',
+                        -1
+                    );
+                }
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/DefaultRestrictionContainer.php b/typo3/sysext/core/Classes/Database/Query/Restriction/DefaultRestrictionContainer.php
new file mode 100644 (file)
index 0000000..387cdc1
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This is the container with restrictions, that are added to any doctrine query
+ */
+class DefaultRestrictionContainer extends AbstractRestrictionContainer
+{
+    /**
+     * Default restriction classes.
+     *
+     * @var QueryRestrictionInterface[]
+     */
+    protected $defaultRestrictionTypes = [
+        DeletedRestriction::class,
+        HiddenRestriction::class,
+        StartTimeRestriction::class,
+        EndTimeRestriction::class
+    ];
+
+    /**
+     * Creates instances of the registered default restriction classes
+     */
+    public function __construct()
+    {
+        foreach ($this->defaultRestrictionTypes as $restrictionType) {
+            $this->add($this->createRestriction($restrictionType));
+        }
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/DeletedRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/DeletedRestriction.php
new file mode 100644 (file)
index 0000000..9e4a192
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to respect the soft-delete functionality of TYPO3.
+ * Filters out records, that were marked as deleted.
+ */
+class DeletedRestriction implements QueryRestrictionInterface
+{
+    /**
+     * Main method to build expressions for given tables
+     * Evaluates the ctrl/delete flag of the table and adds the according restriction if set
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $deletedFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['delete'] ?? null;
+            if (!empty($deletedFieldName)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                $constraints[] = $expressionBuilder->eq(
+                    $tablePrefix . '.' . $deletedFieldName,
+                    0
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/EndTimeRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/EndTimeRestriction.php
new file mode 100644 (file)
index 0000000..014cb45
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records with an end time set that has passed
+ */
+class EndTimeRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var int
+     */
+    protected $accessTimeStamp;
+
+    /**
+     * @param int $accessTimeStamp
+     */
+    public function __construct(int $accessTimeStamp = null)
+    {
+        $this->accessTimeStamp = $accessTimeStamp ?: ($GLOBALS['SIM_ACCESS_TIME'] ?? null);
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     * Evaluates the ctrl/enablecolumns/endtime flag of the table and adds the according restriction if set
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     * @throws \RuntimeException
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $endTimeFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['endtime'] ?? null;
+            if (!empty($endTimeFieldName)) {
+                if (empty($this->accessTimeStamp)) {
+                    throw new \RuntimeException(
+                        'accessTimeStamp needs to be set to an integer value, but is empty! Maybe $GLOBALS[\'SIM_ACCESS_TIME\'] has been overridden somewhere?',
+                        1462821084
+                    );
+                }
+                $fieldName = ($tableAlias ?: $tableName) . '.' . $endTimeFieldName;
+                $constraints[] = $expressionBuilder->orX(
+                    $expressionBuilder->eq($fieldName, 0),
+                    $expressionBuilder->gt($fieldName, (int)$this->accessTimeStamp)
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendGroupRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendGroupRestriction.php
new file mode 100644 (file)
index 0000000..efabc4e
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records, which are limited to the given user groups
+ */
+class FrontendGroupRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var array
+     */
+    protected $frontendGroupIds;
+
+    /**
+     * @param array $frontendGroupIds Normalized array with user groups of currently logged in user (typically expolded value of $GLOBALS['TSFE']->gr_list
+     */
+    public function __construct(array $frontendGroupIds = null)
+    {
+        $this->frontendGroupIds = $frontendGroupIds === null ? explode(',', $GLOBALS['TSFE']->gr_list) : $frontendGroupIds;
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     * Evaluates the ctrl/enablecolumns/fe_group flag of the table and adds the according restriction if set
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $groupFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['fe_group'] ?? null;
+            if (!empty($groupFieldName)) {
+                $fieldName = ($tableAlias ?: $tableName) . '.' . $groupFieldName;
+                // Allow records where no group access has been configured (field values NULL, 0 or empty string)
+                $constraints = [
+                    $expressionBuilder->isNull($fieldName),
+                    $expressionBuilder->eq($fieldName, $expressionBuilder->literal('')),
+                    $expressionBuilder->eq($fieldName, $expressionBuilder->literal('0')),
+                ];
+                foreach ($this->frontendGroupIds as $frontendGroupId) {
+                    $constraints[] = $expressionBuilder->inSet(
+                        $fieldName,
+                        $expressionBuilder->literal((string)$frontendGroupId)
+                    );
+                }
+            }
+        }
+        return $expressionBuilder->orX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendRestrictionContainer.php b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendRestrictionContainer.php
new file mode 100644 (file)
index 0000000..5444fa0
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+
+/**
+ * A collection of restrictions to be used in frontend context.
+ * This is a replacement for PageRepository::enableFields()
+ */
+class FrontendRestrictionContainer extends AbstractRestrictionContainer
+{
+    /**
+     * @var QueryRestrictionInterface[]
+     */
+    protected $defaultRestrictionTypes = [
+        DeletedRestriction::class,
+        FrontendWorkspaceRestriction::class,
+        HiddenRestriction::class,
+        StartTimeRestriction::class,
+        EndTimeRestriction::class,
+        FrontendGroupRestriction::class,
+    ];
+
+    /**
+     * FrontendRestrictionContainer constructor.
+     * Initializes the default restrictions for frontend requests
+     */
+    public function __construct()
+    {
+        foreach ($this->defaultRestrictionTypes as $restrictionType) {
+            $this->add($this->createRestriction($restrictionType));
+        }
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     * Iterates over all registered restrictions and removes the hidden restriction if preview is requested
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        /** @var TypoScriptFrontendController $typoScriptFrontendController */
+        $typoScriptFrontendController = $GLOBALS['TSFE'];
+        foreach ($this->restrictions as $restriction) {
+            foreach ($queriedTables as $tableName => $tableAlias) {
+                $disableRestriction = false;
+                if ($restriction instanceof HiddenRestriction) {
+                    // If display of hidden records is requested, we must disable the hidden restriction.
+                    $disableRestriction = $tableName === 'pages' ? $typoScriptFrontendController->showHiddenPage : $typoScriptFrontendController->showHiddenRecords;
+                }
+                if (!$disableRestriction) {
+                    $constraints[] = $restriction->buildExpression([$tableName => $tableAlias], $expressionBuilder);
+                }
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendWorkspaceRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/FrontendWorkspaceRestriction.php
new file mode 100644 (file)
index 0000000..744785d
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Versioning\VersionState;
+
+/**
+ * Restriction to filter records for fronted workspaces preview
+ */
+class FrontendWorkspaceRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var int
+     */
+    protected $workspaceId;
+
+    /**
+     * @var bool
+     */
+    protected $includeRowsForWorkspacePreview;
+
+    /**
+     * @var bool
+     */
+    protected $enforceLiveRowsOnly;
+
+    /**
+     * @param int $workspaceId (PageRepository::$versioningWorkspaceId property)
+     * @param bool $includeRowsForWorkspacePreview (PageRepository::$versioningPreview property)
+     * @param bool $enforceLiveRowsOnly (!$noVersionPreview argument from PageRepository::enableFields()) This is ONLY for use in PageRepository class and most likely will be removed
+     */
+    public function __construct(int $workspaceId = null, bool $includeRowsForWorkspacePreview = null, bool $enforceLiveRowsOnly = true)
+    {
+        $this->workspaceId = $workspaceId === null ? $GLOBALS['TSFE']->sys_page->versioningWorkspaceId : $workspaceId;
+        $this->includeRowsForWorkspacePreview = $includeRowsForWorkspacePreview === null ? $GLOBALS['TSFE']->sys_page->versioningPreview : $includeRowsForWorkspacePreview;
+        $this->enforceLiveRowsOnly = $enforceLiveRowsOnly;
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     * Evaluates the ctrl/versioningWS flag of the table and adds various workspace related restrictions if set
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $workspaceEnabled = $GLOBALS['TCA'][$tableName]['ctrl']['versioningWS'] ?? null;
+            if (!empty($workspaceEnabled)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                if (!$this->includeRowsForWorkspacePreview) {
+                    // Filter out placeholder records (new/moved/deleted items)
+                    // in case we are NOT in a versioning preview (That means we are online!)
+                    $constraints[] = $expressionBuilder->lte(
+                        $tablePrefix . '.t3ver_state',
+                        new VersionState(VersionState::DEFAULT_STATE)
+                    );
+                } elseif ($tableName !== 'pages') {
+                    // Show only records of the live and current workspace in case we are in a versioning preview
+                    $constraints[] = $expressionBuilder->orX(
+                        $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', 0),
+                        $expressionBuilder->eq($tablePrefix . '.t3ver_wsid', (int)$this->workspaceId)
+                    );
+                }
+                // Filter out versioned records
+                if ($this->enforceLiveRowsOnly) {
+                    $constraints[] = $expressionBuilder->neq($tablePrefix . '.pid', -1);
+                }
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/HiddenRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/HiddenRestriction.php
new file mode 100644 (file)
index 0000000..227262a
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records that have been marked as hidden
+ */
+class HiddenRestriction implements QueryRestrictionInterface
+{
+    /**
+     * Main method to build expressions for given tables
+     * Evaluates the ctrl/enablecolumns/disabled flag of the table and adds the according restriction if set
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $hiddenFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['disabled'] ?? null;
+            if (!empty($hiddenFieldName)) {
+                $tablePrefix = $tableAlias ?: $tableName;
+                $constraints[] = $expressionBuilder->eq(
+                    $tablePrefix . '.' . $hiddenFieldName,
+                    0
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionContainerInterface.php b/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionContainerInterface.php
new file mode 100644 (file)
index 0000000..6abc72d
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Interface that all restriction collections must implement.
+ * It is an extension of the QueryRestrictionInterface, so collections can be treated as single restriction
+ */
+interface QueryRestrictionContainerInterface extends QueryRestrictionInterface
+{
+    /**
+     * Removes all restrictions stored within this container
+     *
+     * @return QueryRestrictionContainerInterface
+     */
+    public function removeAll();
+
+    /**
+     * Remove restriction of a given type
+     *
+     * @param string $restrictionType Class name of the restriction to be removed
+     * @return QueryRestrictionContainerInterface
+     */
+    public function removeByType(string $restrictionType);
+
+    /**
+     * Add a new restriction instance to this collection
+     *
+     * @param QueryRestrictionInterface $restriction
+     * @return QueryRestrictionContainerInterface
+     */
+    public function add(QueryRestrictionInterface $restriction);
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionInterface.php b/typo3/sysext/core/Classes/Database/Query/Restriction/QueryRestrictionInterface.php
new file mode 100644 (file)
index 0000000..914e8c0
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * The main restriction interface. All restrictions (including the collections) must implement this.
+ */
+interface QueryRestrictionInterface
+{
+    /**
+     * Main method to build expressions for given tables
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression;
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/RootLevelRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/RootLevelRestriction.php
new file mode 100644 (file)
index 0000000..ab56b85
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records which are not stored on the root page.
+ */
+class RootLevelRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @var array
+     */
+    protected $tableNames;
+
+    /**
+     * @param array $tableNames
+     */
+    public function __construct(array $tableNames = array())
+    {
+        $this->tableNames = $tableNames;
+    }
+
+    /**
+     * Main method to build expressions for given tables
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $tablePrefix = $tableAlias ?: $tableName;
+            if (empty($this->tableNames) || in_array($tablePrefix, $this->tableNames, true)) {
+                $constraints[] = $expressionBuilder->eq(
+                    $tablePrefix . '.pid',
+                    0
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Database/Query/Restriction/StartTimeRestriction.php b/typo3/sysext/core/Classes/Database/Query/Restriction/StartTimeRestriction.php
new file mode 100644 (file)
index 0000000..cbc960a
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+
+/**
+ * Restriction to filter records, that should not be shown until the start time has been reached
+ */
+class StartTimeRestriction implements QueryRestrictionInterface
+{
+    /**
+     * @param int $accessTimeStamp
+     */
+    public function __construct(int $accessTimeStamp = null)
+    {
+        $this->accessTimeStamp = $accessTimeStamp ?: ($GLOBALS['SIM_ACCESS_TIME'] ?? null);
+    }
+
+    /**
+     * @var int
+     */
+    protected $accessTimeStamp;
+
+    /**
+     * Main method to build expressions for given tables
+     * Evaluates the ctrl/enablecolumns/starttime flag of the table and adds the according restriction if set
+     *
+     * @param array $queriedTables Array of tables, where array key is table name and value potentially an alias
+     * @param ExpressionBuilder $expressionBuilder Expression builder instance to add restrictions with
+     * @return CompositeExpression The result of query builder expression(s)
+     * @throws \RuntimeException
+     */
+    public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
+    {
+        $constraints = [];
+        foreach ($queriedTables as $tableName => $tableAlias) {
+            $startTimeFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['starttime'] ?? null;
+            if (!empty($startTimeFieldName)) {
+                if (empty($this->accessTimeStamp)) {
+                    throw new \RuntimeException(
+                        'accessTimeStamp needs to be set to an integer value, but is empty! Maybe $GLOBALS[\'SIM_ACCESS_TIME\'] has been overridden somewhere?',
+                        1462820645
+                    );
+                }
+                $tablePrefix = $tableAlias ?: $tableName;
+                $constraints[] = $expressionBuilder->lte(
+                    $tablePrefix . '.' . $startTimeFieldName,
+                    (int)$this->accessTimeStamp
+                );
+            }
+        }
+        return $expressionBuilder->andX(...$constraints);
+    }
+}
index d2eab26..81831f3 100644 (file)
@@ -57,13 +57,13 @@ The :php:``ConnectionPool`` class can be used like this:
    $query = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('aTable);
    $query->select('*')
       ->from('aTable)
-      ->where($query->expr()->eq('aField', $query->createNamedParameter($aValue)))
-      ->andWhere(
+      ->where(
+         $query->expr()->eq('aField', $query->createNamedParameter($aValue)),
          $query->expr()->lte(
             'anotherField',
             $query->createNamedParameter($anotherValue)
          )
-      )
+      );
    $rows = $query->execute()->fetchAll();
 
 Extension authors are advised to use the ``ConnectionPool`` and ``Connection`` classes instead of using
index 045a906..b49bbc1 100644 (file)
@@ -39,8 +39,9 @@ class AbstractUserAuthenticationTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getDatabasePlatform()->willReturn(new MockPlatform());
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             $this->prophesize(\Doctrine\DBAL\Query\QueryBuilder::class)->reveal()
index ec91ddb..e94e0df 100644 (file)
@@ -19,6 +19,7 @@ use Prophecy\Argument;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
 use TYPO3\CMS\Core\Tests\UnitTestCase;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -922,7 +923,7 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
             ->shouldBeCalled();
 
@@ -969,7 +970,7 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $expectedSQL = 'SELECT COUNT(uid) FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT COUNT(uid) FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
             ->shouldBeCalled();
 
@@ -1014,12 +1015,10 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->assertSame($expectedSQL, $subject->getSQL());
 
-        $subject->getQueryContext()
-            ->setIgnoreEnableFields(true)
-            ->setIgnoredEnableFields(['disabled']);
+        $subject->getRestrictions()->removeAll()->add(new DeletedRestriction());
 
         $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND (pages.deleted = 0)';
         $this->assertSame($expectedSQL, $subject->getSQL());
@@ -1063,9 +1062,7 @@ class QueryBuilderTest extends UnitTestCase
             ->from('pages')
             ->where('uid=1');
 
-        $subject->getQueryContext()
-            ->setIgnoreEnableFields(true)
-            ->setIgnoredEnableFields(['disabled']);
+        $subject->getRestrictions()->removeAll()->add(new DeletedRestriction());
 
         $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND (pages.deleted = 0)';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
@@ -1073,10 +1070,9 @@ class QueryBuilderTest extends UnitTestCase
 
         $subject->execute();
 
-        $subject->getQueryContext()
-            ->setIgnoreEnableFields(false);
+        $subject->resetRestrictions();
 
-        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.hidden = 0) AND (pages.deleted = 0))';
+        $expectedSQL = 'SELECT * FROM pages WHERE (uid=1) AND ((pages.deleted = 0) AND (pages.hidden = 0))';
         $this->connection->executeQuery($expectedSQL, Argument::cetera())
             ->shouldBeCalled();
 
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryContextTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryContextTest.php
deleted file mode 100644 (file)
index a3d2862..0000000
+++ /dev/null
@@ -1,283 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Tests\Unit\Database\Query;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use Prophecy\Prophecy\ObjectProphecy;
-use TYPO3\CMS\Core\Database\Query\QueryContext;
-use TYPO3\CMS\Core\Tests\UnitTestCase;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
-use TYPO3\CMS\Frontend\Page\PageRepository;
-
-class QueryContextTest extends UnitTestCase
-{
-    /**
-     * @var QueryContext
-     */
-    protected $subject;
-
-    /**
-     * @var TypoScriptFrontendController|ObjectProphecy
-     */
-    protected $typoScriptFrontendController;
-
-    /**
-     * Create a new database connection mock object for every test.
-     *
-     * @return void
-     */
-    protected function setUp()
-    {
-        parent::setUp();
-
-        $this->typoScriptFrontendController = $this->prophesize(TypoScriptFrontendController::class);
-        $GLOBALS['TSFE'] = $this->typoScriptFrontendController->reveal();
-
-        $this->subject = GeneralUtility::makeInstance(QueryContext::class);
-    }
-
-    /**
-     * @test
-     */
-    public function contextCanBeSetByConstructiorArgument()
-    {
-        $subject = GeneralUtility::makeInstance(QueryContext::class, 'FRONTEND');
-
-        $this->assertSame('FRONTEND', $subject->getContext());
-    }
-
-    /**
-     * @test
-     * @expectedException \TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException
-     * @expectedExceptionMessage Invalid value DUMMY for TYPO3\CMS\Core\Database\Query\QueryContextType
-     */
-    public function unknownContextThrowExceptionInConstructor()
-    {
-        GeneralUtility::makeInstance(QueryContext::class, 'DUMMY');
-    }
-
-    /**
-     * @test
-     * @expectedException \TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException
-     * @expectedExceptionMessage Invalid value DUMMY for TYPO3\CMS\Core\Database\Query\QueryContextType
-     */
-    public function unknownContextThrowExceptionWhenSet()
-    {
-        $this->subject->setContext('DUMMY');
-    }
-
-    /**
-     * @test
-     */
-    public function getMemberGroupsPrefersExplicitlySetInformation()
-    {
-        $GLOBALS['TSFE']->gr_list = '3,5';
-        $this->subject->setMemberGroups([1, 2]);
-
-        $this->assertSame([1, 2], $this->subject->getMemberGroups());
-    }
-
-    /**
-     * @test
-     */
-    public function getMemberGroupsFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->gr_list = '3,5';
-
-        $this->assertSame([3, 5], $this->subject->getMemberGroups());
-    }
-
-    /**
-     * @test
-     */
-    public function getCurrentWorkspacePrefersExplicitlySetInformation()
-    {
-        /** @var PageRepository|ObjectProphecy $pageRepository */
-        $pageRepository = $this->prophesize(PageRepository::class);
-        $pageRepository->versioningWorkspaceId = 3;
-
-        $GLOBALS['TSFE']->sys_page = $pageRepository->reveal();
-
-        $this->subject->setCurrentWorkspace(1);
-        $this->subject->setContext('FRONTEND');
-
-        $this->assertSame(1, $this->subject->getCurrentWorkspace());
-    }
-
-    /**
-     * @test
-     */
-    public function getCurrentWorkspaceFallsBackToTSFE()
-    {
-        /** @var PageRepository|ObjectProphecy $pageRepository */
-        $pageRepository = $this->prophesize(PageRepository::class);
-        $pageRepository->versioningWorkspaceId = 3;
-
-        $GLOBALS['TSFE']->sys_page = $pageRepository->reveal();
-
-        $this->subject->setContext('FRONTEND');
-
-        $this->assertSame(3, $this->subject->getCurrentWorkspace());
-    }
-
-    /**
-     * @test
-     */
-    public function getAccessTimePrefersExplicitlySetInformation()
-    {
-        $GLOBALS['SIM_ACCESS_TIME'] = 100;
-        $this->subject->setAccessTime(200);
-
-        $this->assertSame(200, $this->subject->getAccessTime());
-    }
-
-    /**
-     * @test
-     */
-    public function getAccessTimeFallsBackToTSFE()
-    {
-        $GLOBALS['SIM_ACCESS_TIME'] = 100;
-
-        $this->assertSame(100, $this->subject->getAccessTime());
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForTablePrefersExplicitlySetInformation()
-    {
-        $GLOBALS['TSFE']->showHiddenPage = false;
-        $GLOBALS['TSFE']->showHiddenRecords = false;
-        $this->subject->setIncludeHidden(true);
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForTablePagesFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->showHiddenPage = true;
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForTablePagesLanguageOverlayFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->showHiddenPage = true;
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludeHiddenForRecordsFallsBackToTSFE()
-    {
-        $GLOBALS['TSFE']->showHiddenRecords = true;
-
-        $this->assertSame(true, $this->subject->getIncludeHiddenForTable('tt_content'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludePlaceholdersPrefersExplicitlySetInformation()
-    {
-        $this->subject->setIncludePlaceholders(true);
-
-        $this->assertSame(true, $this->subject->getIncludePlaceholders());
-    }
-
-    /**
-     * @test
-     */
-    public function getIncludePlaceholdersFallsBackToTSFE()
-    {
-        /** @var PageRepository|ObjectProphecy $pageRepository */
-        $pageRepository = $this->prophesize(PageRepository::class);
-        $pageRepository->versioningPreview = true;
-
-        $GLOBALS['TSFE']->sys_page = $pageRepository->reveal();
-
-        $this->subject->setContext('FRONTEND');
-        $this->assertSame(true, $this->subject->getIncludePlaceholders());
-    }
-
-    /**
-     * @test
-     */
-    public function getIgnoredEnableFieldsForTableFallsBackToGlobalList()
-    {
-        $this->subject->setIgnoredEnableFields(['disabled']);
-
-        $this->assertSame(['disabled'], $this->subject->getIgnoredEnableFieldsForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIgnoredEnableFieldsForTablePrefersExplictlySetInformation()
-    {
-        $this->subject->setIgnoredEnableFields(['disabled']);
-        $this->subject->setIgnoredEnableFieldsForTable('pages', ['starttime', 'endtime']);
-
-        $this->assertSame(['starttime', 'endtime'], $this->subject->getIgnoredEnableFieldsForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getIgnoredEnableFieldsForTableReturnsEmptyArrayWithoutInformation()
-    {
-        $this->assertSame([], $this->subject->getIgnoredEnableFieldsForTable('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getTableConfigPrefersExplicitlySetInformation()
-    {
-        $this->subject->setTableConfigs(['pages' => ['delete' => 'deleted']]);
-        $GLOBALS['TCA']['pages']['ctrl'] = ['delete' => 'deleted'];
-
-        $this->assertSame(['delete' => 'deleted'], $this->subject->getTableConfig('pages'));
-    }
-
-    /**
-     * @test
-     */
-    public function getTableConfigFallsBackToTCA()
-    {
-        $GLOBALS['TCA']['pages']['ctrl'] = [
-            'label' => 'title',
-            'tstamp' => 'tstamp',
-            'delete' => 'deleted',
-            'enablecolumns' => [
-                'disabled' => 'hidden',
-            ],
-        ];
-
-        $this->assertSame(
-            ['delete' => 'deleted', 'enablecolumns' => ['disabled' => 'hidden']],
-            $this->subject->getTableConfig('pages')
-        );
-    }
-}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/QueryRestrictionBuilderTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/QueryRestrictionBuilderTest.php
deleted file mode 100644 (file)
index bf0ce97..0000000
+++ /dev/null
@@ -1,942 +0,0 @@
-<?php
-declare (strict_types = 1);
-namespace TYPO3\CMS\Core\Tests\Unit\Database\Query;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use Prophecy\Argument;
-use TYPO3\CMS\Core\Database\Connection;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-use TYPO3\CMS\Core\Database\Query\QueryContext;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
-use TYPO3\CMS\Core\Database\Query\QueryRestrictionBuilder;
-use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
-use TYPO3\CMS\Core\Tests\UnitTestCase;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-class QueryRestrictionBuilderTest extends UnitTestCase
-{
-    /**
-     * @var array
-     */
-    protected $defaultTableConfig = [
-        'versioningWS' => true,
-        'delete' => 'deleted',
-        'enablecolumns' => [
-            'disabled' => 'hidden',
-            'starttime' => 'starttime',
-            'endtime' => 'endtime',
-            'fe_group' => 'fe_group',
-        ],
-    ];
-
-    /**
-     * @var \TYPO3\CMS\Frontend\Page\PageRepository
-     */
-    protected $pageRepository;
-
-    /**
-     * @var \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-     */
-    protected $expressionBuilder;
-
-    /**
-     * @var Connection|\Prophecy\Prophecy\ObjectProphecy
-     */
-    protected $connection;
-
-    /**
-     * @var QueryContext
-     */
-    protected $queryContext;
-
-    /**
-     * Create a new database connection mock object for every test.
-     *
-     * @return void
-     */
-    protected function setUp()
-    {
-        parent::setUp();
-
-        $this->connection = $this->prophesize(Connection::class);
-        $this->connection->quoteIdentifier(Argument::cetera())->will(function ($args) {
-            return '"' . join('"."', explode('.', $args[0])) . '"';
-        });
-        $this->connection->quote(Argument::cetera())->will(function ($args) {
-            return "'" . $args[0] . "'";
-        });
-        $this->connection->getDatabasePlatform()->willReturn(new MockPlatform());
-
-        $this->queryContext = GeneralUtility::makeInstance(QueryContext::class);
-        $this->expressionBuilder = GeneralUtility::makeInstance(ExpressionBuilder::class, $this->connection->reveal());
-    }
-
-    /**
-     * @test
-     */
-    public function getVisibilityConstraintsReturnsEmptyConstraintForUnrestrictedContext()
-    {
-        $this->queryContext->setContext(QueryContextType::UNRESTRICTED);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            [],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertEmpty((string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsSkipsUnconfiguredTables()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertEmpty((string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithUserGroups()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setMemberGroups([1, 2])
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'1\', "pages"."fe_group")) OR (FIND_IN_SET(\'2\', "pages"."fe_group")))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithVersioningPreview()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setIncludePlaceholders(true)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = '("pages"."deleted" = 0) AND ("pages"."pid" <> -1)';
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithVersioningPreviewAndNoPreviewSet()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setIncludePlaceholders(true)
-            ->setIncludeVersionedRecords(true)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutDisabledColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => [
-                'versioningWS' => true,
-                'delete' => 'deleted',
-                'enablecolumns' => [
-                    'starttime' => 'starttime',
-                    'endtime' => 'endtime',
-                    'fe_group' => 'fe_group',
-                ]
-            ]]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutStarttimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'endtime' => 'endtime',
-                        'fe_group' => 'fe_group',
-                    ]
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutEndtimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'starttime' => 'starttime',
-                        'fe_group' => 'fe_group',
-                    ]
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithoutUsergroupsColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'starttime' => 'starttime',
-                        'endtime' => 'endtime',
-                    ]
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsWithIgnoreEnableFieldsSet()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = '"pages"."deleted" = 0';
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * Data provider for getFrontendVisibilityRestrictionsWithSelectiveIgnoreEnableFieldsSet
-     *
-     * @return array
-     */
-    public function getFrontendVisibilityRestrictionsIgnoreEnableFieldsDataProvider()
-    {
-        return [
-            'disabled' => [
-                ['disabled'],
-            ],
-            'starttime' => [
-                ['starttime'],
-            ],
-            'endtime' => [
-                ['endtime'],
-            ],
-            'starttime, endtime' => [
-                ['starttime', 'endtime'],
-            ],
-            'fe_group' => [
-                ['fe_group'],
-            ],
-            'disabled, starttime, endtime' => [
-                ['disabled', 'starttime', 'endtime'],
-            ],
-            'disabled, starttime, endtime, fe_group' => [
-                ['disabled', 'starttime', 'endtime', 'fe_group'],
-            ],
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider getFrontendVisibilityRestrictionsIgnoreEnableFieldsDataProvider
-     * @param string[] $ignoreFields
-     */
-    public function getFrontendVisibilityRestrictionsWithSelectiveIgnoreEnableFieldsSet(array $ignoreFields)
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIgnoreEnableFields(true)
-            ->setIgnoredEnableFields($ignoreFields);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSqlFragments = [
-            'deleted' => '("pages"."deleted" = 0)',
-            'versioningWS' => '("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)',
-            'disabled' => '("pages"."hidden" = 0)',
-            'starttime' => '("pages"."starttime" <= 1459706700)',
-            'endtime' => '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            'fe_group' => '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ];
-
-        foreach ($ignoreFields as $fragmentName) {
-            unset($expectedSqlFragments[$fragmentName]);
-        }
-
-        $this->assertSame(join(' AND ', $expectedSqlFragments), (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForMultipleTablesWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSqlPages = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $expectedSqlTtContent = join(' AND ', [
-            '("tt_content"."deleted" = 0)',
-            '("tt_content"."t3ver_state" <= 0)',
-            '("tt_content"."pid" <> -1)',
-            '("tt_content"."hidden" = 0)',
-            '("tt_content"."starttime" <= 1459706700)',
-            '(("tt_content"."endtime" = 0) OR ("tt_content"."endtime" > 1459706700))',
-            '(("tt_content"."fe_group" IS NULL) OR ("tt_content"."fe_group" = \'\') OR ("tt_content"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSqlPages . ') AND (' . $expectedSqlTtContent . ')',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForMultipleTablesWithIgnoreEnableFields()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("tt_content"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForMultipleTablesWithDifferentEnableColumns()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => [
-                    'versioningWS' => false,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                    ],
-                ],
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSql . ') AND (("tt_content"."deleted" = 0) AND ("tt_content"."hidden" = 0))',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForJoinedTablesWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => 't'],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSqlPages = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $expectedSqlTtContent = join(' AND ', [
-            '("t"."deleted" = 0)',
-            '("t"."t3ver_state" <= 0)',
-            '("t"."pid" <> -1)',
-            '("t"."hidden" = 0)',
-            '("t"."starttime" <= 1459706700)',
-            '(("t"."endtime" = 0) OR ("t"."endtime" > 1459706700))',
-            '(("t"."fe_group" IS NULL) OR ("t"."fe_group" = \'\') OR ("t"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSqlPages . ') AND (' . $expectedSqlTtContent . ')',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForJoinedTablesWithIgnoreEnableFields()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => $this->defaultTableConfig,
-            ])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => 't'],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("t"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getFrontendVisibilityRestrictionsForJoinedTablesWithDifferentEnableColumns()
-    {
-        $this->queryContext->setContext(QueryContextType::FRONTEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => $this->defaultTableConfig,
-                'tt_content' => [
-                    'versioningWS' => false,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                    ],
-                ],
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => '', 'tt_content' => 't'],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."deleted" = 0)',
-            '("pages"."t3ver_state" <= 0)',
-            '("pages"."pid" <> -1)',
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '(("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\'))'
-        ]);
-
-        $this->assertSame(
-            '(' . $expectedSql . ') AND (("t"."deleted" = 0) AND ("t"."hidden" = 0))',
-            (string)$subject->getVisibilityConstraints()
-        );
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsSkipsUnconfiguredTables()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertEmpty((string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithDefaultSettings()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutDisabledColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => [
-                'versioningWS' => true,
-                'delete' => 'deleted',
-                'enablecolumns' => [
-                    'starttime' => 'starttime',
-                    'endtime' => 'endtime',
-                    'fe_group' => 'fe_group',
-                ],
-            ]]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."starttime" <= 1459706700)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutStarttimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'endtime' => 'endtime',
-                        'fe_group' => 'fe_group',
-                    ],
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."hidden" = 0)',
-            '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutEndtimeColumn()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs([
-                'pages' => [
-                    'versioningWS' => true,
-                    'delete' => 'deleted',
-                    'enablecolumns' => [
-                        'disabled' => 'hidden',
-                        'starttime' => 'starttime',
-                        'fe_group' => 'fe_group',
-                    ],
-                ]
-            ]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            '("pages"."hidden" = 0)',
-            '("pages"."starttime" <= 1459706700)',
-            '("pages"."deleted" = 0)',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithIgnoreEnableFieldsSet()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = '"pages"."deleted" = 0';
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithIncludeDeletedSet()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIncludeDeleted(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            'disabled' => '("pages"."hidden" = 0)',
-            'starttime' => '("pages"."starttime" <= 1459706700)',
-            'endtime' => '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithNoVersionPlaceholdersContext()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND_NO_VERSIONING_PLACEHOLDERS)
-            ->setCurrentWorkspace(4)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig]);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $expectedSql = join(' AND ', [
-            'disabled' => '("pages"."hidden" = 0)',
-            'starttime' => '("pages"."starttime" <= 1459706700)',
-            'endtime' => '(("pages"."endtime" = 0) OR ("pages"."endtime" > 1459706700))',
-            'deleted' => '("pages"."deleted" = 0)',
-            'placeholders' => '(("pages"."t3ver_state" <= 0) OR ("pages"."t3ver_wsid" = 4))',
-        ]);
-
-        $this->assertSame($expectedSql, (string)$subject->getVisibilityConstraints());
-    }
-
-    /**
-     * @test
-     */
-    public function getBackendVisibilityRestrictionsWithoutRestrictions()
-    {
-        $this->queryContext->setContext(QueryContextType::BACKEND)
-            ->setAccessTime(1459706700)
-            ->setTableConfigs(['pages' => $this->defaultTableConfig])
-            ->setIncludeDeleted(true)
-            ->setIgnoreEnableFields(true);
-
-        $subject = GeneralUtility::makeInstance(
-            QueryRestrictionBuilder::class,
-            ['pages' => ''],
-            $this->expressionBuilder,
-            $this->queryContext
-        );
-
-        $this->assertSame('', (string)$subject->getVisibilityConstraints());
-    }
-
-    // @todo: Test for per table overrides
-}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/AbstractRestrictionTestCase.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/AbstractRestrictionTestCase.php
new file mode 100644 (file)
index 0000000..6ee152c
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Prophecy\Argument;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Tests\Unit\Database\Mocks\MockPlatform;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+class AbstractRestrictionTestCase extends UnitTestCase
+{
+    /**
+     * @var \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+     */
+    protected $expressionBuilder;
+
+    /**
+     * Create a new database connection mock object for every test.
+     */
+    protected function setUp()
+    {
+        /** @var Connection|\Prophecy\Prophecy\ObjectProphecy $connection */
+        $connection = $this->prophesize(Connection::class);
+        $connection->quoteIdentifier(Argument::cetera())->will(function ($args) {
+            return '"' . implode('"."', explode('.', $args[0])) . '"';
+        });
+        $connection->quote(Argument::cetera())->will(function ($args) {
+            return '\'' . $args[0] . '\'';
+        });
+        $connection->getDatabasePlatform()->willReturn(new MockPlatform());
+
+        $this->expressionBuilder = GeneralUtility::makeInstance(ExpressionBuilder::class, $connection->reveal());
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/BackendWorkspaceRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/BackendWorkspaceRestrictionTest.php
new file mode 100644 (file)
index 0000000..d0863df
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+
+class BackendWorkspaceRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(0);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_state" <= 0)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(42);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 42) OR ("aTable"."t3ver_state" <= 0)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsLiveWorkspaceLimitedWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(0, false);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 0) AND ("aTable"."pid" <> -1)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceLimitedWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'versioningWS' => 2,
+        ];
+        $subject = new BackendWorkspaceRestriction(42, false);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 42) AND ("aTable"."pid" = -1)', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DefaultRestrictionContainerTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DefaultRestrictionContainerTest.php
new file mode 100644 (file)
index 0000000..fa48e04
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\DefaultRestrictionContainer;
+
+class DefaultRestrictionContainerTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsAllDefaultRestrictions()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'delete' => 'deleted',
+            'enablecolumns' => [
+                'disabled' => 'myHiddenField',
+                'starttime' => 'myStartTimeField',
+                'endtime' => 'myEndTimeField',
+            ],
+        ];
+        $GLOBALS['SIM_ACCESS_TIME'] = 123;
+        $subject = new DefaultRestrictionContainer();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $more[] = $expression;
+        $expression = $this->expressionBuilder->andX($expression);
+
+        $this->assertSame('("aTable"."deleted" = 0) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 123) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 123))', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DeletedRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/DeletedRestrictionTest.php
new file mode 100644 (file)
index 0000000..6860190
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+
+class DeletedRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsDeletedWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'delete' => 'deleted',
+        ];
+        $subject = new DeletedRestriction();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."deleted" = 0', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/EndTimeRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/EndTimeRestrictionTest.php
new file mode 100644 (file)
index 0000000..2e9c742
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
+
+class EndTimeRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsThrowsExceptionInStartTimeIfGlobalsAccessTimeIsMissing()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'endtime' => 'myEndTimeField',
+            ],
+        ];
+        unset($GLOBALS['SIM_ACCESS_TIME']);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1462821084);
+
+        $subject = new EndTimeRestriction();
+        $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsStartTimeWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'endtime' => 'myEndTimeField',
+            ],
+        ];
+
+        $subject = new EndTimeRestriction(42);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendGroupRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendGroupRestrictionTest.php
new file mode 100644 (file)
index 0000000..38a4b8f
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\FrontendGroupRestriction;
+
+class FrontendGroupRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNoAccessGroupWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'fe_group' => 'myGroupField',
+            ],
+        ];
+        $subject = new FrontendGroupRestriction([]);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\')', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsGroupWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'fe_group' => 'myGroupField',
+            ],
+        ];
+        $subject = new FrontendGroupRestriction([2, 3]);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'2\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'3\', "aTable"."myGroupField"))', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendRestrictionContainerTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendRestrictionContainerTest.php
new file mode 100644 (file)
index 0000000..0927168
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\DatabaseConnection;
+use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
+class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    public function frontendStatesDataProvider()
+    {
+        return [
+            'Live, no preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND (("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Live, with hidden record preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND (("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Workspace, with WS preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND ((("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 1)) AND ("aTable"."pid" <> -1)) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Workspace, with WS preview and hidden record preview' => [
+                'tableName' => 'aTable',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("aTable"."deleted" = 0) AND ((("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 1)) AND ("aTable"."pid" <> -1)) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
+            ],
+            'Live page, no preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND (("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)) AND ("pages"."hidden" = 0) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+            'Live page, with hidden page preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 0,
+                'workspacePreview' => false,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND (("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+            'Workspace page, with WS preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => false,
+                'hiddenRecordPreview' => false,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND ("pages"."pid" <> -1) AND ("pages"."hidden" = 0) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+            'Workspace page, with WS preview and hidden pages preview' => [
+                'tableName' => 'pages',
+                'workspaceId' => 1,
+                'workspacePreview' => true,
+                'hiddenPagePreview' => true,
+                'hiddenRecordPreview' => true,
+                'feGroupList' => '0,-1',
+                'expectedSQL' => '("pages"."deleted" = 0) AND ("pages"."pid" <> -1) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
+            ],
+        ];
+    }
+
+    /**
+     * @param string $tableName
+     * @param int $workspaceId
+     * @param bool $workspacePreview
+     * @param bool $hiddenPagePreview
+     * @param bool $hiddenRecordPreview
+     * @param string $feGroupList
+     * @param string $expectedSQL
+     *
+     * @test
+     * @dataProvider frontendStatesDataProvider
+     */
+    public function buildExpressionAddsCorrectClause(
+        string $tableName,
+        int $workspaceId,
+        bool $workspacePreview,
+        bool $hiddenPagePreview,
+        bool $hiddenRecordPreview,
+        string $feGroupList,
+        string $expectedSQL
+    ) {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                    'delete' => 'deleted',
+                    'enablecolumns' => [
+                        'disabled' => 'myHiddenField',
+                        'starttime' => 'myStartTimeField',
+                        'endtime' => 'myEndTimeField',
+                        'fe_group' => 'myGroupField',
+                    ],
+                ],
+            ],
+            'pages' => array(
+                'ctrl' => array(
+                    'label' => 'title',
+                    'tstamp' => 'tstamp',
+                    'sortby' => 'sorting',
+                    'type' => 'doktype',
+                    'versioningWS' => true,
+                    'origUid' => 't3_origuid',
+                    'delete' => 'deleted',
+                    'enablecolumns' => array(
+                        'disabled' => 'hidden',
+                        'starttime' => 'starttime',
+                        'endtime' => 'endtime',
+                        'fe_group' => 'fe_group'
+                    ),
+                ),
+                'columns' => array()
+            )
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningWorkspaceId = $workspaceId;
+        $pageRepository->versioningPreview = $workspacePreview;
+
+        $typoScriptFrontendController = new \stdClass();
+        $typoScriptFrontendController->showHiddenPage = $hiddenPagePreview;
+        $typoScriptFrontendController->showHiddenRecords = $hiddenRecordPreview;
+        $typoScriptFrontendController->gr_list = $feGroupList;
+        $typoScriptFrontendController->sys_page = $pageRepository;
+
+        $dbMock = $this->getMock(DatabaseConnection::class, ['quoteStr']);
+        $dbMock->expects($this->any())->method('quoteStr')->willReturnArgument(0);
+
+        $GLOBALS['TSFE'] = $typoScriptFrontendController;
+        $GLOBALS['SIM_ACCESS_TIME'] = 42;
+        $GLOBALS['TYPO3_DB'] = $dbMock;
+
+        $subject = new FrontendRestrictionContainer();
+        $expression = $subject->buildExpression([$tableName => ''], $this->expressionBuilder);
+        $this->assertSame($expectedSQL, (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendWorkspaceRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendWorkspaceRestrictionTest.php
new file mode 100644 (file)
index 0000000..e7689f7
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction;
+use TYPO3\CMS\Frontend\Page\PageRepository;
+
+class FrontendWorkspaceRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                ],
+            ]
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningPreview = false;
+
+        $subject = new FrontendWorkspaceRestriction(0);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceWhereClause()
+    {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                ],
+            ]
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningPreview = true;
+        $pageRepository->versioningWorkspaceId = 42;
+
+        $subject = new FrontendWorkspaceRestriction(42, true);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('(("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 42)) AND ("aTable"."pid" <> -1)', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildExpressionAddsNonLiveWorkspaceExclusiveWhereClause()
+    {
+        $GLOBALS['TCA'] = [
+            'aTable' => [
+                'ctrl' => [
+                    'versioningWS' => 2,
+                ],
+            ]
+        ];
+
+        $pageRepository = $this->getMock(PageRepository::class);
+        $pageRepository->versioningPreview = true;
+        $pageRepository->versioningWorkspaceId = 42;
+
+        $subject = new FrontendWorkspaceRestriction(42, true, false);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 42)', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/HiddenRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/HiddenRestrictionTest.php
new file mode 100644 (file)
index 0000000..41c4f2e
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+
+class HiddenRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsHiddenWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'disabled' => 'myHiddenField',
+            ],
+        ];
+        $subject = new HiddenRestriction();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."myHiddenField" = 0', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/RootLevelRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/RootLevelRestrictionTest.php
new file mode 100644 (file)
index 0000000..c345e2c
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
+
+class RootLevelRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsPidWhereClause()
+    {
+        $subject = new RootLevelRestriction();
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsAliasedPidWhereClause()
+    {
+        $subject = new RootLevelRestriction();
+        $expression = $subject->buildExpression(['aTable' => 'aTableAlias'], $this->expressionBuilder);
+        $this->assertSame('"aTableAlias"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsPidWhereClauseIfTableIsSpecified()
+    {
+        $subject = new RootLevelRestriction(['aTable']);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsAliasedPidWhereClauseIfAliasIsSpecified()
+    {
+        $subject = new RootLevelRestriction(['aTableAlias']);
+        $expression = $subject->buildExpression(['aTable' => 'aTableAlias'], $this->expressionBuilder);
+        $this->assertSame('"aTableAlias"."pid" = 0', (string)$expression);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsSkipsUnrestrictedTablesIfOtherTableIsSpecifiedThanUsedInTheQuery()
+    {
+        $subject = new RootLevelRestriction(['aTable']);
+        $expression = $subject->buildExpression(['anotherTable' => ''], $this->expressionBuilder);
+        $this->assertSame('', (string)$expression);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/StartTimeRestrictionTest.php b/typo3/sysext/core/Tests/Unit/Database/Query/Restriction/StartTimeRestrictionTest.php
new file mode 100644 (file)
index 0000000..1dcd324
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+declare (strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
+
+class StartTimeRestrictionTest extends AbstractRestrictionTestCase
+{
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsThrowsExceptionInStartTimeIfGlobalsAccessTimeIsMissing()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'starttime' => 'myStartTimeField',
+            ],
+        ];
+        unset($GLOBALS['SIM_ACCESS_TIME']);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1462820645);
+
+        $subject = new StartTimeRestriction();
+        $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+    }
+
+    /**
+     * @test
+     */
+    public function buildRestrictionsAddsStartTimeWhereClause()
+    {
+        $GLOBALS['TCA']['aTable']['ctrl'] = [
+            'enablecolumns' => [
+                'starttime' => 'myStartTimeField',
+            ],
+        ];
+
+        $subject = new StartTimeRestriction(42);
+        $expression = $subject->buildExpression(['aTable' => ''], $this->expressionBuilder);
+        $this->assertSame('"aTable"."myStartTimeField" <= 42', (string)$expression);
+    }
+}
index d4fe68f..5b95ca2 100644 (file)
@@ -16,7 +16,7 @@ namespace TYPO3\CMS\Felogin\Controller;
 
 use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 
@@ -227,26 +227,24 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
             $postedHash = $postData['forgot_hash'];
             $hashData = $this->frontendController->fe_user->getKey('ses', 'forgot_hash');
             if ($postedHash === $hashData['forgot_hash']) {
-
-                /** @var QueryBuilder $queryBuilder */
-                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_users');
+                $userTable = $this->frontendController->fe_user->user_table;
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+                $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
                 $row = $queryBuilder
                     ->select('uid', 'username', 'password', 'email')
-                    ->from('fe_users')
+                    ->from($userTable)
                     ->where(
-                        $queryBuilder->expr()->andX(
-                            $queryBuilder->expr()->orX(
-                                $queryBuilder->expr()->eq(
-                                    'email',
-                                    $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
-                                ),
-                                $queryBuilder->expr()->eq(
-                                    'username',
-                                    $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
-                                )
+                        $queryBuilder->expr()->orX(
+                            $queryBuilder->expr()->eq(
+                                'email',
+                                $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
                             ),
-                            $queryBuilder->expr()->in('pid', GeneralUtility::intExplode(',', $this->spid))
-                        )
+                            $queryBuilder->expr()->eq(
+                                'username',
+                                $queryBuilder->createNamedParameter($this->piVars['forgot_email'])
+                            )
+                        ),
+                        $queryBuilder->expr()->in('pid', GeneralUtility::intExplode(',', $this->spid))
                     )
                     ->execute()
                     ->fetch();
@@ -366,12 +364,13 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
                         }
 
                         // Save new password and clear DB-hash
-                        /** @var QueryBuilder $queryBuilder */
-                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_users');
-                        $queryBuilder->update('fe_users')
+                        $userTable = $this->frontendController->fe_user->user_table;
+                        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+                        $queryBuilder->getRestrictions()->removeAll();
+                        $queryBuilder->update($userTable)
                             ->set('password', $newPass)
                             ->set('felogin_forgotHash', '')
-                            ->set('tstamp', $GLOBALS['EXEC_TIME'])
+                            ->set('tstamp', (int)$GLOBALS['EXEC_TIME'])
                             ->where($queryBuilder->expr()->eq('uid', (int)$user['uid']))
                             ->execute();
 
@@ -423,10 +422,11 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
         $randHashDB = $validEnd . '|' . md5($hash);
 
         // Write hash to DB
-        /** @var QueryBuilder $queryBuilder */
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_users');
-        $queryBuilder->update('fe_users')
-            ->set('felogin_forgotHash', (string)$randHashDB)
+        $userTable = $this->frontendController->fe_user->user_table;
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+        $queryBuilder->getRestrictions()->removeAll();
+        $queryBuilder->update($userTable)
+            ->set('felogin_forgotHash', $randHashDB)
             ->where($queryBuilder->expr()->eq('uid', (int)$user['uid']))
             ->execute();
 
@@ -671,16 +671,14 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
 
                                 // take the first group with a redirect page
                                 $userGroupTable = $this->frontendController->fe_user->usergroup_table;
-                                /** @var QueryBuilder $queryBuilder */
                                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userGroupTable);
+                                $queryBuilder->getRestrictions()->removeAll();
                                 $row = $queryBuilder
                                     ->select('felogin_redirectPid')
                                     ->from($userGroupTable)
                                     ->where(
-                                        $queryBuilder->expr()->andX(
-                                            $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
-                                            $queryBuilder->expr()->in('uid', implode(',', $groupData['uid']))
-                                        )
+                                        $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
+                                        $queryBuilder->expr()->in('uid', array_map('intval', $groupData['uid']))
                                     )
                                     ->execute()
                                     ->fetch();
@@ -693,18 +691,16 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
                         case 'userLogin':
 
                             $userTable = $this->frontendController->fe_user->user_table;
-                            /** @var QueryBuilder $queryBuilder */
                             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($userTable);
+                            $queryBuilder->getRestrictions()->removeAll();
                             $row = $queryBuilder
                                 ->select('felogin_redirectPid')
                                 ->from($userTable)
                                 ->where(
-                                    $queryBuilder->expr()->andX(
-                                        $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
-                                        $queryBuilder->expr()->eq(
-                                            $this->frontendController->fe_user->userid_column,
-                                            (int)$this->frontendController->fe_user->user['uid']
-                                        )
+                                    $queryBuilder->expr()->neq('felogin_redirectPid', $queryBuilder->quote('')),
+                                    $queryBuilder->expr()->eq(
+                                        $this->frontendController->fe_user->userid_column,
+                                        (int)$this->frontendController->fe_user->user['uid']
                                     )
                                 )
                                 ->execute()
@@ -1046,8 +1042,8 @@ class FrontendLoginController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin
                 // Removes the last path segment and slash sequences like /// (if given):
                 $path = preg_replace('#/+[^/]*$#', '', $parsedUrl['path']);
 
-                /** @var QueryBuilder $queryBuilder */
                 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
+                $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
                 $localDomains = $queryBuilder->select('domainName')
                     ->from('sys_domain')
                     ->execute()
index 0757f8b..3edb460 100644 (file)
@@ -54,6 +54,8 @@ class FrontendLoginControllerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     protected function setUp()
     {
+        $GLOBALS['TSFE'] = new \stdClass();
+        $GLOBALS['TSFE']->gr_list = '0,-1';
         $this->testTableName = 'sys_domain';
         $this->testHostName = 'hostname.tld';
         $this->testSitePath = '/';
@@ -84,8 +86,9 @@ class FrontendLoginControllerTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
         $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
index 131fb9c..413f156 100644 (file)
@@ -18,8 +18,8 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Class FileFacade
@@ -256,9 +256,12 @@ class FileFacade
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
             $count = $queryBuilder->count('*')
                 ->from('sys_refindex')
-                ->where($queryBuilder->expr()->eq('ref_table', $queryBuilder->quote('sys_file')))
-                ->andWhere($queryBuilder->expr()->eq('ref_uid', (int)$this->resource->getProperty('uid')))
-                ->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')))
+                ->where(
+                    $queryBuilder->expr()->eq('deleted', 0),
+                    $queryBuilder->expr()->eq('ref_table', $queryBuilder->createNamedParameter('sys_file')),
+                    $queryBuilder->expr()->eq('ref_uid', (int)$this->resource->getProperty('uid')),
+                    $queryBuilder->expr()->neq('tablename', $queryBuilder->createNamedParameter('sys_file_metadata'))
+                )
                 ->execute()
                 ->fetchColumn();
 
index 7c40ee3..311fd25 100644 (file)
@@ -19,7 +19,6 @@ use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\RecordList\AbstractRecordList;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
@@ -785,11 +784,13 @@ class FileList extends AbstractRecordList
     protected function getTranslationsForMetaData($metaDataRecord)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_metadata');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
         $translationRecords = $queryBuilder->select('*')
             ->from('sys_file_metadata')
-            ->where($queryBuilder->expr()->eq($GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'], (int)$metaDataRecord['uid']))
-            ->andWhere($queryBuilder->expr()->gt($GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'], 0))
+            ->where(
+                $queryBuilder->expr()->eq($GLOBALS['TCA']['sys_file_metadata']['ctrl']['transOrigPointerField'], (int)$metaDataRecord['uid']),
+                $queryBuilder->expr()->gt($GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'], 0)
+            )
             ->execute()
             ->fetchAll();
 
@@ -1045,9 +1046,12 @@ class FileList extends AbstractRecordList
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
         $referenceCount = $queryBuilder->count('*')
             ->from('sys_refindex')
-            ->where($queryBuilder->expr()->eq('ref_table', $queryBuilder->quote('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('ref_uid', (int)$fileOrFolderObject->getUid()))
-            ->andWhere($queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata')))
+            ->where(
+                $queryBuilder->expr()->eq('deleted', 0),
+                $queryBuilder->expr()->eq('ref_table', $queryBuilder->quote('sys_file')),
+                $queryBuilder->expr()->eq('ref_uid', (int)$fileOrFolderObject->getUid()),
+                $queryBuilder->expr()->neq('tablename', $queryBuilder->quote('sys_file_metadata'))
+            )
             ->execute()
             ->fetchColumn();
 
index 3bb4d46..c59cf1d 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Lowlevel;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -92,15 +91,12 @@ Reports missing relations';
         );
 
         // Select DB relations from reference table
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
         $rowIterator = $queryBuilder
             ->select('ref_uid', 'ref_table', 'softref_key', 'hash', 'tablename', 'recuid', 'field', 'flexpointer', 'deleted')
             ->from('sys_refindex')
             ->where(
-                $queryBuilder->expr()->neq('ref_table', $queryBuilder->quote('_FILE'))
-            )
-            ->andWhere(
+                $queryBuilder->expr()->neq('ref_table', $queryBuilder->createNamedParameter('_FILE')),
                 $queryBuilder->expr()->gt('ref_uid', 0)
             )
             ->orderBy('sorting', 'DESC')
index b699a8a..5537992 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Recycler\Domain\Model;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -147,17 +146,17 @@ class DeletedRecords
             return;
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         // find the 'deleted' field for this table
         $deletedField = RecyclerUtility::getDeletedField($table);
 
         // create the filter WHERE-clause
         $filterConstraint = null;
-        if (trim($filter) != '') {
+        if (trim($filter) !== '') {
             $labelConstraint = $queryBuilder->expr()->like(
                 $tcaCtrl['label'],
-                $queryBuilder->quote('%' . addcslashes($filter, '_%') . '%')
+                $queryBuilder->quote('%' . $queryBuilder->escapeLikeWildcards($filter) . '%')
             );
             if (MathUtility::canBeInterpretedAsInteger($filter)) {
                 $filterConstraint = $queryBuilder->expr()->orX(
@@ -265,7 +264,7 @@ class DeletedRecords
         if ($allowDepth && $depth >= 1) {
             // check recursively for elements beneath this page
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-            $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+            $queryBuilder->getRestrictions()->removeAll();
             $resPages = $queryBuilder
                 ->select('uid')
                 ->from('pages')
@@ -415,12 +414,14 @@ class DeletedRecords
     protected function getDeletedParentPages($uid, &$pages = array())
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
         $record = $queryBuilder
             ->select('uid', 'pid')
             ->from('pages')
-            ->where($queryBuilder->expr()->eq('uid', (int)$uid))
-            ->andWhere($queryBuilder->expr()->eq($GLOBALS['TCA']['pages']['ctrl']['delete'], 1))
+            ->where(
+                $queryBuilder->expr()->eq('uid', (int)$uid),
+                $queryBuilder->expr()->eq($GLOBALS['TCA']['pages']['ctrl']['delete'], 1)
+            )
             ->execute()
             ->fetch();
         if ($record) {
index a17270e..1d2cd0b 100644 (file)
@@ -15,7 +15,6 @@ namespace TYPO3\CMS\Recycler\Domain\Model;
  */
 
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Recycler\Utility\RecyclerUtility;
 
@@ -42,7 +41,7 @@ class Tables
             if ($deletedField) {
                 // Determine whether the table has deleted records:
                 $queryBuilder = $connection->getQueryBuilderForTable($tableName);
-                $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+                $queryBuilder->getRestrictions()->removeAll();
 
                 $deletedCount = $queryBuilder->count('uid')
                     ->from($tableName)
index a16d9ee..a503850 100644 (file)
@@ -62,6 +62,7 @@ class CleanerTask extends AbstractTask
     {
         if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
+            $queryBuilder->getRestrictions()->removeAll();
 
             $constraints = [
                 $queryBuilder->expr()->eq($GLOBALS['TCA'][$tableName]['ctrl']['delete'], 1),
@@ -69,25 +70,17 @@ class CleanerTask extends AbstractTask
 
             if ($GLOBALS['TCA'][$tableName]['ctrl']['tstamp']) {
                 $dateBefore = $this->getPeriodAsTimestamp();
-                $constraints[] = $queryBuilder->expr()->lt($GLOBALS['TCA'][$tableName]['ctrl']['tstamp'], $dateBefore);
+                $constraints[] = $queryBuilder->expr()->lt($GLOBALS['TCA'][$tableName]['ctrl']['tstamp'], (int)$dateBefore);
             }
-
             $this->checkFileResourceFieldsBeforeDeletion($tableName, $constraints);
-
-            $queryBuilder
-                ->getQueryContext()
-                ->setIgnoreEnableFields(true)
-                ->setIncludeDeleted(true);
-
             try {
                 $queryBuilder->delete($tableName)
-                    ->where($queryBuilder->expr()->andX(...$constraints))
+                    ->where(...$constraints)
                     ->execute();
             } catch (\Doctrine\DBAL\DBALException $e) {
                 return false;
             }
         }
-
         return true;
     }
 
@@ -189,10 +182,7 @@ class CleanerTask extends AbstractTask
     protected function deleteFilesForTable($table, array $constraints, array $fieldList)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-        $queryBuilder
-            ->getQueryContext()
-            ->setIgnoreEnableFields(true)
-            ->setIncludeDeleted(true);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $result = $queryBuilder
             ->select(...$fieldList)
index f605b9a..72d1e8e 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Recycler\Utility;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryContextType;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -86,7 +85,7 @@ class RecyclerUtility
             return $output;
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $clause = trim($clause);
         $loopCheck = 100;
@@ -154,7 +153,7 @@ class RecyclerUtility
             return false;
         }
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $deleted = $queryBuilder
             ->select('deleted')
@@ -176,7 +175,7 @@ class RecyclerUtility
     public static function getPidOfUid($uid, $table)
     {
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
-        $queryBuilder->getQueryContext()->setContext(QueryContextType::UNRESTRICTED);
+        $queryBuilder->getRestrictions()->removeAll();
 
         $pid = $queryBuilder
             ->select('pid')
index 4cb11dc..3dfb434 100644 (file)
@@ -83,8 +83,9 @@ class CleanerTaskTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
         $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
@@ -116,8 +117,9 @@ class CleanerTaskTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         $connection->getExpressionBuilder()->willReturn(new ExpressionBuilder($connection->reveal()));
         $connection->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
 
-        $queryBuilder = GeneralUtility::makeInstance(
-            QueryBuilder::class,
+        // TODO: This should rather be a functional test if we need a query builder
+        // or we should clean up the code itself to not need to mock internal behavior here
+        $queryBuilder = new QueryBuilder(
             $connection->reveal(),
             null,
             new \Doctrine\DBAL\Query\QueryBuilder($connection->reveal())
index 26b0efe..0e13d88 100644 (file)
@@ -22,7 +22,6 @@ use TYPO3\CMS\Backend\Module\ModuleLoader;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
 use TYPO3\CMS\Core\Imaging\Icon;
@@ -747,13 +746,14 @@ class SetupModuleController extends AbstractModule
         unset($this->OLD_BE_USER);
         if ($this->getBackendUser()->isAdmin()) {
             $this->simUser = (int)GeneralUtility::_GP('simUser');
-            /** @var QueryBuilder $queryBuilder */
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
             $users = $queryBuilder
                 ->select('*')
                 ->from('be_users')
-                ->where($queryBuilder->expr()->neq('uid', (int)$this->getBackendUser()->user['uid']))
-                ->andWhere($queryBuilder->expr()->notLike('username', $queryBuilder->createNamedParameter('_cli_%')))
+                ->where(
+                    $queryBuilder->expr()->neq('uid', (int)$this->getBackendUser()->user['uid']),
+                    $queryBuilder->expr()->notLike('username', $queryBuilder->createNamedParameter($queryBuilder->escapeLikeWildcards('_cli_') . '%'))
+                )
                 ->orderBy('username')
                 ->execute()
                 ->fetchAll();
@@ -891,15 +891,16 @@ class SetupModuleController extends AbstractModule
      */
     protected function getAvatarFileUid($beUserId)
     {
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
         $file = $queryBuilder
             ->select('uid_local')
             ->from('sys_file_reference')
-            ->where($queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')))
-            ->andWhere($queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')))
-            ->andWhere($queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('uid_foreign', (int)$beUserId))
+            ->where(
+                $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')),
+                $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')),
+                $queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')),
+                $queryBuilder->expr()->eq('uid_foreign', (int)$beUserId)
+            )
             ->execute()
             ->fetchColumn();
         return (int)$file;
@@ -920,14 +921,16 @@ class SetupModuleController extends AbstractModule
             return;
         }
 
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
+        $queryBuilder->getRestrictions()->removeAll();
         $queryBuilder
             ->delete('sys_file_reference')
-            ->where($queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')))
-            ->andWhere($queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')))
-            ->andWhere($queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')))
-            ->andWhere($queryBuilder->expr()->eq('uid_foreign', (int)$beUserId))
+            ->where(
+                $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('be_users')),
+                $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('avatar')),
+                $queryBuilder->expr()->eq('table_local', $queryBuilder->createNamedParameter('sys_file')),
+                $queryBuilder->expr()->eq('uid_foreign', (int)$beUserId)
+            )
             ->execute();
 
         // Create new reference
index cb76d08..80384ab 100644 (file)
@@ -14,9 +14,7 @@ namespace TYPO3\CMS\SysNote\Core;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
@@ -76,16 +74,15 @@ class Bootstrap
         if (!isset($arguments['pids']) || empty($arguments['pids']) || empty($GLOBALS['BE_USER']->user['uid'])) {
             return false;
         }
-        $pidList = GeneralUtility::intExplode(',', $arguments['pids'], true);
-        if (empty($pidList)) {
+        $cleanedPageIds = GeneralUtility::intExplode(',', $arguments['pids'], true);
+        if (empty($cleanedPageIds)) {
             return false;
         }
-        /** @var QueryBuilder $queryBuilder */
         $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_note');
         $count = $queryBuilder
             ->count('uid')
             ->from('sys_note')
-            ->where($queryBuilder->expr()->in('pid', $pidList))
+            ->where($queryBuilder->expr()->in('pid', $cleanedPageIds))
             ->execute()
             ->fetchColumn();
         return (bool)$count;