Commit 36e461d1 authored by Morton Jonuschat's avatar Morton Jonuschat Committed by Christian Kuhn
Browse files

[TASK] Deprecate BackendUtility::getRecordsByField

Deprecate BackendUtility::getRecordsByField() as it has a flawed design
due to passing SQL fragments. This contradicts the goal of using named
parameters for all queries in the core and requires passing the original
QueryBuilder object in addition to the stringified constraint.

Replace all calls to the method with direct usage of the QueryBuilder and
deprecate the method.

Resolves: #79122
Releases: master
Change-Id: I8b040b98e20271aff84ef16fb89b59a406d54003
Reviewed-on: https://review.typo3.org/51078


Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Mona Muzaffar's avatarMona Muzaffar <mona.muzaffar@gmx.de>
Tested-by: Mona Muzaffar's avatarMona Muzaffar <mona.muzaffar@gmx.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent cce7d774
......@@ -20,8 +20,12 @@ use TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository;
use TYPO3\CMS\Backend\Module\ModuleLoader;
use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Type\File\ImageInfo;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -720,16 +724,35 @@ class BackendController
$beUser = $this->getBackendUser();
// EDIT page:
$editId = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('edit'));
$editRecord = '';
if ($editId) {
// Looking up the page to edit, checking permissions:
$where = ' AND (' . $beUser->getPagePermsClause(2) . ' OR ' . $beUser->getPagePermsClause(16) . ')';
if (MathUtility::canBeInterpretedAsInteger($editId)) {
$editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
} else {
$records = BackendUtility::getRecordsByField('pages', 'alias', $editId, $where);
if (is_array($records)) {
$editRecord = reset($records);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$editRecord = $queryBuilder->select('*')
->from('pages')
->where(
$queryBuilder->expr()->eq(
'alias',
$queryBuilder->createNamedParameter($editId, \PDO::PARAM_STR)
),
$queryBuilder->expr()->orX(
$beUser->getPagePermsClause(Permission::PAGE_EDIT),
$beUser->getPagePermsClause(Permission::CONTENT_EDIT)
)
)
->setMaxResults(1)
->execute()
->fetch();
if ($editRecord !== false) {
BackendUtility::workspaceOL('pages', $editRecord);
}
}
......
......@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -330,15 +331,32 @@ class LocalizationRepository
$tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
if (isset($tcaCtrl['origUid'])) {
$recordLocalization = BackendUtility::getRecordsByField(
$table,
$tcaCtrl['origUid'],
$uid,
'AND ' . $tcaCtrl['languageField'] . '=' . (int)$language . ($andWhereClause ? ' ' . $andWhereClause : ''),
'',
'',
'1'
);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder->select('*')
->from($table)
->where(
$queryBuilder->expr()->eq(
$tcaCtrl['origUid'],
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
$tcaCtrl['languageField'],
$queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
)
)
->setMaxResults(1);
if ($andWhereClause) {
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
}
$recordLocalization = $queryBuilder->execute()->fetchAll();
}
}
return $recordLocalization;
......
......@@ -221,6 +221,7 @@ class BackendUtility
* @param bool $useDeleteClause Use the deleteClause to check if a record is deleted (default TRUE)
* @param null|QueryBuilder $queryBuilder The queryBuilder must be provided, if the parameter $whereClause is given and the concept of prepared statement was used. Example within self::firstDomainRecord()
* @return mixed Multidimensional array with selected records (if any is selected)
* @deprecated since TYPO3 v8, will be removed in TYPO3 v9
*/
public static function getRecordsByField(
$theTable,
......@@ -233,6 +234,7 @@ class BackendUtility
$useDeleteClause = true,
$queryBuilder = null
) {
GeneralUtility::logDeprecatedFunction();
if (is_array($GLOBALS['TCA'][$theTable])) {
if (null === $queryBuilder) {
$queryBuilder = static::getQueryBuilderForTable($theTable);
......@@ -394,27 +396,32 @@ class BackendUtility
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$constraint = $queryBuilder->expr()->andX(
$queryBuilder->expr()->eq(
$tcaCtrl['languageField'],
$queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
),
QueryHelper::stripLogicalOperatorPrefix($andWhereClause)
);
$queryBuilder->select('*')
->from($table)
->where(
$queryBuilder->expr()->eq(
$tcaCtrl['transOrigPointerField'],
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
$tcaCtrl['languageField'],
$queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
)
)
->setMaxResults(1);
$recordLocalization = self::getRecordsByField(
$table,
$tcaCtrl['transOrigPointerField'],
$uid,
(string)$constraint,
'',
'',
1,
true,
$queryBuilder
);
if ($andWhereClause) {
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
}
$recordLocalization = $queryBuilder->execute()->fetchAll();
}
return $recordLocalization;
}
......@@ -3970,31 +3977,37 @@ class BackendUtility
public static function firstDomainRecord($rootLine)
{
$queryBuilder = static::getQueryBuilderForTable('sys_domain');
$constraint = $queryBuilder->expr()->andX(
$queryBuilder->expr()->eq(
'redirectTo',
$queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
),
$queryBuilder->expr()->eq(
'hidden',
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder->select('domainName')
->from('sys_domain')
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT, ':pid')
),
$queryBuilder->expr()->eq(
'redirectTo',
$queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
),
$queryBuilder->expr()->eq(
'hidden',
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
)
)
);
->setMaxResults(1)
->orderBy('sorting');
foreach ($rootLine as $row) {
$dRec = self::getRecordsByField(
'sys_domain',
'pid',
$row['uid'],
(string)$constraint,
'',
'sorting',
'',
true,
$queryBuilder
);
if (is_array($dRec)) {
$dRecord = reset($dRec);
return rtrim($dRecord['domainName'], '/');
$domainName = $queryBuilder->setParameter('pid', $row['uid'], \PDO::PARAM_INT)
->execute()
->fetchColumn(0);
if ($domainName) {
return rtrim($domainName, '/');
}
}
return null;
......
......@@ -453,28 +453,32 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
$userCanEditPage = $this->getBackendUser()->check('tables_modify', 'pages_language_overlay');
}
if ($userCanEditPage) {
$languageOverlayId = 0;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionForTable('pages_language_overlay')
->createQueryBuilder();
$constraint = $queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($this->tt_contentConfig['sys_language_uid'], \PDO::PARAM_INT)
);
$pageOverlayRecord = BackendUtility::getRecordsByField(
'pages_language_overlay',
'pid',
(int)$this->id,
$constraint,
'',
'',
'',
true,
$queryBuilder
);
if (!empty($pageOverlayRecord[0]['uid'])) {
$languageOverlayId = $pageOverlayRecord[0]['uid'];
}
->getQueryBuilderForTable('pages_language_overlay');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder->select('uid')
->from('pages_language_overlay')
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter((int)$this->id, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter(
$this->tt_contentConfig['sys_language_uid'],
\PDO::PARAM_INT
)
)
)
->setMaxResults(1);
$languageOverlayId = (int)$queryBuilder->execute()->fetchColumn(0);
$pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/PageActions', 'function(PageActions) {
PageActions.setPageId(' . (int)$this->id . ');
PageActions.setLanguageOverlayId(' . $languageOverlayId . ');
......@@ -853,7 +857,29 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
}
// Language overlay page header:
if ($lP) {
list($lpRecord) = BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $id, 'AND sys_language_uid=' . $lP);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('pages_language_overlay');
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$lpRecord = $queryBuilder->select('*')
->from('pages_language_overlay')
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($lP, \PDO::PARAM_INT)
)
)
->setMaxResults(1)
->execute()
->fetch();
BackendUtility::workspaceOL('pages_language_overlay', $lpRecord);
$recordIcon = BackendUtility::wrapClickMenuOnIcon(
$this->iconFactory->getIconForRecord('pages_language_overlay', $lpRecord, Icon::SIZE_SMALL)->render(),
......
<?php
namespace TYPO3\CMS\Backend\Tests\Functional\Controller\Page;
/*
* 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\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\RootlineUtility;
/**
* Test case for TYPO3\CMS\Backend\Controller\Page\LocalizationController
*/
class BackendUtilityTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
{
/**
* Sets up this test case.
*
* @return void
*/
protected function setUp()
{
parent::setUp();
$this->importDataSet(ORIGINAL_ROOT . 'components/testing_framework/Resources/Core/Functional/Fixtures/pages.xml');
$this->importDataSet(ORIGINAL_ROOT . 'typo3/sysext/backend/Tests/Functional/Utility/Fixtures/sys_domain.xml');
}
/**
* @test
*/
public function determineFirstDomainRecord()
{
$rootLineUtility = GeneralUtility::makeInstance(RootlineUtility::class, 4);
$rootLine = $rootLineUtility->get();
$this->assertEquals('example.com', BackendUtility::firstDomainRecord($rootLine));
}
}
<?xml version="1.0" encoding="utf-8"?>
<dataset>
<sys_domain>
<uid>1</uid>
<pid>1</pid>
<tstamp>1487563944</tstamp>
<hidden>0</hidden>
<domainName>example.com</domainName>
<forced>1</forced>
</sys_domain>
<sys_domain>
<uid>2</uid>
<pid>7</pid>
<tstamp>1487563945</tstamp>
<hidden>0</hidden>
<domainName>www.example.net</domainName>
<redirectTo>http://example.com/</redirectTo>
<redirectHttpStatusCode>301</redirectHttpStatusCode>
<forced>0</forced>
</sys_domain>
</dataset>
......@@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
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\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
......@@ -701,15 +702,30 @@ class BackendUserAuthentication extends \TYPO3\CMS\Core\Authentication\AbstractU
$pointerField = $GLOBALS['TCA'][$l10nTable]['ctrl']['transOrigPointerField'];
$pointerValue = $record[$pointerField] > 0 ? $record[$pointerField] : $record['uid'];
}
$recordLocalizations = BackendUtility::getRecordsByField($l10nTable, $pointerField, $pointerValue, '', '', '', '1');
if (is_array($recordLocalizations)) {
foreach ($recordLocalizations as $localization) {
$recordLocalizationAccess = $recordLocalizationAccess
&& $this->checkLanguageAccess($localization[$GLOBALS['TCA'][$l10nTable]['ctrl']['languageField']]);
if (!$recordLocalizationAccess) {
break;
}
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($l10nTable);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$recordLocalization = $queryBuilder->select('*')
->from($l10nTable)
->where(
$queryBuilder->expr()->eq(
$pointerField,
$queryBuilder->createNamedParameter($pointerValue, \PDO::PARAM_INT)
)
)
->setMaxResults(1)
->execute()
->fetch();
if (is_array($recordLocalization)) {
$languageAccess = $this->checkLanguageAccess(
$recordLocalization[$GLOBALS['TCA'][$l10nTable]['ctrl']['languageField']]
);
$recordLocalizationAccess = $recordLocalizationAccess && $languageAccess;
}
}
return $recordLocalizationAccess;
......
......@@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
use TYPO3\CMS\Core\Database\ReferenceIndex;
......@@ -2630,12 +2631,44 @@ class DataHandler
public function getRecordsWithSameValue($tableName, $uid, $fieldName, $value, $pageId = 0)
{
$result = [];
if (!empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
$uid = (int)$uid;
$pageId = (int)$pageId;
$whereStatement = ' AND uid <> ' . $uid . ' AND ' . ($pageId ? 'pid = ' . $pageId : 'pid >= 0');
$result = BackendUtility::getRecordsByField($tableName, $fieldName, $value, $whereStatement);
if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName])) {
return $result;
}
$uid = (int)$uid;
$pageId = (int)$pageId;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder->select('*')
->from($tableName)
->where(
$queryBuilder->expr()->eq(
$fieldName,
$queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)
),
$queryBuilder->expr()->neq(
'uid',
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
)
);
if ($pageId) {
$queryBuilder->andWhere(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT))
);
} else {
$queryBuilder->andWhere(
$queryBuilder->expr()->gte('pid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
);
}
$result = $queryBuilder->execute()->fetchAll();
return $result;
}
......@@ -4112,20 +4145,38 @@ class DataHandler
if (!BackendUtility::isTableLocalizable($table) || $table === 'pages' || $table === 'pages_language_overlay') {
return;
}
$where = '';
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder->select('*')
->from($table)
->where(
$queryBuilder->expr()->eq(
$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
)
);
if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
$where = ' AND t3ver_oid=0';
$queryBuilder->andWhere(
$queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
);
}
// If $destPid is < 0, get the pid of the record with uid equal to abs($destPid)
$tscPID = BackendUtility::getTSconfig_pidValue($table, $uid, $destPid);
// Get the localized records to be copied
$l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
$l10nRecords = $queryBuilder->execute()->fetchAll();
if (is_array($l10nRecords)) {
$localizedDestPids = [];
// If $destPid < 0, then it is the uid of the original language record we are inserting after
if ($destPid < 0) {
// Get the localized records of the record we are inserting after
$destL10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], abs($destPid), $where);
$queryBuilder->setParameter('pointer', abs($destPid), \PDO::PARAM_INT);
$destL10nRecords = $queryBuilder->execute()->fetchAll();
// Index the localized record uids by language
if (is_array($destL10nRecords)) {
foreach ($destL10nRecords as $record) {
......@@ -4512,17 +4563,36 @@ class DataHandler
if (!BackendUtility::isTableLocalizable($table) || $table === 'pages' || $table === 'pages_language_overlay') {
return;
}
$where = '';
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
$queryBuilder->select('*')
->from($table)
->where(
$queryBuilder->expr()->eq(
$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
$queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT, ':pointer')
)
);
if (isset($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
$where = ' AND t3ver_oid=0';
$queryBuilder->andWhere(
$queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
);
}
$l10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], $uid, $where);
$l10nRecords = $queryBuilder->execute()->fetchAll();
if (is_array($l10nRecords)) {
$localizedDestPids = [];
// If $$originalRecordDestinationPid < 0, then it is the uid of the original language record we are inserting after
if ($originalRecordDestinationPid < 0) {
// Get the localized records of the record we are inserting after
$destL10nRecords = BackendUtility::getRecordsByField($table, $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'], abs($originalRecordDestinationPid), $where);
$queryBuilder->setParameter('pointer', abs($originalRecordDestinationPid), \PDO::PARAM_INT);
$destL10nRecords = $queryBuilder->execute()->fetchAll();
// Index the localized record uids by language