From 27738cdc41e79db5cdeea35df696e7ad88c11353 Mon Sep 17 00:00:00 2001 From: Morton Jonuschat <m.jonuschat@mojocode.de> Date: Thu, 4 Aug 2016 09:57:36 +0200 Subject: [PATCH] [TASK] Doctrine: Migrate \TYPO3\CMS\Frontend\Page\PageRepository Resolves: #76543 Releases: master Change-Id: I7993a04b44838f7f425a09bac812b02e1fc19cbe Reviewed-on: https://review.typo3.org/48555 Tested-by: Bamboo TYPO3com <info@typo3.com> Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de> Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de> Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de> Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de> --- .../Storage/Typo3DbQueryParserTest.php | 10 +- .../TypoScriptFrontendController.php | 9 +- .../frontend/Classes/Page/PageRepository.php | 540 +++++++++++++----- .../ContentObjectRendererTest.php | 519 +++++++++++++++++ .../Tests/Functional/Fixtures/pages.xml | 24 + .../Functional/Page/PageRepositoryTest.php | 255 +++++++++ .../ContentObjectRendererTest.php | 416 -------------- .../Tests/Unit/Page/PageRepositoryTest.php | 273 --------- 8 files changed, 1201 insertions(+), 845 deletions(-) create mode 100644 typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php diff --git a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php index bd34d205d8a5..f27f730ffa49 100644 --- a/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php +++ b/typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php @@ -289,8 +289,8 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase 'in be: respect enable fields and do not include deleted' => array('BE', false, array(), false, 'tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789) AND tx_foo_table.deleted_column = 0'), 'in fe: include all' => array('FE', true, array(), true, ''), 'in fe: ignore enable fields but do not include deleted' => array('FE', true, array(), false, 'tx_foo_table.deleted_column=0'), - 'in fe: ignore only starttime and do not include deleted' => array('FE', true, array('starttime'), false, 'tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0'), - 'in fe: respect enable fields and do not include deleted' => array('FE', false, array(), false, 'tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0 AND tx_foo_table.starttime_column<=123456789') + 'in fe: ignore only starttime and do not include deleted' => array('FE', true, array('starttime'), false, '(tx_foo_table.deleted_column = 0) AND (tx_foo_table.disabled_column = 0)'), + 'in fe: respect enable fields and do not include deleted' => array('FE', false, array(), false, '(tx_foo_table.deleted_column = 0) AND (tx_foo_table.disabled_column = 0) AND (tx_foo_table.starttime_column <= 123456789)') ); } @@ -321,7 +321,7 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase ); $connectionPoolProphet = $this->prophesize(ConnectionPool::class); - $connectionPoolProphet->getQueryBuilderForTable($tableName)->willReturn($queryBuilderProphet->reveal()); + $connectionPoolProphet->getQueryBuilderForTable(Argument::any($tableName, 'pages'))->willReturn($queryBuilderProphet->reveal()); GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal()); $mockQuerySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class) @@ -351,7 +351,7 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase 'in be: respectEnableFields=false' => array('BE', false, ''), 'in be: respectEnableFields=true' => array('BE', true, 'tx_foo_table.disabled_column=0 AND (tx_foo_table.starttime_column<=123456789) AND tx_foo_table.deleted_column = 0'), 'in FE: respectEnableFields=false' => array('FE', false, ''), - 'in FE: respectEnableFields=true' => array('FE', true, 'tx_foo_table.deleted_column=0 AND tx_foo_table.disabled_column=0 AND tx_foo_table.starttime_column<=123456789') + 'in FE: respectEnableFields=true' => array('FE', true, '(tx_foo_table.deleted_column = 0) AND (tx_foo_table.disabled_column = 0) AND (tx_foo_table.starttime_column <= 123456789)') ); } @@ -382,7 +382,7 @@ class Typo3DbQueryParserTest extends \TYPO3\CMS\Core\Tests\UnitTestCase ); $connectionPoolProphet = $this->prophesize(ConnectionPool::class); - $connectionPoolProphet->getQueryBuilderForTable($tableName)->willReturn($queryBuilderProphet->reveal()); + $connectionPoolProphet->getQueryBuilderForTable(Argument::any($tableName, 'pages'))->willReturn($queryBuilderProphet->reveal()); GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal()); /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings $mockQuerySettings */ diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index 21b400b2aace..307de2806612 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Charset\CharsetConverter; use TYPO3\CMS\Core\Controller\ErrorPageController; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\QueryHelper; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer; use TYPO3\CMS\Core\Error\Http\PageNotFoundException; @@ -1906,7 +1907,13 @@ class TypoScriptFrontendController */ public function setSysPageWhereClause() { - $this->sys_page->where_hid_del .= ' AND pages.doktype<200'; + $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable('pages') + ->getExpressionBuilder(); + $this->sys_page->where_hid_del = ' AND ' . (string)$expressionBuilder->andX( + QueryHelper::stripLogicalOperatorPrefix($this->sys_page->where_hid_del), + $expressionBuilder->lt('pages.doktype', 200) + ); $this->sys_page->where_groupAccess = $this->sys_page->getMultipleGroupsWhereClause('pages.fe_group', 'pages'); } diff --git a/typo3/sysext/frontend/Classes/Page/PageRepository.php b/typo3/sysext/frontend/Classes/Page/PageRepository.php index ec02d41fbf42..aa9963f8f373 100644 --- a/typo3/sysext/frontend/Classes/Page/PageRepository.php +++ b/typo3/sysext/frontend/Classes/Page/PageRepository.php @@ -15,6 +15,11 @@ namespace TYPO3\CMS\Frontend\Page; */ use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\QueryHelper; +use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; +use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer; +use TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction; use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; use TYPO3\CMS\Core\Resource\FileRepository; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; @@ -186,7 +191,16 @@ class PageRepository // de-selecting hidden pages - we need versionOL() to unset them only // if the overlay record instructs us to. // Clear where_hid_del and restrict to live and current workspaces - $this->where_hid_del = ' AND pages.deleted=0 AND (pages.t3ver_wsid=0 OR pages.t3ver_wsid=' . (int)$this->versioningWorkspaceId . ')'; + $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('pages') + ->expr(); + $this->where_hid_del = ' AND ' . $expressionBuilder->andX( + $expressionBuilder->eq('pages.deleted', 0), + $expressionBuilder->orX( + $expressionBuilder->eq('pages.t3ver_wsid', 0), + $expressionBuilder->eq('pages.t3ver_wsid', (int)$this->versioningWorkspaceId) + ) + ); } else { // add starttime / endtime, and check for hidden/deleted // Filter out new/deleted place-holder pages in case we are NOT in a @@ -233,13 +247,34 @@ class PageRepository $hookObject->getPage_preProcess($uid, $disableGroupAccessCheck, $this); } } - $accessCheck = $disableGroupAccessCheck ? '' : $this->where_groupAccess; - $cacheKey = md5($accessCheck . '-' . $this->where_hid_del . '-' . $this->sys_language_uid); + $cacheKey = md5( + implode( + '-', + [ + ($disableGroupAccessCheck ? '' : $this->where_groupAccess), + $this->where_hid_del, + $this->sys_language_uid + ] + ) + ); if (is_array($this->cache_getPage[$uid][$cacheKey])) { return $this->cache_getPage[$uid][$cacheKey]; } $result = array(); - $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . (int)$uid . $this->where_hid_del . $accessCheck); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder->select('*') + ->from('pages') + ->where( + $queryBuilder->expr()->eq('uid', (int)$uid), + QueryHelper::stripLogicalOperatorPrefix($this->where_hid_del) + ); + + if (!$disableGroupAccessCheck) { + $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->where_groupAccess)); + } + + $row = $queryBuilder->execute()->fetch(); if ($row) { $this->versionOL('pages', $row); if (is_array($row)) { @@ -263,9 +298,17 @@ class PageRepository if ($this->cache_getPage_noCheck[$uid]) { return $this->cache_getPage_noCheck[$uid]; } - $res = $this->getDatabaseConnection()->exec_SELECTquery('*', 'pages', 'uid=' . (int)$uid . $this->deleteClause('pages')); - $row = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + $row = $queryBuilder->select('*') + ->from('pages') + ->where($queryBuilder->expr()->eq('uid', (int)$uid)) + ->execute() + ->fetch(); + $result = array(); if ($row) { $this->versionOL('pages', $row); @@ -287,9 +330,20 @@ class PageRepository public function getFirstWebPage($uid) { $output = ''; - $res = $this->getDatabaseConnection()->exec_SELECTquery('*', 'pages', 'pid=' . (int)$uid . $this->where_hid_del . $this->where_groupAccess, '', 'sorting', '1'); - $row = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions()->removeAll(); + $row = $queryBuilder->select('*') + ->from('pages') + ->where( + $queryBuilder->expr()->eq('pid', (int)$uid), + QueryHelper::stripLogicalOperatorPrefix($this->where_hid_del), + QueryHelper::stripLogicalOperatorPrefix($this->where_groupAccess) + ) + ->orderBy('sorting') + ->setMaxResults(1) + ->execute() + ->fetch(); + if ($row) { $this->versionOL('pages', $row); if (is_array($row)) { @@ -312,9 +366,22 @@ class PageRepository if ($this->cache_getPageIdFromAlias[$alias]) { return $this->cache_getPageIdFromAlias[$alias]; } - $db = $this->getDatabaseConnection(); - $row = $db->exec_SELECTgetSingleRow('uid', 'pages', 'alias=' . $db->fullQuoteStr($alias, 'pages') . ' AND pid>=0 AND pages.deleted=0'); - // "AND pid>=0" because of versioning (means that aliases sent MUST be online!) + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + $row = $queryBuilder->select('uid') + ->from('pages') + ->where( + $queryBuilder->expr()->eq('alias', $queryBuilder->createNamedParameter($alias)), + // "AND pid>=0" because of versioning (means that aliases sent MUST be online!) + $queryBuilder->expr()->gte('pid', 0) + ) + ->setMaxResults(1) + ->execute() + ->fetch(); + if ($row) { $this->cache_getPageIdFromAlias[$alias] = $row['uid']; return $row['uid']; @@ -394,22 +461,26 @@ class PageRepository if (!in_array('pid', $fieldArr, true)) { $fieldArr[] = 'pid'; } - // NOTE to enabledFields('pages_language_overlay'): + // NOTE regarding the query restrictions // Currently the showHiddenRecords of TSFE set will allow // pages_language_overlay records to be selected as they are // child-records of a page. // However you may argue that the showHiddenField flag should // determine this. But that's not how it's done right now. // Selecting overlay record: - $db = $this->getDatabaseConnection(); - $res = $db->exec_SELECTquery( - implode(',', $fieldArr), - 'pages_language_overlay', - 'pid IN(' . implode(',', array_map('intval', $page_ids)) . ')' - . ' AND sys_language_uid=' . (int)$lUid . $this->enableFields('pages_language_overlay') - ); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('pages_language_overlay'); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + $result = $queryBuilder->select(...$fieldArr) + ->from('pages_language_overlay') + ->where( + $queryBuilder->expr()->in('pid', array_map('intval', $page_ids)), + $queryBuilder->expr()->eq('sys_language_uid', (int)$lUid) + ) + ->execute(); + $overlays = array(); - while ($row = $db->sql_fetch_assoc($res)) { + while ($row = $result->fetch()) { $this->versionOL('pages_language_overlay', $row); if (is_array($row)) { $row['_PAGES_OVERLAY'] = true; @@ -422,7 +493,6 @@ class PageRepository $overlays[$origUid] = $row; } } - $db->sql_free_result($res); } } // Create output: @@ -486,9 +556,28 @@ class PageRepository // Must be default language, otherwise no overlaying if ((int)$row[$GLOBALS['TCA'][$table]['ctrl']['languageField']] === 0) { // Select overlay record: - $res = $this->getDatabaseConnection()->exec_SELECTquery('*', $table, 'pid=' . (int)$row['pid'] . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$sys_language_content . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$row['uid'] . $this->enableFields($table), '', '', '1'); - $olrow = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable($table); + $queryBuilder->setRestrictions( + GeneralUtility::makeInstance(FrontendRestrictionContainer::class) + ); + $olrow = $queryBuilder->select('*') + ->from($table) + ->where( + $queryBuilder->expr()->eq('pid', (int)$row['pid']), + $queryBuilder->expr()->eq( + $GLOBALS['TCA'][$table]['ctrl']['languageField'], + (int)$sys_language_content + ), + $queryBuilder->expr()->eq( + $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], + (int)$row['uid'] + ) + ) + ->setMaxResults(1) + ->execute() + ->fetch(); + $this->versionOL($table, $olrow); // Merge record content by traversing all fields: if (is_array($olrow)) { @@ -605,24 +694,21 @@ class PageRepository { $pages = []; $relationField = $parentPages ? 'pid' : 'uid'; - $db = $this->getDatabaseConnection(); - - $whereStatement = $relationField . ' IN (' - . implode(',', array_map('intval', $pageIds)) . ')' - . $this->where_hid_del - . $this->where_groupAccess - . ' ' - . $additionalWhereClause; - - $databaseResource = $db->exec_SELECTquery( - $fields, - 'pages', - $whereStatement, - '', - $sortField - ); - - while (($page = $db->sql_fetch_assoc($databaseResource))) { + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions()->removeAll(); + + $result = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields, true)) + ->from('pages') + ->where( + $queryBuilder->expr()->in($relationField, array_map('intval', $pageIds)), + QueryHelper::stripLogicalOperatorPrefix($this->where_hid_del), + QueryHelper::stripLogicalOperatorPrefix($this->where_groupAccess), + QueryHelper::stripLogicalOperatorPrefix($additionalWhereClause) + ) + ->orderBy($sortField) + ->execute(); + + while ($page = $result->fetch()) { $originalUid = $page['uid']; // Versioning Preview Overlay @@ -647,8 +733,6 @@ class PageRepository } } - $db->sql_free_result($databaseResource); - // Finally load language overlays return $this->getPagesOverlay($pages); } @@ -721,16 +805,18 @@ class PageRepository $searchUid = 0; } - $whereStatement = $searchField . '=' . $searchUid - . $this->where_hid_del - . $this->where_groupAccess - . ' ' . $additionalWhereClause; - - $count = $this->getDatabaseConnection()->exec_SELECTcountRows( - 'uid', - 'pages', - $whereStatement - ); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions()->removeAll(); + $count = $queryBuilder->count('uid') + ->from('pages') + ->where( + $queryBuilder->expr()->eq($searchField, (int)$searchUid), + QueryHelper::stripLogicalOperatorPrefix($this->where_hid_del), + QueryHelper::stripLogicalOperatorPrefix($this->where_groupAccess), + QueryHelper::stripLogicalOperatorPrefix($additionalWhereClause) + ) + ->execute() + ->fetchColumn(); if (!$count) { $page = []; @@ -761,31 +847,58 @@ class PageRepository // Appending to domain string $domain .= $path; $domain = preg_replace('/\\/*$/', '', $domain); - $res = $this->getDatabaseConnection()->exec_SELECTquery('pages.uid,sys_domain.redirectTo,sys_domain.redirectHttpStatusCode,sys_domain.prepend_params', 'pages,sys_domain', 'pages.uid=sys_domain.pid - AND sys_domain.hidden=0 - AND (sys_domain.domainName=' . $this->getDatabaseConnection()->fullQuoteStr($domain, 'sys_domain') . ' OR sys_domain.domainName=' . $this->getDatabaseConnection()->fullQuoteStr(($domain . '/'), 'sys_domain') . ') ' . $this->where_hid_del . $this->where_groupAccess, '', '', 1); - $row = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); - if ($row) { - if ($row['redirectTo']) { - $redirectUrl = $row['redirectTo']; - if ($row['prepend_params']) { - $redirectUrl = rtrim($redirectUrl, '/'); - $prependStr = ltrim(substr($request_uri, strlen($path)), '/'); - $redirectUrl .= '/' . $prependStr; - } - $statusCode = (int)$row['redirectHttpStatusCode']; - if ($statusCode && defined(HttpUtility::class . '::HTTP_STATUS_' . $statusCode)) { - HttpUtility::redirect($redirectUrl, constant(HttpUtility::class . '::HTTP_STATUS_' . $statusCode)); - } else { - HttpUtility::redirect($redirectUrl, HttpUtility::HTTP_STATUS_301); - } - die; + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions()->removeAll(); + $row = $queryBuilder + ->select( + 'pages.uid', + 'sys_domain.redirectTo', + 'sys_domain.redirectHttpStatusCode', + 'sys_domain.prepend_params' + ) + ->from('pages') + ->from('sys_domain') + ->where( + $queryBuilder->expr()->eq('pages.uid', $queryBuilder->quoteIdentifier('sys_domain.pid')), + $queryBuilder->expr()->eq('sys_domain.hidden', 0), + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq( + 'sys_domain.domainName', + $queryBuilder->createNamedParameter($domain) + ), + $queryBuilder->expr()->eq( + 'sys_domain.domainName', + $queryBuilder->createNamedParameter($domain . '/') + ) + ), + QueryHelper::stripLogicalOperatorPrefix($this->where_hid_del), + QueryHelper::stripLogicalOperatorPrefix($this->where_groupAccess) + ) + ->setMaxResults(1) + ->execute() + ->fetch(); + + if (!$row) { + return ''; + } + + if ($row['redirectTo']) { + $redirectUrl = $row['redirectTo']; + if ($row['prepend_params']) { + $redirectUrl = rtrim($redirectUrl, '/'); + $prependStr = ltrim(substr($request_uri, strlen($path)), '/'); + $redirectUrl .= '/' . $prependStr; + } + $statusCode = (int)$row['redirectHttpStatusCode']; + if ($statusCode && defined(HttpUtility::class . '::HTTP_STATUS_' . $statusCode)) { + HttpUtility::redirect($redirectUrl, constant(HttpUtility::class . '::HTTP_STATUS_' . $statusCode)); } else { - return $row['uid']; + HttpUtility::redirect($redirectUrl, HttpUtility::HTTP_STATUS_301); } + die; + } else { + return $row['uid']; } - return ''; } /** @@ -897,9 +1010,20 @@ class PageRepository } // Get pageRec if not supplied: if (!is_array($pageRec)) { - $res = $this->getDatabaseConnection()->exec_SELECTquery('uid,pid,doktype,mount_pid,mount_pid_ol,t3ver_state', 'pages', 'uid=' . (int)$pageId . ' AND pages.deleted=0 AND pages.doktype<>255'); - $pageRec = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + $pageRec = $queryBuilder->select('uid', 'pid', 'doktype', 'mount_pid', 'mount_pid_ol', 't3ver_state') + ->from('pages') + ->where( + $queryBuilder->expr()->eq('uid', (int)$pageId), + $queryBuilder->expr()->neq('doktype', 255) + ) + ->execute() + ->fetch(); + // Only look for version overlay if page record is not supplied; This assumes // that the input record is overlaid with preview version, if any! $this->versionOL('pages', $pageRec); @@ -912,9 +1036,20 @@ class PageRepository $mount_pid = (int)$pageRec['mount_pid']; if (is_array($pageRec) && (int)$pageRec['doktype'] === self::DOKTYPE_MOUNTPOINT && $mount_pid > 0 && !in_array($mount_pid, $prevMountPids, true)) { // Get the mount point record (to verify its general existence): - $res = $this->getDatabaseConnection()->exec_SELECTquery('uid,pid,doktype,mount_pid,mount_pid_ol,t3ver_state', 'pages', 'uid=' . $mount_pid . ' AND pages.deleted=0 AND pages.doktype<>255'); - $mountRec = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + $mountRec = $queryBuilder->select('uid', 'pid', 'doktype', 'mount_pid', 'mount_pid_ol', 't3ver_state') + ->from('pages') + ->where( + $queryBuilder->expr()->eq('uid', $mount_pid), + $queryBuilder->expr()->neq('doktype', 255) + ) + ->execute() + ->fetch(); + $this->versionOL('pages', $mountRec); if (is_array($mountRec)) { // Look for recursive mount point: @@ -957,16 +1092,26 @@ class PageRepository { $uid = (int)$uid; if (is_array($GLOBALS['TCA'][$table]) && $uid > 0) { - $res = $this->getDatabaseConnection()->exec_SELECTquery('*', $table, 'uid = ' . $uid . $this->enableFields($table)); - $row = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + $row = $queryBuilder->select('*') + ->from($table) + ->where($queryBuilder->expr()->eq('uid', $uid)) + ->execute() + ->fetch(); + if ($row) { $this->versionOL($table, $row); if (is_array($row)) { if ($checkPage) { - $res = $this->getDatabaseConnection()->exec_SELECTquery('uid', 'pages', 'uid=' . (int)$row['pid'] . $this->enableFields('pages')); - $numRows = $this->getDatabaseConnection()->sql_num_rows($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('pages'); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + $numRows = (int)$queryBuilder->count('*') + ->from('pages') + ->where($queryBuilder->expr()->eq('uid', (int)$row['pid'])) + ->execute() + ->fetchColumn(); if ($numRows > 0) { return $row; } else { @@ -995,9 +1140,16 @@ class PageRepository { $uid = (int)$uid; if (isset($GLOBALS['TCA'][$table]) && is_array($GLOBALS['TCA'][$table]) && $uid > 0) { - $res = $this->getDatabaseConnection()->exec_SELECTquery($fields, $table, 'uid = ' . $uid . $this->deleteClause($table)); - $row = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + $row = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields, true)) + ->from($table) + ->where($queryBuilder->expr()->eq('uid', $uid)) + ->execute() + ->fetch(); + if ($row) { if (!$noWSOL) { $this->versionOL($table, $row); @@ -1025,14 +1177,42 @@ class PageRepository public function getRecordsByField($theTable, $theField, $theValue, $whereClause = '', $groupBy = '', $orderBy = '', $limit = '') { if (is_array($GLOBALS['TCA'][$theTable])) { - $res = $this->getDatabaseConnection()->exec_SELECTquery('*', $theTable, $theField . '=' . $this->getDatabaseConnection()->fullQuoteStr($theValue, $theTable) . $this->deleteClause($theTable) . ' ' . $whereClause, $groupBy, $orderBy, $limit); - $rows = array(); - while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($res)) { - if (is_array($row)) { - $rows[] = $row; + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($theTable); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + $queryBuilder->select('*') + ->from($theTable) + ->where($queryBuilder->expr()->eq($theField, $queryBuilder->createNamedParameter($theValue))); + + if ($whereClause !== '') { + $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($whereClause)); + } + + if ($groupBy !== '') { + $queryBuilder->groupBy(QueryHelper::parseGroupBy($groupBy)); + } + + if ($orderBy !== '') { + foreach (QueryHelper::parseOrderBy($orderBy) as $orderPair) { + list($fieldName, $order) = $orderPair; + $queryBuilder->addOrderBy($fieldName, $order); + } + } + + if ($limit !== '') { + if (strpos($limit, ',')) { + $limitOffsetAndMax = GeneralUtility::intExplode(',', $limit); + $queryBuilder->setFirstResult((int)$limitOffsetAndMax[0]); + $queryBuilder->setMaxResults((int)$limitOffsetAndMax[1]); + } else { + $queryBuilder->setMaxResults((int)$limit); } } - $this->getDatabaseConnection()->sql_free_result($res); + + $rows = $queryBuilder->execute()->fetchAll(); + if (!empty($rows)) { return $rows; } @@ -1118,7 +1298,7 @@ class PageRepository * @return string The clause starting like " AND ...=... AND ...=... * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::enableFields(), deleteClause() */ - public function enableFields($table, $show_hidden = -1, $ignore_array = array(), $noVersionPreview = false) + public function enableFields($table, $show_hidden = -1, $ignore_array = [], $noVersionPreview = false) { if ($show_hidden === -1 && is_object($this->getTypoScriptFrontendController())) { // If show_hidden was not set from outside and if TSFE is an object, set it @@ -1132,29 +1312,35 @@ class PageRepository } // If show_hidden was not changed during the previous evaluation, do it here. $ctrl = $GLOBALS['TCA'][$table]['ctrl']; - $query = ''; + $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('pages') + ->expr(); + $constraints = []; if (is_array($ctrl)) { // Delete field check: if ($ctrl['delete']) { - $query .= ' AND ' . $table . '.' . $ctrl['delete'] . '=0'; + $constraints[] = $expressionBuilder->eq($table . '.' . $ctrl['delete'], 0); } if ($ctrl['versioningWS']) { if (!$this->versioningPreview) { // Filter out placeholder records (new/moved/deleted items) // in case we are NOT in a versioning preview (that means we are online!) - $query .= ' AND ' . $table . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE); + $constraints[] = $expressionBuilder->lte( + $table . '.t3ver_state', + new VersionState(VersionState::DEFAULT_STATE) + ); } elseif ($table !== 'pages') { // show only records of live and of the current workspace // in case we are in a versioning preview - $query .= ' AND (' . - $table . '.t3ver_wsid=0 OR ' . - $table . '.t3ver_wsid=' . (int)$this->versioningWorkspaceId . - ')'; + $constraints[] = $expressionBuilder->orX( + $expressionBuilder->eq($table . '.t3ver_wsid', 0), + $expressionBuilder->eq($table . '.t3ver_wsid', (int)$this->versioningWorkspaceId) + ); } // Filter out versioned records if (!$noVersionPreview && empty($ignore_array['pid'])) { - $query .= ' AND ' . $table . '.pid<>-1'; + $constraints[] = $expressionBuilder->neq($table . '.pid', -1); } } @@ -1165,32 +1351,39 @@ class PageRepository if (!$this->versioningPreview || !$ctrl['versioningWS'] || $noVersionPreview) { if ($ctrl['enablecolumns']['disabled'] && !$show_hidden && !$ignore_array['disabled']) { $field = $table . '.' . $ctrl['enablecolumns']['disabled']; - $query .= ' AND ' . $field . '=0'; + $constraints[] = $expressionBuilder->eq($field, 0); } if ($ctrl['enablecolumns']['starttime'] && !$ignore_array['starttime']) { $field = $table . '.' . $ctrl['enablecolumns']['starttime']; - $query .= ' AND ' . $field . '<=' . $GLOBALS['SIM_ACCESS_TIME']; + $constraints[] = $expressionBuilder->lte($field, (int)$GLOBALS['SIM_ACCESS_TIME']); } if ($ctrl['enablecolumns']['endtime'] && !$ignore_array['endtime']) { $field = $table . '.' . $ctrl['enablecolumns']['endtime']; - $query .= ' AND (' . $field . '=0 OR ' . $field . '>' . $GLOBALS['SIM_ACCESS_TIME'] . ')'; + $constraints[] = $expressionBuilder->orX( + $expressionBuilder->eq($field, 0), + $expressionBuilder->gt($field, (int)$GLOBALS['SIM_ACCESS_TIME']) + ); } if ($ctrl['enablecolumns']['fe_group'] && !$ignore_array['fe_group']) { $field = $table . '.' . $ctrl['enablecolumns']['fe_group']; - $query .= $this->getMultipleGroupsWhereClause($field, $table); + $constraints[] = QueryHelper::stripLogicalOperatorPrefix( + $this->getMultipleGroupsWhereClause($field, $table) + ); } // Call hook functions for additional enableColumns // It is used by the extension ingmar_accessctrl which enables assigning more // than one usergroup to content and page records if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'])) { - $_params = array( + $_params = [ 'table' => $table, 'show_hidden' => $show_hidden, 'ignore_array' => $ignore_array, 'ctrl' => $ctrl - ); + ]; foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['addEnableColumns'] as $_funcRef) { - $query .= GeneralUtility::callUserFunction($_funcRef, $_params, $this); + $constraints[] = QueryHelper::stripLogicalOperatorPrefix( + GeneralUtility::callUserFunction($_funcRef, $_params, $this) + ); } } } @@ -1198,7 +1391,8 @@ class PageRepository } else { throw new \InvalidArgumentException('There is no entry in the $TCA array for the table "' . $table . '". This means that the function enableFields() is ' . 'called with an invalid table name as argument.', 1283790586); } - return $query; + + return empty($constraints) ? '' : ' AND ' . $expressionBuilder->andX(...$constraints); } /** @@ -1212,18 +1406,22 @@ class PageRepository */ public function getMultipleGroupsWhereClause($field, $table) { + $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable($table) + ->expr(); $memberGroups = GeneralUtility::intExplode(',', $this->getTypoScriptFrontendController()->gr_list); - $orChecks = array(); + $orChecks = []; // If the field is empty, then OK - $orChecks[] = $field . '=\'\''; + $orChecks[] = $expressionBuilder->eq($field, $expressionBuilder->literal('')); // If the field is NULL, then OK - $orChecks[] = $field . ' IS NULL'; - // If the field contsains zero, then OK - $orChecks[] = $field . '=\'0\''; + $orChecks[] = $expressionBuilder->isNull($field); + // If the field contains zero, then OK + $orChecks[] = $expressionBuilder->eq($field, 0); foreach ($memberGroups as $value) { - $orChecks[] = $this->getDatabaseConnection()->listQuery($field, $value, $table); + $orChecks[] = $expressionBuilder->inSet($field, $expressionBuilder->literal($value)); } - return ' AND (' . implode(' OR ', $orChecks) . ')'; + + return' AND (' . $expressionBuilder->orX(...$orChecks) . ')'; } /********************** @@ -1405,9 +1603,15 @@ class PageRepository } // Find pointed-to record. if ($moveID) { - $res = $this->getDatabaseConnection()->exec_SELECTquery(implode(',', array_keys($this->purgeComputedProperties($row))), $table, 'uid=' . (int)$moveID . $this->enableFields($table)); - $origRow = $this->getDatabaseConnection()->sql_fetch_assoc($res); - $this->getDatabaseConnection()->sql_free_result($res); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + $origRow = $queryBuilder->select(...array_keys($this->purgeComputedProperties($row))) + ->from($table) + ->where($queryBuilder->expr()->eq('uid', (int)$moveID)) + ->setMaxResults(1) + ->execute() + ->fetch(); + if ($origRow) { $row = $origRow; return true; @@ -1432,10 +1636,23 @@ class PageRepository $workspace = (int)$this->versioningWorkspaceId; if (!empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $workspace !== 0) { // Select workspace version of record: - $row = $this->getDatabaseConnection()->exec_SELECTgetSingleRow($fields, $table, 'pid<>-1 AND - t3ver_state=' . new VersionState(VersionState::MOVE_PLACEHOLDER) . ' AND - t3ver_move_id=' . (int)$uid . ' AND - t3ver_wsid=' . (int)$workspace . $this->deleteClause($table)); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + $row = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields, true)) + ->from($table) + ->where( + $queryBuilder->expr()->neq('pid', -1), + $queryBuilder->expr()->eq('t3ver_state', new VersionState(VersionState::MOVE_PLACEHOLDER)), + $queryBuilder->expr()->eq('t3ver_move_id', (int)$uid), + $queryBuilder->expr()->eq('t3ver_wsid', (int)$workspace) + ) + ->setMaxResults(1) + ->execute() + ->fetch(); + if (is_array($row)) { return $row; } @@ -1460,18 +1677,40 @@ class PageRepository if ($workspace !== 0 && !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])) { $workspace = (int)$workspace; $uid = (int)$uid; - // Setting up enableFields for version record - $enFields = $this->enableFields($table, -1, array(), true); // Select workspace version of record, only testing for deleted. - $newrow = $this->getDatabaseConnection()->exec_SELECTgetSingleRow($fields, $table, 'pid=-1 AND - t3ver_oid=' . $uid . ' AND - t3ver_wsid=' . $workspace . $this->deleteClause($table)); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->getRestrictions() + ->removeAll() + ->add(GeneralUtility::makeInstance(DeletedRestriction::class)); + + $newrow = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields, true)) + ->from($table) + ->where( + $queryBuilder->expr()->eq('pid', -1), + $queryBuilder->expr()->eq('t3ver_oid', $uid), + $queryBuilder->expr()->eq('t3ver_wsid', $workspace) + ) + ->setMaxResults(1) + ->execute() + ->fetch(); + // If version found, check if it could have been selected with enableFields on // as well: + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table); + $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class)); + // Remove the frontend workspace restriction because we are testing a version record + $queryBuilder->getRestrictions()->removeByType(FrontendWorkspaceRestriction::class); + $queryBuilder->select('uid') + ->from($table) + ->setMaxResults(1); + if (is_array($newrow)) { - if ($bypassEnableFieldsCheck || $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', $table, 'pid=-1 AND - t3ver_oid=' . $uid . ' AND - t3ver_wsid=' . $workspace . $enFields)) { + $queryBuilder->where( + $queryBuilder->expr()->eq('pid', -1), + $queryBuilder->expr()->eq('t3ver_oid', $uid), + $queryBuilder->expr()->eq('t3ver_wsid', $workspace) + ); + if ($bypassEnableFieldsCheck || $queryBuilder->execute()->fetchColumn()) { // Return offline version, tested for its enableFields. return $newrow; } else { @@ -1481,7 +1720,8 @@ class PageRepository } else { // OK, so no workspace version was found. Then check if online version can be // selected with full enable fields and if so, return 1: - if ($bypassEnableFieldsCheck || $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', $table, 'uid=' . $uid . $enFields)) { + $queryBuilder->where($queryBuilder->expr()->eq('uid', $uid)); + if ($bypassEnableFieldsCheck || $queryBuilder->execute()->fetchColumn()) { // Means search was done, but no version found. return 1; } else { @@ -1511,7 +1751,17 @@ class PageRepository } else { if ($wsid > 0) { // No $GLOBALS['TCA'] yet! - $ws = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'sys_workspace', 'uid=' . (int)$wsid . ' AND deleted=0'); + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) + ->getQueryBuilderForTable('sys_workspace'); + $queryBuilder->getRestrictions()->removeAll(); + $ws = $queryBuilder->select('*') + ->from('sys_workspace') + ->where( + $queryBuilder->expr()->eq('uid', (int)$wsid), + $queryBuilder->expr()->eq('deleted', 0) + ) + ->execute() + ->fetch(); if (!is_array($ws)) { return false; } @@ -1634,16 +1884,6 @@ class PageRepository return $shouldFieldBeOverlaid; } - /** - * Returns the database connection - * - * @return \TYPO3\CMS\Core\Database\DatabaseConnection - */ - protected function getDatabaseConnection() - { - return $GLOBALS['TYPO3_DB']; - } - /** * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController */ diff --git a/typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php new file mode 100644 index 000000000000..01f6c96fcd8d --- /dev/null +++ b/typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php @@ -0,0 +1,519 @@ +<?php +namespace TYPO3\CMS\Frontend\Tests\Functional\ContentObject; + +/* + * 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\TypoScript\TemplateService; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; +use TYPO3\CMS\Frontend\Page\PageRepository; + +/** + * Testcase for TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer + */ +class ContentObjectRendererTest extends \TYPO3\CMS\Core\Tests\FunctionalTestCase +{ + /** + * @var ContentObjectRenderer + */ + protected $subject; + + protected function setUp() + { + parent::setUp(); + + $typoScriptFrontendController = GeneralUtility::makeInstance( + TypoScriptFrontendController::class, + null, + 1, + 0 + ); + $typoScriptFrontendController->sys_page = GeneralUtility::makeInstance(PageRepository::class); + $typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class); + $GLOBALS['TSFE'] = $typoScriptFrontendController; + + $this->subject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + } + + /** + * Data provider for the getQuery test + * + * @return array multi-dimensional array with the second level like this: + * @see getQuery + */ + public function getQueryDataProvider(): array + { + $data = [ + 'testing empty conf' => [ + 'tt_content', + [], + [ + 'SELECT' => '*' + ] + ], + 'testing #17284: adding uid/pid for workspaces' => [ + 'tt_content', + [ + 'selectFields' => 'header,bodytext' + ], + [ + 'SELECT' => 'header,bodytext, tt_content.uid as uid, tt_content.pid as pid, tt_content.t3ver_state as t3ver_state' + ] + ], + 'testing #17284: no need to add' => [ + 'tt_content', + [ + 'selectFields' => 'tt_content.*' + ], + [ + 'SELECT' => 'tt_content.*' + ] + ], + 'testing #17284: no need to add #2' => [ + 'tt_content', + [ + 'selectFields' => '*' + ], + [ + 'SELECT' => '*' + ] + ], + 'testing #29783: joined tables, prefix tablename' => [ + 'tt_content', + [ + 'selectFields' => 'tt_content.header,be_users.username', + 'join' => 'be_users ON tt_content.cruser_id = be_users.uid' + ], + [ + 'SELECT' => 'tt_content.header,be_users.username, tt_content.uid as uid, tt_content.pid as pid, tt_content.t3ver_state as t3ver_state' + ] + ], + 'testing #34152: single count(*), add nothing' => [ + 'tt_content', + [ + 'selectFields' => 'count(*)' + ], + [ + 'SELECT' => 'count(*)' + ] + ], + 'testing #34152: single max(crdate), add nothing' => [ + 'tt_content', + [ + 'selectFields' => 'max(crdate)' + ], + [ + 'SELECT' => 'max(crdate)' + ] + ], + 'testing #34152: single min(crdate), add nothing' => [ + 'tt_content', + [ + 'selectFields' => 'min(crdate)' + ], + [ + 'SELECT' => 'min(crdate)' + ] + ], + 'testing #34152: single sum(is_siteroot), add nothing' => [ + 'tt_content', + [ + 'selectFields' => 'sum(is_siteroot)' + ], + [ + 'SELECT' => 'sum(is_siteroot)' + ] + ], + 'testing #34152: single avg(crdate), add nothing' => [ + 'tt_content', + [ + 'selectFields' => 'avg(crdate)' + ], + [ + 'SELECT' => 'avg(crdate)' + ] + ] + ]; + + return $data; + } + + /** + * Check if sanitizeSelectPart works as expected + * + * @dataProvider getQueryDataProvider + * @test + * @param string $table + * @param array $conf + * @param array $expected + */ + public function getQuery(string $table, array $conf, array $expected) + { + $GLOBALS['TCA'] = [ + 'pages' => [ + 'ctrl' => [ + 'enablecolumns' => [ + 'disabled' => 'hidden' + ] + ] + ], + 'tt_content' => [ + 'ctrl' => [ + 'enablecolumns' => [ + 'disabled' => 'hidden' + ], + 'versioningWS' => true + ] + ], + ]; + + $result = $this->subject->getQuery($table, $conf, true); + foreach ($expected as $field => $value) { + $this->assertEquals($value, $result[$field]); + } + } + + /** + * @test + */ + public function getQueryCallsGetTreeListWithNegativeValuesIfRecursiveIsSet() + { + $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList']); + $this->subject->start([], 'tt_content'); + + $conf = [ + 'recursive' => '15', + 'pidInList' => '16, -35' + ]; + + $this->subject->expects($this->at(0)) + ->method('getTreeList') + ->with(-16, 15) + ->will($this->returnValue('15,16')); + $this->subject->expects($this->at(1)) + ->method('getTreeList') + ->with(-35, 15) + ->will($this->returnValue('15,35')); + + $this->subject->getQuery('tt_content', $conf, true); + } + + /** + * @test + */ + public function getQueryCallsGetTreeListWithCurrentPageIfThisIsSet() + { + $GLOBALS['TSFE']->id = 27; + + $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, ['getTreeList']); + $this->subject->start([], 'tt_content'); + + $conf = [ + 'pidInList' => 'this', + 'recursive' => '4' + ]; + + $this->subject->expects($this->once()) + ->method('getTreeList') + ->with(-27) + ->will($this->returnValue('27')); + + $this->subject->getQuery('tt_content', $conf, true); + } + + /** + * @return array + */ + public function getWhereReturnCorrectQueryDataProvider() + { + return [ + [ + [ + 'tt_content' => [ + 'ctrl' => [ + ], + 'columns' => [ + ] + ], + ], + 'tt_content', + [ + 'uidInList' => '42', + 'pidInList' => 43, + 'where' => 'tt_content.cruser_id=5', + 'groupBy' => 'tt_content.title', + 'orderBy' => 'tt_content.sorting', + ], + 'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5 GROUP BY tt_content.title ORDER BY tt_content.sorting', + ], + [ + [ + 'tt_content' => [ + 'ctrl' => [ + 'delete' => 'deleted', + 'enablecolumns' => [ + 'disabled' => 'hidden', + 'starttime' => 'startdate', + 'endtime' => 'enddate', + ], + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l18n_parent', + ], + 'columns' => [ + ] + ], + ], + 'tt_content', + [ + 'uidInList' => 42, + 'pidInList' => 43, + 'where' => 'tt_content.cruser_id=5', + 'groupBy' => 'tt_content.title', + 'orderBy' => 'tt_content.sorting', + ], + 'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5 AND (tt_content.sys_language_uid = 13)%s GROUP BY tt_content.title ORDER BY tt_content.sorting', + ], + [ + [ + 'tt_content' => [ + 'ctrl' => [ + 'languageField' => 'sys_language_uid', + 'transOrigPointerField' => 'l18n_parent', + ], + 'columns' => [ + ] + ], + ], + 'tt_content', + [ + 'uidInList' => 42, + 'pidInList' => 43, + 'where' => 'tt_content.cruser_id=5', + 'languageField' => 0, + ], + 'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5', + ], + ]; + } + + /** + * @test + * @param array $tca + * @param string $table + * @param array $configuration + * @param string $expectedResult + * @dataProvider getWhereReturnCorrectQueryDataProvider + */ + public function getWhereReturnCorrectQuery(array $tca, string $table, array $configuration, string $expectedResult) + { + $GLOBALS['TCA'] = $tca; + $GLOBALS['SIM_ACCESS_TIME'] = '4242'; + $GLOBALS['TSFE']->sys_language_content = 13; + /** @var \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer $contentObjectRenderer */ + $contentObjectRenderer = $this->getMockBuilder(ContentObjectRenderer::class) + ->setMethods(['checkPidArray']) + ->getMock(); + $contentObjectRenderer->expects($this->any()) + ->method('checkPidArray') + ->willReturn(explode(',', $configuration['pidInList'])); + + // Embed the enable fields string into the expected result as the database + // connection is still unconfigured when the data provider is being run. + $expectedResult = sprintf($expectedResult, $GLOBALS['TSFE']->sys_page->enableFields($table)); + + $this->assertSame($expectedResult, $contentObjectRenderer->getWhere($table, $configuration)); + } + + /** + * @return array + */ + public function typolinkReturnsCorrectLinksForPagesDataProvider() + { + return array( + 'Link to page' => array( + 'My page', + array( + 'parameter' => 42, + ), + array( + 'uid' => 42, + 'title' => 'Page title', + ), + '<a href="index.php?id=42">My page</a>', + ), + 'Link to page without link text' => array( + '', + array( + 'parameter' => 42, + ), + array( + 'uid' => 42, + 'title' => 'Page title', + ), + '<a href="index.php?id=42">Page title</a>', + ), + 'Link to page with attributes' => array( + 'My page', + array( + 'parameter' => '42', + 'ATagParams' => 'class="page-class"', + 'target' => '_self', + 'title' => 'Link to internal page', + ), + array( + 'uid' => 42, + 'title' => 'Page title', + ), + '<a href="index.php?id=42" title="Link to internal page" target="_self" class="page-class">My page</a>', + ), + 'Link to page with attributes in parameter' => array( + 'My page', + array( + 'parameter' => '42 _self page-class "Link to internal page"', + ), + array( + 'uid' => 42, + 'title' => 'Page title', + ), + '<a href="index.php?id=42" title="Link to internal page" target="_self" class="page-class">My page</a>', + ), + 'Link to page with bold tag in title' => array( + '', + array( + 'parameter' => 42, + ), + array( + 'uid' => 42, + 'title' => 'Page <b>title</b>', + ), + '<a href="index.php?id=42">Page <b>title</b></a>', + ), + 'Link to page with script tag in title' => array( + '', + array( + 'parameter' => 42, + ), + array( + 'uid' => 42, + 'title' => '<script>alert(123)</script>Page title', + ), + '<a href="index.php?id=42"><script>alert(123)</script>Page title</a>', + ), + ); + } + + /** + * @test + * @param string $linkText + * @param array $configuration + * @param array $pageArray + * @param string $expectedResult + * @dataProvider typolinkReturnsCorrectLinksForPagesDataProvider + */ + public function typolinkReturnsCorrectLinksForPages($linkText, $configuration, $pageArray, $expectedResult) + { + $pageRepositoryMockObject = $this->getMockBuilder(PageRepository::class) + ->setMethods(['getPage']) + ->getMock(); + $pageRepositoryMockObject->expects($this->any())->method('getPage')->willReturn($pageArray); + + $typoScriptFrontendController = GeneralUtility::makeInstance( + TypoScriptFrontendController::class, + null, + 1, + 0 + ); + $typoScriptFrontendController->config = [ + 'config' => [], + 'mainScript' => 'index.php', + ]; + $typoScriptFrontendController->sys_page = $pageRepositoryMockObject; + $typoScriptFrontendController->tmpl = GeneralUtility::makeInstance(TemplateService::class); + $typoScriptFrontendController->tmpl->setup = [ + 'lib.' => [ + 'parseFunc.' => $this->getLibParseFunc(), + ], + ]; + $GLOBALS['TSFE'] = $typoScriptFrontendController; + + $subject = GeneralUtility::makeInstance(ContentObjectRenderer::class); + $this->assertEquals($expectedResult, $subject->typoLink($linkText, $configuration)); + } + + /** + * @return array + */ + protected function getLibParseTarget() + { + return [ + 'override' => '', + 'override.' => [ + 'if.' => [ + 'isTrue.' => [ + 'data' => 'TSFE:dtdAllowsFrames', + ], + ], + ], + ]; + } + + /** + * @return array + */ + protected function getLibParseFunc() + { + return [ + 'makelinks' => '1', + 'makelinks.' => [ + 'http.' => [ + 'keep' => '{$styles.content.links.keep}', + 'extTarget' => '', + 'extTarget.' => $this->getLibParseTarget(), + 'mailto.' => [ + 'keep' => 'path', + ], + ], + ], + 'tags' => [ + 'link' => 'TEXT', + 'link.' => [ + 'current' => '1', + 'typolink.' => [ + 'parameter.' => [ + 'data' => 'parameters : allParams', + ], + 'extTarget.' => $this->getLibParseTarget(), + 'target.' => $this->getLibParseTarget(), + ], + 'parseFunc.' => [ + 'constants' => '1', + ], + ], + ], + + 'allowTags' => 'a, abbr, acronym, address, article, aside, b, bdo, big, blockquote, br, caption, center, cite, code, col, colgroup, dd, del, dfn, dl, div, dt, em, font, footer, header, h1, h2, h3, h4, h5, h6, hr, i, img, ins, kbd, label, li, link, meta, nav, ol, p, pre, q, samp, sdfield, section, small, span, strike, strong, style, sub, sup, table, thead, tbody, tfoot, td, th, tr, title, tt, u, ul, var', + 'denyTags' => '*', + 'sword' => '<span class="csc-sword">|</span>', + 'constants' => '1', + 'nonTypoTagStdWrap.' => [ + 'HTMLparser' => '1', + 'HTMLparser.' => [ + 'keepNonMatchedTags' => '1', + 'htmlSpecialChars' => '2', + ], + ], + ]; + } +} diff --git a/typo3/sysext/frontend/Tests/Functional/Fixtures/pages.xml b/typo3/sysext/frontend/Tests/Functional/Fixtures/pages.xml index b149d2bb2e0e..ff8cb0c32f37 100644 --- a/typo3/sysext/frontend/Tests/Functional/Fixtures/pages.xml +++ b/typo3/sysext/frontend/Tests/Functional/Fixtures/pages.xml @@ -70,6 +70,30 @@ <deleted>0</deleted> <perms_everybody>15</perms_everybody> </pages> + <pages> + <uid>11</uid> + <pid>0</pid> + <title>Workspace Root</title> + <deleted>0</deleted> + <t3ver_oid>0</t3ver_oid> + <t3ver_id>0</t3ver_id> + <t3ver_wsid>987654321</t3ver_wsid> + <t3ver_label>INITIAL PLACEHOLDER</t3ver_label> + <t3ver_state>1</t3ver_state> + <perms_everybody>15</perms_everybody> + </pages> + <pages> + <uid>12</uid> + <pid>-1</pid> + <title>Workspace Root</title> + <deleted>0</deleted> + <t3ver_oid>11</t3ver_oid> + <t3ver_id>1</t3ver_id> + <t3ver_wsid>987654321</t3ver_wsid> + <t3ver_label>First draft version</t3ver_label> + <t3ver_state>-1</t3ver_state> + <perms_everybody>15</perms_everybody> + </pages> <pages_language_overlay> <uid>901</uid> diff --git a/typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php b/typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php index c9f181a26870..4e105dfa8a04 100644 --- a/typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php +++ b/typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php @@ -14,8 +14,12 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\Page; * The TYPO3 project - inspiring people to share! */ +use Prophecy\Argument; +use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Tests\FunctionalTestCase; +use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Frontend\Page\PageRepository; +use TYPO3\CMS\Frontend\Page\PageRepositoryGetPageHookInterface; /** * Test case @@ -32,6 +36,7 @@ class PageRepositoryTest extends FunctionalTestCase protected function setUp() { parent::setUp(); + $GLOBALS['TSFE']->gr_list = ''; $this->importDataSet(__DIR__ . '/../Fixtures/pages.xml'); $this->pageRepo = new PageRepository(); $this->pageRepo->init(false); @@ -281,6 +286,256 @@ class PageRepositoryTest extends FunctionalTestCase $this->assertEquals('Attrappe 1-3-9', $row['title']); } + /** + * Tests whether the getPage Hook is called correctly. + * + * @test + */ + public function isGetPageHookCalled() + { + // Create a hook mock object + $getPageHookProphet = $this->prophesize(\stdClass::class); + $getPageHookProphet->willImplement(PageRepositoryGetPageHookInterface::class); + $getPageHookProphet->getPage_preProcess(42, false, Argument::type(PageRepository::class))->shouldBeCalled(); + $getPageHookMock = $getPageHookProphet->reveal(); + $className = get_class($getPageHookMock); + + // Register hook mock object + GeneralUtility::addInstance($className, $getPageHookMock); + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage'][] = $className; + $this->pageRepo->getPage(42, false); + } + + /** + * @test + */ + public function initSetsPublicPropertyCorrectlyForWorkspacePreview() + { + $this->pageRepo->versioningPreview = true; + $this->pageRepo->versioningWorkspaceId = 2; + $this->pageRepo->init(false); + + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages'); + + $expectedSQL = sprintf( + ' AND (%s = 0) AND ((%s = 0) OR (%s = 2))', + $connection->quoteIdentifier('pages.deleted'), + $connection->quoteIdentifier('pages.t3ver_wsid'), + $connection->quoteIdentifier('pages.t3ver_wsid') + ); + + $this->assertSame($expectedSQL, $this->pageRepo->where_hid_del); + } + + /** + * @test + */ + public function initSetsPublicPropertyCorrectlyForLive() + { + $GLOBALS['SIM_ACCESS_TIME'] = 123; + + $this->pageRepo->versioningPreview = false; + $this->pageRepo->versioningWorkspaceId = 0; + $this->pageRepo->init(false); + + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages'); + $expectedSQL = sprintf( + ' AND (%s = 0) AND (%s <= 0) AND (%s = 0) AND (%s <= 123) AND ((%s = 0) OR (%s > 123))', + $connection->quoteIdentifier('pages.deleted'), + $connection->quoteIdentifier('pages.t3ver_state'), + $connection->quoteIdentifier('pages.hidden'), + $connection->quoteIdentifier('pages.starttime'), + $connection->quoteIdentifier('pages.endtime'), + $connection->quoteIdentifier('pages.endtime') + ); + + $this->assertSame($expectedSQL, $this->pageRepo->where_hid_del); + } + + //////////////////////////////// + // Tests concerning workspaces + //////////////////////////////// + + /** + * @test + */ + public function noPagesFromWorkspaceAreShownLive() + { + // initialization + $wsid = 987654321; + + // simulate calls from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->fetch_the_id() + $this->pageRepo->versioningPreview = false; + $this->pageRepo->versioningWorkspaceId = $wsid; + $this->pageRepo->init(false); + + $this->assertSame([], $this->pageRepo->getPage(11)); + } + + /** + * @test + */ + public function previewShowsPagesFromLiveAndCurrentWorkspace() + { + // initialization + $wsid = 987654321; + + // simulate calls from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->fetch_the_id() + $this->pageRepo->versioningPreview = true; + $this->pageRepo->versioningWorkspaceId = $wsid; + $this->pageRepo->init(false); + + $pageRec = $this->pageRepo->getPage(11); + + $this->assertSame(11, $pageRec['uid']); + $this->assertSame(11, $pageRec['t3ver_oid']); + $this->assertSame(987654321, $pageRec['t3ver_wsid']); + $this->assertSame(-1, $pageRec['t3ver_state']); + $this->assertSame('First draft version', $pageRec['t3ver_label']); + } + + /** + * @test + */ + public function getWorkspaceVersionReturnsTheCorrectMethod() + { + // initialization + $wsid = 987654321; + + // simulate calls from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->fetch_the_id() + $this->pageRepo->versioningPreview = true; + $this->pageRepo->versioningWorkspaceId = $wsid; + $this->pageRepo->init(false); + + $pageRec = $this->pageRepo->getWorkspaceVersionOfRecord($wsid, 'pages', 11); + + $this->assertSame(12, $pageRec['uid']); + $this->assertSame(11, $pageRec['t3ver_oid']); + $this->assertSame(987654321, $pageRec['t3ver_wsid']); + $this->assertSame(-1, $pageRec['t3ver_state']); + $this->assertSame('First draft version', $pageRec['t3ver_label']); + } + + //////////////////////////////// + // Tests concerning versioning + //////////////////////////////// + + /** + * @test + */ + public function enableFieldsHidesVersionedRecordsAndPlaceholders() + { + $table = $this->getUniqueId('aTable'); + $GLOBALS['TCA'][$table] = [ + 'ctrl' => [ + 'versioningWS' => true + ] + ]; + + $this->pageRepo->versioningPreview = false; + $this->pageRepo->init(false); + + $conditions = $this->pageRepo->enableFields($table); + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + + $this->assertThat( + $conditions, + $this->stringContains(' AND (' . $connection->quoteIdentifier($table . '.t3ver_state') . ' <= 0)'), + 'Versioning placeholders' + ); + $this->assertThat( + $conditions, + $this->stringContains(' AND (' . $connection->quoteIdentifier($table . '.pid') . ' <> -1)'), + 'Records from page -1' + ); + } + + /** + * @test + */ + public function enableFieldsDoesNotHidePlaceholdersInPreview() + { + $table = $this->getUniqueId('aTable'); + $GLOBALS['TCA'][$table] = [ + 'ctrl' => [ + 'versioningWS' => true + ] + ]; + + $this->pageRepo->versioningPreview = true; + $this->pageRepo->init(false); + + $conditions = $this->pageRepo->enableFields($table); + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + + $this->assertThat( + $conditions, + $this->logicalNot($this->stringContains(' AND (' . $connection->quoteIdentifier($table . '.t3ver_state') . ' <= 0)')), + 'No versioning placeholders' + ); + $this->assertThat( + $conditions, + $this->stringContains(' AND (' . $connection->quoteIdentifier($table . '.pid') . ' <> -1)'), + 'Records from page -1' + ); + } + + /** + * @test + */ + public function enableFieldsDoesFilterToCurrentAndLiveWorkspaceForRecordsInPreview() + { + $table = $this->getUniqueId('aTable'); + $GLOBALS['TCA'][$table] = [ + 'ctrl' => [ + 'versioningWS' => true + ] + ]; + + $this->pageRepo->versioningPreview = true; + $this->pageRepo->versioningWorkspaceId = 2; + $this->pageRepo->init(false); + + $conditions = $this->pageRepo->enableFields($table); + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + + $this->assertThat( + $conditions, + $this->stringContains(' AND ((' . $connection->quoteIdentifier($table . '.t3ver_wsid') . ' = 0) OR (' . $connection->quoteIdentifier($table . '.t3ver_wsid') . ' = 2))'), + 'No versioning placeholders' + ); + } + + /** + * @test + */ + public function enableFieldsDoesNotHideVersionedRecordsWhenCheckingVersionOverlays() + { + $table = $this->getUniqueId('aTable'); + $GLOBALS['TCA'][$table] = [ + 'ctrl' => [ + 'versioningWS' => true + ] + ]; + + $this->pageRepo->versioningPreview = true; + $this->pageRepo->init(false); + + $conditions = $this->pageRepo->enableFields($table, -1, [], true); + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table); + + $this->assertThat( + $conditions, + $this->logicalNot($this->stringContains(' AND (' . $connection->quoteIdentifier($table . '.t3ver_state') . ' <= 0)')), + 'No versioning placeholders' + ); + $this->assertThat( + $conditions, + $this->logicalNot($this->stringContains(' AND (' . $connection->quoteIdentifier($table . '.pid') . ' <> -1)')), + 'No necords from page -1' + ); + } + protected function assertOverlayRow($row) { $this->assertInternalType('array', $row); diff --git a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php index 5b922725a4f2..8222836d3bed 100644 --- a/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php +++ b/typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php @@ -54,7 +54,6 @@ use TYPO3\CMS\Frontend\ContentObject\TextContentObject; use TYPO3\CMS\Frontend\ContentObject\UserContentObject; use TYPO3\CMS\Frontend\ContentObject\UserInternalContentObject; use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; -use TYPO3\CMS\Frontend\Page\PageRepository; use TYPO3\CMS\Frontend\Tests\Unit\ContentObject\Fixtures\PageRepositoryFixture; /** @@ -1013,212 +1012,6 @@ class ContentObjectRendererTest extends UnitTestCase $this->subject->_call('replacement', $content, $conf)); } - /** - * Data provider for the getQuery test - * - * @return array multi-dimensional array with the second level like this: - * @see getQuery - */ - public function getQueryDataProvider() - { - $data = array( - 'testing empty conf' => array( - 'tt_content', - array(), - array( - 'SELECT' => '*' - ) - ), - 'testing #17284: adding uid/pid for workspaces' => array( - 'tt_content', - array( - 'selectFields' => 'header,bodytext' - ), - array( - 'SELECT' => 'header,bodytext, tt_content.uid as uid, tt_content.pid as pid, tt_content.t3ver_state as t3ver_state' - ) - ), - 'testing #17284: no need to add' => array( - 'tt_content', - array( - 'selectFields' => 'tt_content.*' - ), - array( - 'SELECT' => 'tt_content.*' - ) - ), - 'testing #17284: no need to add #2' => array( - 'tt_content', - array( - 'selectFields' => '*' - ), - array( - 'SELECT' => '*' - ) - ), - 'testing #29783: joined tables, prefix tablename' => array( - 'tt_content', - array( - 'selectFields' => 'tt_content.header,be_users.username', - 'join' => 'be_users ON tt_content.cruser_id = be_users.uid' - ), - array( - 'SELECT' => 'tt_content.header,be_users.username, tt_content.uid as uid, tt_content.pid as pid, tt_content.t3ver_state as t3ver_state' - ) - ), - 'testing #34152: single count(*), add nothing' => array( - 'tt_content', - array( - 'selectFields' => 'count(*)' - ), - array( - 'SELECT' => 'count(*)' - ) - ), - 'testing #34152: single max(crdate), add nothing' => array( - 'tt_content', - array( - 'selectFields' => 'max(crdate)' - ), - array( - 'SELECT' => 'max(crdate)' - ) - ), - 'testing #34152: single min(crdate), add nothing' => array( - 'tt_content', - array( - 'selectFields' => 'min(crdate)' - ), - array( - 'SELECT' => 'min(crdate)' - ) - ), - 'testing #34152: single sum(is_siteroot), add nothing' => array( - 'tt_content', - array( - 'selectFields' => 'sum(is_siteroot)' - ), - array( - 'SELECT' => 'sum(is_siteroot)' - ) - ), - 'testing #34152: single avg(crdate), add nothing' => array( - 'tt_content', - array( - 'selectFields' => 'avg(crdate)' - ), - array( - 'SELECT' => 'avg(crdate)' - ) - ) - ); - return $data; - } - - /** - * Check if sanitizeSelectPart works as expected - * - * @dataProvider getQueryDataProvider - * @test - */ - public function getQuery($table, $conf, $expected) - { - $GLOBALS['TCA'] = array( - 'pages' => array( - 'ctrl' => array( - 'enablecolumns' => array( - 'disabled' => 'hidden' - ) - ) - ), - 'tt_content' => array( - 'ctrl' => array( - 'enablecolumns' => array( - 'disabled' => 'hidden' - ), - 'versioningWS' => true - ) - ), - ); - $result = $this->subject->getQuery($table, $conf, true); - foreach ($expected as $field => $value) { - $this->assertEquals($value, $result[$field]); - } - } - - /** - * @test - */ - public function getQueryCallsGetTreeListWithNegativeValuesIfRecursiveIsSet() - { - $GLOBALS['TCA'] = array( - 'pages' => array( - 'ctrl' => array( - 'enablecolumns' => array( - 'disabled' => 'hidden' - ) - ) - ), - 'tt_content' => array( - 'ctrl' => array( - 'enablecolumns' => array( - 'disabled' => 'hidden' - ) - ) - ), - ); - $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, array('getTreeList')); - $this->subject->start(array(), 'tt_content'); - $conf = array( - 'recursive' => '15', - 'pidInList' => '16, -35' - ); - $this->subject->expects($this->at(0)) - ->method('getTreeList') - ->with(-16, 15) - ->will($this->returnValue('15,16')); - $this->subject->expects($this->at(1)) - ->method('getTreeList') - ->with(-35, 15) - ->will($this->returnValue('15,35')); - $this->subject->getQuery('tt_content', $conf, true); - } - - /** - * @test - */ - public function getQueryCallsGetTreeListWithCurrentPageIfThisIsSet() - { - $GLOBALS['TCA'] = array( - 'pages' => array( - 'ctrl' => array( - 'enablecolumns' => array( - 'disabled' => 'hidden' - ) - ) - ), - 'tt_content' => array( - 'ctrl' => array( - 'enablecolumns' => array( - 'disabled' => 'hidden' - ) - ) - ), - ); - $this->subject = $this->getAccessibleMock(ContentObjectRenderer::class, array('getTreeList')); - $GLOBALS['TSFE']->id = 27; - $this->subject->start(array(), 'tt_content'); - $conf = array( - 'pidInList' => 'this', - 'recursive' => '4' - ); - $this->subject->expects($this->once()) - ->method('getTreeList') - ->with(-27) - ->will($this->returnValue('27')); - $this->subject->getQuery('tt_content', $conf, true); - } - /** * Data provider for calcAge. * @@ -3084,84 +2877,6 @@ class ContentObjectRendererTest extends UnitTestCase $this->assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration)); } - /** - * @return array - */ - public function typolinkReturnsCorrectLinksForPagesDataProvider() - { - return array( - 'Link to page' => array( - 'My page', - array( - 'parameter' => 42, - ), - array( - 'uid' => 42, - 'title' => 'Page title', - ), - '<a href="index.php?id=42">My page</a>', - ), - 'Link to page without link text' => array( - '', - array( - 'parameter' => 42, - ), - array( - 'uid' => 42, - 'title' => 'Page title', - ), - '<a href="index.php?id=42">Page title</a>', - ), - 'Link to page with attributes' => array( - 'My page', - array( - 'parameter' => '42', - 'ATagParams' => 'class="page-class"', - 'target' => '_self', - 'title' => 'Link to internal page', - ), - array( - 'uid' => 42, - 'title' => 'Page title', - ), - '<a href="index.php?id=42" title="Link to internal page" target="_self" class="page-class">My page</a>', - ), - 'Link to page with attributes in parameter' => array( - 'My page', - array( - 'parameter' => '42 _self page-class "Link to internal page"', - ), - array( - 'uid' => 42, - 'title' => 'Page title', - ), - '<a href="index.php?id=42" title="Link to internal page" target="_self" class="page-class">My page</a>', - ), - 'Link to page with bold tag in title' => array( - '', - array( - 'parameter' => 42, - ), - array( - 'uid' => 42, - 'title' => 'Page <b>title</b>', - ), - '<a href="index.php?id=42">Page <b>title</b></a>', - ), - 'Link to page with script tag in title' => array( - '', - array( - 'parameter' => 42, - ), - array( - 'uid' => 42, - 'title' => '<script>alert(123)</script>Page title', - ), - '<a href="index.php?id=42"><script>alert(123)</script>Page title</a>', - ), - ); - } - /** * @param array $settings * @param string $linkText @@ -3278,41 +2993,6 @@ class ContentObjectRendererTest extends UnitTestCase ); } - /** - * @test - * @param string $linkText - * @param array $configuration - * @param array $pageArray - * @param string $expectedResult - * @dataProvider typolinkReturnsCorrectLinksForPagesDataProvider - */ - public function typolinkReturnsCorrectLinksForPages($linkText, $configuration, $pageArray, $expectedResult) - { - $pageRepositoryMockObject = $this->getMockBuilder(PageRepository::class) - ->setMethods(array('getPage')) - ->getMock(); - $pageRepositoryMockObject->expects($this->any())->method('getPage')->willReturn($pageArray); - $templateServiceObjectMock = $this->getMockBuilder(TemplateService::class) - ->setMethods(array('dummy')) - ->getMock(); - $templateServiceObjectMock->setup = array( - 'lib.' => array( - 'parseFunc.' => $this->getLibParseFunc(), - ), - ); - $typoScriptFrontendControllerMockObject = $this->createMock(TypoScriptFrontendController::class); - $typoScriptFrontendControllerMockObject->config = array( - 'config' => array(), - 'mainScript' => 'index.php', - ); - $typoScriptFrontendControllerMockObject->sys_page = $pageRepositoryMockObject; - $typoScriptFrontendControllerMockObject->tmpl = $templateServiceObjectMock; - $GLOBALS['TSFE'] = $typoScriptFrontendControllerMockObject; - $this->subject->_set('typoScriptFrontendController', $typoScriptFrontendControllerMockObject); - - $this->assertEquals($expectedResult, $this->subject->typoLink($linkText, $configuration)); - } - /** * @return array */ @@ -3557,102 +3237,6 @@ class ContentObjectRendererTest extends UnitTestCase ); } - /** - * @return array - */ - public function getWhereReturnCorrectQueryDataProvider() - { - return array( - array( - array( - 'tt_content' => array( - 'ctrl' => array( - ), - 'columns' => array( - ) - ), - ), - 'tt_content', - array( - 'uidInList' => '42', - 'pidInList' => 43, - 'where' => 'tt_content.cruser_id=5', - 'groupBy' => 'tt_content.title', - 'orderBy' => 'tt_content.sorting', - ), - 'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5 GROUP BY tt_content.title ORDER BY tt_content.sorting', - ), - array( - array( - 'tt_content' => array( - 'ctrl' => array( - 'delete' => 'deleted', - 'enablecolumns' => array( - 'disabled' => 'hidden', - 'starttime' => 'startdate', - 'endtime' => 'enddate', - ), - 'languageField' => 'sys_language_uid', - 'transOrigPointerField' => 'l18n_parent', - ), - 'columns' => array( - ) - ), - ), - 'tt_content', - array( - 'uidInList' => 42, - 'pidInList' => 43, - 'where' => 'tt_content.cruser_id=5', - 'groupBy' => 'tt_content.title', - 'orderBy' => 'tt_content.sorting', - ), - 'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5 AND (tt_content.sys_language_uid = 13) AND tt_content.deleted=0 AND tt_content.hidden=0 AND tt_content.startdate<=4242 AND (tt_content.enddate=0 OR tt_content.enddate>4242) GROUP BY tt_content.title ORDER BY tt_content.sorting', - ), - array( - array( - 'tt_content' => array( - 'ctrl' => array( - 'languageField' => 'sys_language_uid', - 'transOrigPointerField' => 'l18n_parent', - ), - 'columns' => array( - ) - ), - ), - 'tt_content', - array( - 'uidInList' => 42, - 'pidInList' => 43, - 'where' => 'tt_content.cruser_id=5', - 'languageField' => 0, - ), - 'WHERE tt_content.uid=42 AND tt_content.pid IN (43) AND tt_content.cruser_id=5', - ), - ); - } - - /** - * @test - * @param array $tca - * @param string $table - * @param array $configuration - * @param string $expectedResult - * @dataProvider getWhereReturnCorrectQueryDataProvider - */ - public function getWhereReturnCorrectQuery($tca, $table, $configuration, $expectedResult) - { - $GLOBALS['TCA'] = $tca; - $GLOBALS['SIM_ACCESS_TIME'] = '4242'; - $GLOBALS['TSFE']->sys_language_content = 13; - /** @var \PHPUnit_Framework_MockObject_MockObject|ContentObjectRenderer $contentObjectRenderer */ - $contentObjectRenderer = $this->getMockBuilder(ContentObjectRenderer::class) - ->setMethods(array('checkPidArray')) - ->getMock(); - $contentObjectRenderer->expects($this->any())->method('checkPidArray')->willReturn(explode(',', $configuration['pidInList'])); - $this->assertEquals($expectedResult, $contentObjectRenderer->getWhere($table, $configuration)); - } - //////////////////////////////////// // Test concerning link generation //////////////////////////////////// diff --git a/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php b/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php index dc90c59c1517..83e0ef00b7fd 100644 --- a/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php @@ -13,7 +13,6 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\Page; * * The TYPO3 project - inspiring people to share! */ -use TYPO3\CMS\Core\Utility\GeneralUtility; /** * Test case @@ -56,41 +55,6 @@ class PageRepositoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase $this->pageSelectObject->expects($this->any())->method('getMultipleGroupsWhereClause')->will($this->returnValue(' AND 1=1')); } - /** - * Tests whether the getPage Hook is called correctly. - * - * @test - */ - public function isGetPageHookCalled() - { - // Create a hook mock object - $className = $this->getUniqueId('tx_coretest'); - $getPageHookMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\Page\PageRepositoryGetPageHookInterface::class) - ->setMethods(array('getPage_preProcess')) - ->setMockClassName($className) - ->getMock(); - // Register hook mock object - GeneralUtility::addInstance($className, $getPageHookMock); - $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getPage'][] = $className; - // Test if hook is called and register a callback method to check given arguments - $getPageHookMock->expects($this->once())->method('getPage_preProcess')->will($this->returnCallback(array($this, 'isGetPagePreProcessCalledCallback'))); - $this->pageSelectObject->getPage(42, false); - } - - /** - * Handles the arguments that have been sent to the getPage_preProcess hook - * - * @param int $uid - * @param $disableGroupAccessCheck - * @param \TYPO3\CMS\Frontend\Page\PageRepository $parent - */ - public function isGetPagePreProcessCalledCallback($uid, $disableGroupAccessCheck, $parent) - { - $this->assertEquals(42, $uid); - $this->assertFalse($disableGroupAccessCheck); - $this->assertTrue($parent instanceof \TYPO3\CMS\Frontend\Page\PageRepository); - } - ///////////////////////////////////////// // Tests concerning getPathFromRootline ///////////////////////////////////////// @@ -200,241 +164,4 @@ class PageRepositoryTest extends \TYPO3\CMS\Core\Tests\UnitTestCase array('prefixLangTitle', 'fake_table', '', true, 'prefixLangTitle is merged with empty string'), ); } - - //////////////////////////////// - // Tests concerning workspaces - //////////////////////////////// - - /** - * @test - */ - public function noPagesFromWorkspaceAreShownLive() - { - // initialization - $wsid = 987654321; - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages - ); - - // simulate calls from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->fetch_the_id() - $this->pageSelectObject->versioningPreview = false; - $this->pageSelectObject->versioningWorkspaceId = $wsid; - $this->pageSelectObject->init(false); - - // check SQL created by \TYPO3\CMS\Frontend\Page\PageRepository->getPage() - $GLOBALS['TYPO3_DB']->expects($this->once()) - ->method('exec_SELECTgetSingleRow') - ->with( - '*', - 'pages', - $this->logicalAnd( - $this->logicalNot( - $this->stringContains('(pages.t3ver_wsid=0 or pages.t3ver_wsid=' . $wsid . ')') - ), - $this->stringContains('AND pages.t3ver_state<=0') - ) - ); - - $this->pageSelectObject->getPage(1); - } - - /** - * @test - */ - public function previewShowsPagesFromLiveAndCurrentWorkspace() - { - // initialization - $wsid = 987654321; - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages - ); - - // simulate calls from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->fetch_the_id() - $this->pageSelectObject->versioningPreview = true; - $this->pageSelectObject->versioningWorkspaceId = $wsid; - $this->pageSelectObject->init(false); - - // check SQL created by \TYPO3\CMS\Frontend\Page\PageRepository->getPage() - $GLOBALS['TYPO3_DB']->expects($this->once()) - ->method('exec_SELECTgetSingleRow') - ->with( - '*', - 'pages', - $this->stringContains('(pages.t3ver_wsid=0 or pages.t3ver_wsid=' . $wsid . ')') - ); - - $this->pageSelectObject->getPage(1); - } - - /** - * @test - */ - public function getWorkspaceVersionReturnsTheCorrectMethod() - { - // initialization - $wsid = 987654321; - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages - ); - $GLOBALS['SIM_ACCESS_TIME'] = 123; - - // simulate calls from \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->fetch_the_id() - $this->pageSelectObject->versioningPreview = true; - $this->pageSelectObject->versioningWorkspaceId = $wsid; - $this->pageSelectObject->init(false); - - $GLOBALS['TYPO3_DB']->expects($this->at(0)) - ->method('exec_SELECTgetSingleRow') - ->with( - '*', - 'pages', - $this->logicalAnd( - $this->stringContains('pid=-1 AND'), - $this->stringContains('t3ver_oid=1 AND'), - $this->stringContains('t3ver_wsid=' . $wsid . ' AND pages.deleted=0') - ) - )->willReturn(array('uid' => 1)); - $GLOBALS['TYPO3_DB']->expects($this->at(1)) - ->method('exec_SELECTgetSingleRow') - ->with( - 'uid', - 'pages', - $this->logicalAnd( - $this->stringContains('t3ver_wsid=' . $wsid . ' AND pages.deleted=0 AND pages.hidden=0 AND pages.starttime<=123 AND (pages.endtime=0 OR pages.endtime>123) AND 1=1') - ) - ); - $this->pageSelectObject->getWorkspaceVersionOfRecord($wsid, 'pages', 1); - } - - //////////////////////////////// - // Tests concerning versioning - //////////////////////////////// - - /** - * @test - */ - public function enableFieldsHidesVersionedRecordsAndPlaceholders() - { - $table = $this->getUniqueId('aTable'); - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages, - $table => array( - 'ctrl' => array( - 'versioningWS' => true - ) - ) - ); - - $this->pageSelectObject->versioningPreview = false; - $this->pageSelectObject->init(false); - - $conditions = $this->pageSelectObject->enableFields($table); - - $this->assertThat($conditions, $this->stringContains(' AND ' . $table . '.t3ver_state<=0'), 'Versioning placeholders'); - $this->assertThat($conditions, $this->stringContains(' AND ' . $table . '.pid<>-1'), 'Records from page -1'); - } - - /** - * @test - */ - public function enableFieldsDoesNotHidePlaceholdersInPreview() - { - $table = $this->getUniqueId('aTable'); - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages, - $table => array( - 'ctrl' => array( - 'versioningWS' => true - ) - ) - ); - - $this->pageSelectObject->versioningPreview = true; - $this->pageSelectObject->init(false); - - $conditions = $this->pageSelectObject->enableFields($table); - - $this->assertThat($conditions, $this->logicalNot($this->stringContains(' AND ' . $table . '.t3ver_state<=0')), 'No versioning placeholders'); - $this->assertThat($conditions, $this->stringContains(' AND ' . $table . '.pid<>-1'), 'Records from page -1'); - } - - /** - * @test - */ - public function enableFieldsDoesFilterToCurrentAndLiveWorkspaceForRecordsInPreview() - { - $table = $this->getUniqueId('aTable'); - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages, - $table => array( - 'ctrl' => array( - 'versioningWS' => true - ) - ) - ); - - $this->pageSelectObject->versioningPreview = true; - $this->pageSelectObject->versioningWorkspaceId = 2; - $this->pageSelectObject->init(false); - - $conditions = $this->pageSelectObject->enableFields($table); - - $this->assertThat($conditions, $this->stringContains(' AND (' . $table . '.t3ver_wsid=0 OR ' . $table . '.t3ver_wsid=2)'), 'No versioning placeholders'); - } - - /** - * @test - */ - public function initSetsPublicPropertyCorrectlyForWorkspacePreview() - { - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages, - ); - - $this->pageSelectObject->versioningPreview = true; - $this->pageSelectObject->versioningWorkspaceId = 2; - $this->pageSelectObject->init(false); - - $this->assertSame(' AND pages.deleted=0 AND (pages.t3ver_wsid=0 OR pages.t3ver_wsid=2)', $this->pageSelectObject->where_hid_del); - } - - /** - * @test - */ - public function initSetsPublicPropertyCorrectlyForLive() - { - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages, - ); - $GLOBALS['SIM_ACCESS_TIME'] = 123; - $this->pageSelectObject->versioningPreview = false; - $this->pageSelectObject->versioningWorkspaceId = 0; - $this->pageSelectObject->init(false); - - $this->assertSame(' AND pages.deleted=0 AND pages.t3ver_state<=0 AND pages.hidden=0 AND pages.starttime<=123 AND (pages.endtime=0 OR pages.endtime>123)', $this->pageSelectObject->where_hid_del); - } - - /** - * @test - */ - public function enableFieldsDoesNotHideVersionedRecordsWhenCheckingVersionOverlays() - { - $table = $this->getUniqueId('aTable'); - $GLOBALS['TCA'] = array( - 'pages' => $this->defaultTcaForPages, - $table => array( - 'ctrl' => array( - 'versioningWS' => true - ) - ) - ); - - $this->pageSelectObject->versioningPreview = true; - $this->pageSelectObject->init(false); - - $conditions = $this->pageSelectObject->enableFields($table, -1, array(), true); - - $this->assertThat($conditions, $this->logicalNot($this->stringContains(' AND ' . $table . '.t3ver_state<=0')), 'No versioning placeholders'); - $this->assertThat($conditions, $this->logicalNot($this->stringContains(' AND ' . $table . '.pid<>-1')), 'No ecords from page -1'); - } } -- GitLab