617 lines
26 KiB
PHP
617 lines
26 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/*
|
|
* 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!
|
|
*/
|
|
|
|
namespace Evoweb\EwBase\DataProcessing;
|
|
|
|
use Doctrine\DBAL\Exception as DBALException;
|
|
use Doctrine\DBAL\Result;
|
|
use Psr\Http\Message\ServerRequestInterface;
|
|
use TYPO3\CMS\Core\Context\Context;
|
|
use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
|
|
use TYPO3\CMS\Core\Context\LanguageAspect;
|
|
use TYPO3\CMS\Core\Database\Connection;
|
|
use TYPO3\CMS\Core\Database\ConnectionPool;
|
|
use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
|
|
use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
|
|
use TYPO3\CMS\Core\Database\Query\QueryHelper;
|
|
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
|
|
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Core\Utility\MathUtility;
|
|
use TYPO3\CMS\Core\Versioning\VersionState;
|
|
use TYPO3\CMS\Frontend\ContentObject\ContentDataProcessor;
|
|
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
|
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
|
|
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
|
|
|
|
/**
|
|
* Fetch records from the database, using the default .select syntax from TypoScript.
|
|
*
|
|
* This way, e.g. a FLUIDTEMPLATE cObject can iterate over the array of records.
|
|
*
|
|
* Example TypoScript configuration:
|
|
*
|
|
* 10 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
|
|
* 10 {
|
|
* table = tt_address
|
|
* pidInList = 123
|
|
* where = company="Acme" AND first_name="Ralph"
|
|
* orderBy = sorting DESC
|
|
* as = addresses
|
|
* dataProcessing {
|
|
* 10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
|
|
* 10 {
|
|
* references.fieldName = image
|
|
* }
|
|
* }
|
|
* }
|
|
*
|
|
* where "as" means the variable to be containing the result-set from the DB query.
|
|
*/
|
|
class DatabaseQueryProcessor implements DataProcessorInterface
|
|
{
|
|
protected ContentObjectRenderer $cObj;
|
|
|
|
protected ?ServerRequestInterface $request = null;
|
|
|
|
protected string $tableName = '';
|
|
|
|
public function __construct(protected readonly ContentDataProcessor $contentDataProcessor) {}
|
|
|
|
/**
|
|
* Fetches records from the database as an array
|
|
*
|
|
* @param ContentObjectRenderer $cObj The data of the content element or page
|
|
* @param array $contentObjectConfiguration The configuration of Content Object
|
|
* @param array $processorConfiguration The configuration of this processor
|
|
* @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
|
|
*
|
|
* @return array the processed data as key/value store
|
|
* @throws ContentRenderingException
|
|
*/
|
|
public function process(
|
|
ContentObjectRenderer $cObj,
|
|
array $contentObjectConfiguration,
|
|
array $processorConfiguration,
|
|
array $processedData
|
|
): array {
|
|
if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
|
|
return $processedData;
|
|
}
|
|
|
|
// the table to query, if none given, exit
|
|
$tableName = $cObj->stdWrapValue('table', $processorConfiguration);
|
|
if (empty($tableName)) {
|
|
return $processedData;
|
|
}
|
|
if (isset($processorConfiguration['table.'])) {
|
|
unset($processorConfiguration['table.']);
|
|
}
|
|
if (isset($processorConfiguration['table'])) {
|
|
unset($processorConfiguration['table']);
|
|
}
|
|
|
|
$this->request = $cObj->getRequest();
|
|
$this->tableName = $tableName;
|
|
$this->cObj = clone $cObj;
|
|
// @extensionScannerIgnoreLine
|
|
$this->cObj->start($cObj->data, $tableName);
|
|
$this->cObj->setRequest($this->request);
|
|
|
|
// The variable to be used within the result
|
|
$targetVariableName = $cObj->stdWrapValue('as', $processorConfiguration, 'records');
|
|
|
|
// Execute a SQL statement to fetch the records
|
|
$records = $this->getRecords($tableName, $processorConfiguration);
|
|
$request = $cObj->getRequest();
|
|
$processedRecordVariables = [];
|
|
foreach ($records as $key => $record) {
|
|
/** @var ContentObjectRenderer $recordContentObjectRenderer */
|
|
$recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
|
|
$recordContentObjectRenderer->setRequest($request);
|
|
$recordContentObjectRenderer->start($record, $tableName);
|
|
$processedRecordVariables[$key] = ['data' => $record];
|
|
$processedRecordVariables[$key] = $this->contentDataProcessor->process(
|
|
$recordContentObjectRenderer,
|
|
$processorConfiguration,
|
|
$processedRecordVariables[$key]
|
|
);
|
|
}
|
|
|
|
$processedData[$targetVariableName] = $processedRecordVariables;
|
|
|
|
return $processedData;
|
|
}
|
|
|
|
protected function getRecords(string $tableName, array $queryConfiguration): array
|
|
{
|
|
$records = [];
|
|
|
|
$statement = $this->exec_getQuery($tableName, $queryConfiguration);
|
|
|
|
$pageRepository = $this->getPageRepository();
|
|
while ($row = $statement->fetchAssociative()) {
|
|
// Versioning preview:
|
|
$pageRepository->versionOL($tableName, $row, true);
|
|
|
|
// Language overlay:
|
|
if (is_array($row)) {
|
|
$row = $pageRepository->getLanguageOverlay($tableName, $row);
|
|
}
|
|
|
|
// Might be unset in the language overlay
|
|
if (is_array($row)) {
|
|
$records[] = $row;
|
|
}
|
|
}
|
|
|
|
return $records;
|
|
}
|
|
|
|
protected function exec_getQuery(string $table, array $conf): Result
|
|
{
|
|
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
|
|
$statement = $this->getQuery($connection, $table, $conf);
|
|
|
|
return $connection->executeQuery($statement);
|
|
}
|
|
|
|
public function getQuery(Connection $connection, $table, $conf): string
|
|
{
|
|
// Resolve stdWrap in these properties first
|
|
$properties = [
|
|
'pidInList',
|
|
'uidInList',
|
|
'languageField',
|
|
'selectFields',
|
|
'max',
|
|
'begin',
|
|
'groupBy',
|
|
'orderBy',
|
|
'join',
|
|
'leftjoin',
|
|
'rightjoin',
|
|
'recursive',
|
|
'where',
|
|
];
|
|
foreach ($properties as $property) {
|
|
$conf[$property] = trim(
|
|
isset($conf[$property . '.'])
|
|
? (string)$this->cObj->stdWrap($conf[$property] ?? '', $conf[$property . '.'] ?? [])
|
|
: (string)($conf[$property] ?? '')
|
|
);
|
|
if ($conf[$property] === '') {
|
|
unset($conf[$property]);
|
|
} elseif (in_array($property, ['languageField', 'selectFields', 'join', 'leftjoin', 'rightjoin', 'where'], true)) {
|
|
$conf[$property] = QueryHelper::quoteDatabaseIdentifiers($connection, $conf[$property]);
|
|
}
|
|
if (isset($conf[$property . '.'])) {
|
|
// stdWrapping already done, so remove the sub-array
|
|
unset($conf[$property . '.']);
|
|
}
|
|
}
|
|
// Handle PDO-style named parameter markers first
|
|
$queryMarkers = $this->cObj->getQueryMarkers($connection, $conf);
|
|
// Replace the markers in the non-stdWrap properties
|
|
foreach ($queryMarkers as $marker => $markerValue) {
|
|
$properties = [
|
|
'uidInList',
|
|
'selectFields',
|
|
'where',
|
|
'max',
|
|
'begin',
|
|
'groupBy',
|
|
'orderBy',
|
|
'join',
|
|
'leftjoin',
|
|
'rightjoin',
|
|
];
|
|
foreach ($properties as $property) {
|
|
if ($conf[$property] ?? false) {
|
|
$conf[$property] = str_replace('###' . $marker . '###', $markerValue, $conf[$property]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct WHERE clause:
|
|
// Handle recursive function for the pidInList
|
|
if (isset($conf['recursive'])) {
|
|
$conf['recursive'] = (int)$conf['recursive'];
|
|
if ($conf['recursive'] > 0) {
|
|
$pidList = GeneralUtility::trimExplode(',', $conf['pidInList'], true);
|
|
array_walk($pidList, function (&$storagePid) {
|
|
if ($storagePid === 'this') {
|
|
$storagePid = $this->getRequest()->getAttribute('frontend.page.information')->getId();
|
|
}
|
|
});
|
|
$pageRepository = $this->getPageRepository();
|
|
$expandedPidList = $pageRepository->getPageIdsRecursive($pidList, $conf['recursive']);
|
|
$conf['pidInList'] = implode(',', $expandedPidList);
|
|
}
|
|
}
|
|
if ((string)($conf['pidInList'] ?? '') === '') {
|
|
$conf['pidInList'] = 'this';
|
|
}
|
|
|
|
$queryParts = $this->getQueryConstraints($connection, $table, $conf);
|
|
|
|
$queryBuilder = $connection->createQueryBuilder();
|
|
// @todo Check against getQueryConstraints, can probably use FrontendRestrictions
|
|
// @todo here and remove enableFields there.
|
|
$queryBuilder->getRestrictions()->removeAll();
|
|
$queryBuilder->select('*')->from($table);
|
|
|
|
if ($queryParts['where'] ?? false) {
|
|
$queryBuilder->where($queryParts['where']);
|
|
}
|
|
|
|
if (($queryParts['groupBy'] ?? false) && is_array($queryParts['groupBy'])) {
|
|
$queryBuilder->groupBy(...$queryParts['groupBy']);
|
|
}
|
|
|
|
if (is_array($queryParts['orderBy'] ?? false)) {
|
|
foreach ($queryParts['orderBy'] as $orderBy) {
|
|
$queryBuilder->addOrderBy(...$orderBy);
|
|
}
|
|
}
|
|
|
|
// Fields:
|
|
if ($conf['selectFields'] ?? false) {
|
|
$queryBuilder->selectLiteral($this->sanitizeSelectPart($connection, $conf['selectFields'], $table));
|
|
}
|
|
|
|
// Setting LIMIT:
|
|
if (($conf['max'] ?? false) || ($conf['begin'] ?? false)) {
|
|
// Finding the total number of records, if used:
|
|
if (str_contains(strtolower(($conf['begin'] ?? '') . ($conf['max'] ?? '')), 'total')) {
|
|
$countQueryBuilder = $connection->createQueryBuilder();
|
|
$countQueryBuilder->getRestrictions()->removeAll();
|
|
$countQueryBuilder->count('*')
|
|
->from($table)
|
|
->where($queryParts['where']);
|
|
|
|
if ($queryParts['groupBy']) {
|
|
$countQueryBuilder->groupBy(...$queryParts['groupBy']);
|
|
}
|
|
|
|
try {
|
|
$count = $countQueryBuilder->executeQuery()->fetchOne();
|
|
if (isset($conf['max'])) {
|
|
$conf['max'] = str_ireplace('total', $count, (string)$conf['max']);
|
|
}
|
|
if (isset($conf['begin'])) {
|
|
$conf['begin'] = str_ireplace('total', $count, (string)$conf['begin']);
|
|
}
|
|
} catch (DBALException $e) {
|
|
$this->getTimeTracker()->setTSlogMessage($e->getPrevious()->getMessage());
|
|
return '';
|
|
}
|
|
}
|
|
|
|
if (isset($conf['begin']) && $conf['begin'] > 0) {
|
|
$conf['begin'] = MathUtility::forceIntegerInRange((int)ceil($this->cObj->calc($conf['begin'])), 0);
|
|
$queryBuilder->setFirstResult($conf['begin']);
|
|
}
|
|
if (isset($conf['max'])) {
|
|
$conf['max'] = MathUtility::forceIntegerInRange((int)ceil($this->cObj->calc($conf['max'])), 0);
|
|
$queryBuilder->setMaxResults($conf['max'] ?: 100000);
|
|
}
|
|
}
|
|
|
|
// Setting up tablejoins:
|
|
if ($conf['join'] ?? false) {
|
|
$joinParts = QueryHelper::parseJoin($conf['join']);
|
|
$queryBuilder->join(
|
|
$table,
|
|
$joinParts['tableName'],
|
|
$joinParts['tableAlias'],
|
|
$joinParts['joinCondition']
|
|
);
|
|
} elseif ($conf['leftjoin'] ?? false) {
|
|
$joinParts = QueryHelper::parseJoin($conf['leftjoin']);
|
|
$queryBuilder->leftJoin(
|
|
$table,
|
|
$joinParts['tableName'],
|
|
$joinParts['tableAlias'],
|
|
$joinParts['joinCondition']
|
|
);
|
|
} elseif ($conf['rightjoin'] ?? false) {
|
|
$joinParts = QueryHelper::parseJoin($conf['rightjoin']);
|
|
$queryBuilder->rightJoin(
|
|
$table,
|
|
$joinParts['tableName'],
|
|
$joinParts['tableAlias'],
|
|
$joinParts['joinCondition']
|
|
);
|
|
}
|
|
|
|
// Convert the QueryBuilder object into a SQL statement.
|
|
$query = $queryBuilder->getSQL();
|
|
|
|
// Replace the markers in the queryParts to handle stdWrap enabled properties
|
|
foreach ($queryMarkers as $marker => $markerValue) {
|
|
// @todo Ugly hack that needs to be cleaned up, with the current architecture
|
|
// @todo for exec_Query / getQuery it's the best we can do.
|
|
$query = str_replace('###' . $marker . '###', (string)$markerValue, $query);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Helper function for getQuery(), creating the WHERE clause of the SELECT query
|
|
*
|
|
* @param Connection $connection
|
|
* @param string $table The table name
|
|
* @param array $conf The TypoScript configuration properties
|
|
* @return array Associative array containing the prepared data for WHERE, ORDER BY and GROUP BY fragments
|
|
* @throws AspectNotFoundException
|
|
* @throws ContentRenderingException
|
|
* @see getQuery()
|
|
*/
|
|
protected function getQueryConstraints(Connection $connection, string $table, array $conf): array
|
|
{
|
|
$queryBuilder = $connection->createQueryBuilder();
|
|
$expressionBuilder = $queryBuilder->expr();
|
|
$request = $this->getRequest();
|
|
$contentPid = $request->getAttribute('frontend.page.information')->getContentFromPid();
|
|
$constraints = [];
|
|
$pid_uid_flag = 0;
|
|
$enableFieldsIgnore = [];
|
|
$queryParts = [
|
|
'where' => null,
|
|
'groupBy' => null,
|
|
'orderBy' => null,
|
|
];
|
|
|
|
$isInWorkspace = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('workspace', 'isOffline');
|
|
$considerMovePointers = (
|
|
$isInWorkspace && $table !== 'pages'
|
|
&& !empty($GLOBALS['TCA'][$table]['ctrl']['versioningWS'])
|
|
);
|
|
|
|
if (trim($conf['uidInList'] ?? '')) {
|
|
$listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$contentPid, $conf['uidInList']));
|
|
|
|
// If moved records shall be considered, select via t3ver_oid
|
|
if ($considerMovePointers) {
|
|
$constraints[] = (string)$expressionBuilder->or(
|
|
$expressionBuilder->in($table . '.uid', $listArr),
|
|
$expressionBuilder->and(
|
|
$expressionBuilder->eq(
|
|
$table . '.t3ver_state',
|
|
VersionState::MOVE_POINTER->value
|
|
),
|
|
$expressionBuilder->in($table . '.t3ver_oid', $listArr)
|
|
)
|
|
);
|
|
} else {
|
|
$constraints[] = (string)$expressionBuilder->in($table . '.uid', $listArr);
|
|
}
|
|
$pid_uid_flag++;
|
|
}
|
|
|
|
// Static_* tables are allowed to be fetched from root page
|
|
if (str_starts_with($table, 'static_')) {
|
|
$pid_uid_flag++;
|
|
}
|
|
|
|
if (trim($conf['pidInList'])) {
|
|
$listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$contentPid, $conf['pidInList']));
|
|
// Removes all pages which are not visible for the user!
|
|
$listArr = $this->cObj->checkPidArray($listArr);
|
|
if (GeneralUtility::inList($conf['pidInList'], 'root')) {
|
|
$listArr[] = 0;
|
|
}
|
|
if (GeneralUtility::inList($conf['pidInList'], '-1')) {
|
|
$listArr[] = -1;
|
|
$enableFieldsIgnore['pid'] = true;
|
|
}
|
|
if (!empty($listArr)) {
|
|
$constraints[] = $expressionBuilder->in($table . '.pid', array_map('intval', $listArr));
|
|
$pid_uid_flag++;
|
|
} else {
|
|
// If not uid and not pid then uid is set to 0 - which results in nothing!!
|
|
$pid_uid_flag = 0;
|
|
}
|
|
}
|
|
|
|
// If not uid and not pid then uid is set to 0 - which results in nothing!!
|
|
if (!$pid_uid_flag && trim($conf['pidInList'] ?? '') !== 'ignore') {
|
|
$constraints[] = $expressionBuilder->eq($table . '.uid', 0);
|
|
}
|
|
|
|
$where = trim((string)$this->cObj->stdWrapValue('where', $conf));
|
|
if ($where) {
|
|
$constraints[] = QueryHelper::stripLogicalOperatorPrefix($where);
|
|
}
|
|
|
|
// Check if the default language should be fetched (= doing overlays), or if only the records of a language should be fetched
|
|
// but only do this for TCA tables that have languages enabled
|
|
$languageConstraint = $this->getLanguageRestriction($expressionBuilder, $table, $conf, GeneralUtility::makeInstance(Context::class));
|
|
if ($languageConstraint !== null) {
|
|
$constraints[] = $languageConstraint;
|
|
}
|
|
|
|
// default constraints from TCA
|
|
$pageRepository = $this->getPageRepository();
|
|
$constraints = array_merge(
|
|
$constraints,
|
|
array_values($pageRepository->getDefaultConstraints($table, $enableFieldsIgnore))
|
|
);
|
|
|
|
// MAKE WHERE:
|
|
if ($constraints !== []) {
|
|
$queryParts['where'] = $expressionBuilder->and(...$constraints);
|
|
}
|
|
// GROUP BY
|
|
$groupBy = trim((string)$this->cObj->stdWrapValue('groupBy', $conf));
|
|
if ($groupBy) {
|
|
$queryParts['groupBy'] = QueryHelper::parseGroupBy($groupBy);
|
|
}
|
|
|
|
// ORDER BY
|
|
$orderByString = trim((string)$this->cObj->stdWrapValue('orderBy', $conf));
|
|
if ($orderByString) {
|
|
$queryParts['orderBy'] = QueryHelper::parseOrderBy($orderByString);
|
|
}
|
|
|
|
// Return result:
|
|
return $queryParts;
|
|
}
|
|
|
|
/**
|
|
* Adds parts to the WHERE clause that are related to language.
|
|
* This only works on TCA tables which have the [ctrl][languageField] field set or if they
|
|
* have select.languageField = my_language_field set explicitly.
|
|
*
|
|
* It is also possible to disable the language restriction for a query by using select.languageField = 0,
|
|
* if select.languageField is not explicitly set, the TCA default values are taken.
|
|
*
|
|
* If the table is "localizeable" (= any of the criteria above is met), then the DB query is restricted:
|
|
*
|
|
* If the current language aspect has overlays enabled, then the only records with language "0" or "-1" are
|
|
* fetched (the overlays are taken care of later-on).
|
|
* if the current language has overlays but also records without localization-parent (free mode) available,
|
|
* then these are fetched as well. This can explicitly set via select.includeRecordsWithoutDefaultTranslation = 1
|
|
* which overrules the overlayType within the language aspect.
|
|
*
|
|
* If the language aspect has NO overlays enabled, it behaves as in "free mode" (= only fetch the records
|
|
* for the current language.
|
|
*
|
|
* @throws AspectNotFoundException
|
|
*/
|
|
protected function getLanguageRestriction(
|
|
ExpressionBuilder $expressionBuilder,
|
|
string $table,
|
|
array $conf,
|
|
Context $context
|
|
): string|CompositeExpression|null {
|
|
$languageField = '';
|
|
$localizationParentField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? null;
|
|
// Check if the table is translatable, and set the language field by default from the TCA information
|
|
if (!empty($conf['languageField']) || !isset($conf['languageField'])) {
|
|
if (isset($conf['languageField']) && !empty($GLOBALS['TCA'][$table]['columns'][$conf['languageField']])) {
|
|
$languageField = $conf['languageField'];
|
|
} elseif (!empty($GLOBALS['TCA'][$table]['ctrl']['languageField']) && !empty($localizationParentField)) {
|
|
$languageField = $table . '.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
|
|
}
|
|
}
|
|
|
|
// No language restriction enabled explicitly or available via TCA
|
|
if (empty($languageField)) {
|
|
return null;
|
|
}
|
|
|
|
/** @var LanguageAspect $languageAspect */
|
|
$languageAspect = $context->getAspect('language');
|
|
if ($languageAspect->doOverlays() && !empty($localizationParentField)) {
|
|
// Sys language content is set to zero/-1 - and it is expected that whatever routine processes the output will
|
|
// OVERLAY the records with localized versions!
|
|
$languageQuery = $expressionBuilder->in($languageField, [0, -1]);
|
|
// Use this option to include records that don't have a default language counterpart ("free mode")
|
|
// (originalpointerfield is 0 and the language field contains the requested language)
|
|
if (isset($conf['includeRecordsWithoutDefaultTranslation']) || !empty($conf['includeRecordsWithoutDefaultTranslation.'])) {
|
|
$includeRecordsWithoutDefaultTranslation = isset($conf['includeRecordsWithoutDefaultTranslation.'])
|
|
? $this->cObj->stdWrap($conf['includeRecordsWithoutDefaultTranslation'], $conf['includeRecordsWithoutDefaultTranslation.'])
|
|
: $conf['includeRecordsWithoutDefaultTranslation'];
|
|
$includeRecordsWithoutDefaultTranslation = trim((string)$includeRecordsWithoutDefaultTranslation);
|
|
$includeRecordsWithoutDefaultTranslation = $includeRecordsWithoutDefaultTranslation !== '' && $includeRecordsWithoutDefaultTranslation !== '0';
|
|
} else {
|
|
// Option was not explicitly set, check what's in for the language overlay type.
|
|
$includeRecordsWithoutDefaultTranslation = $languageAspect->getOverlayType() === $languageAspect::OVERLAYS_ON_WITH_FLOATING;
|
|
}
|
|
if ($includeRecordsWithoutDefaultTranslation) {
|
|
$languageQuery = $expressionBuilder->or(
|
|
$languageQuery,
|
|
$expressionBuilder->and(
|
|
$expressionBuilder->eq($table . '.' . $localizationParentField, 0),
|
|
$expressionBuilder->eq($languageField, $languageAspect->getContentId())
|
|
)
|
|
);
|
|
}
|
|
return $languageQuery;
|
|
}
|
|
// No overlays = only fetch records given for the requested language and "all languages"
|
|
return (string)$expressionBuilder->in($languageField, [$languageAspect->getContentId(), -1]);
|
|
}
|
|
|
|
/**
|
|
* Helper function for getQuery, sanitizing the select part
|
|
*
|
|
* This functions checks if the necessary fields are part of the select
|
|
* and adds them if necessary.
|
|
*
|
|
* @return string Sanitized select part
|
|
* @internal
|
|
* @see getQuery
|
|
*/
|
|
protected function sanitizeSelectPart(Connection $connection, string $selectPart, string $table): string
|
|
{
|
|
// Pattern matching parts
|
|
$matchStart = '/(^\\s*|,\\s*|' . $table . '\\.)';
|
|
$matchEnd = '(\\s*,|\\s*$)/';
|
|
$necessaryFields = ['uid', 'pid'];
|
|
$wsFields = ['t3ver_state'];
|
|
$languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? false;
|
|
if (isset($GLOBALS['TCA'][$table]) && !preg_match($matchStart . '\\*' . $matchEnd, $selectPart) && !preg_match('/(count|max|min|avg|sum)\\([^\\)]+\\)|distinct/i', $selectPart)) {
|
|
foreach ($necessaryFields as $field) {
|
|
$match = $matchStart . $field . $matchEnd;
|
|
if (!preg_match($match, $selectPart)) {
|
|
$selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
|
|
}
|
|
}
|
|
if (is_string($languageField)) {
|
|
$match = $matchStart . $languageField . $matchEnd;
|
|
if (!preg_match($match, $selectPart)) {
|
|
$selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $languageField) . ' AS ' . $connection->quoteIdentifier($languageField);
|
|
}
|
|
}
|
|
if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] ?? false) {
|
|
foreach ($wsFields as $field) {
|
|
$match = $matchStart . $field . $matchEnd;
|
|
if (!preg_match($match, $selectPart)) {
|
|
$selectPart .= ', ' . $connection->quoteIdentifier($table . '.' . $field) . ' AS ' . $connection->quoteIdentifier($field);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $selectPart;
|
|
}
|
|
|
|
protected function getPageRepository(): PageRepository
|
|
{
|
|
return GeneralUtility::makeInstance(PageRepository::class);
|
|
}
|
|
|
|
protected function getTimeTracker(): TimeTracker
|
|
{
|
|
return GeneralUtility::makeInstance(TimeTracker::class);
|
|
}
|
|
|
|
public function getRequest(): ServerRequestInterface
|
|
{
|
|
if ($this->request instanceof ServerRequestInterface) {
|
|
return $this->request;
|
|
}
|
|
throw new ContentRenderingException(
|
|
'PSR-7 request is missing in ContentObjectRenderer. Inject with start(), setRequest() or provide via $GLOBALS[\'TYPO3_REQUEST\'].',
|
|
1607172972
|
|
);
|
|
}
|
|
}
|