TYPO3 CMS  TYPO3_8-7
Typo3DbQueryParser.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
40 
45 {
49  protected $dataMapper;
50 
56  protected $pageRepository;
57 
62 
68  protected $queryBuilder;
69 
78  protected $tablePropertyMap = [];
79 
86  protected $tableAliasMap = [];
87 
93  protected $unionTableAliasCache = [];
94 
98  protected $tableName = '';
99 
103  protected $suggestDistinctQuery = false;
104 
109  {
110  $this->dataMapper = $dataMapper;
111  }
112 
117  {
118  $this->environmentService = $environmentService;
119  }
120 
128  public function isDistinctQuerySuggested(): bool
129  {
131  }
132 
140  {
141  // Reset all properties
142  $this->tablePropertyMap = [];
143  $this->tableAliasMap = [];
144  $this->unionTableAliasCache = [];
145  $this->tableName = '';
146 
147  if ($query->getStatement() && $query->getStatement()->getStatement() instanceof QueryBuilder) {
148  $this->queryBuilder = clone $query->getStatement()->getStatement();
149  return $this->queryBuilder;
150  }
151  // Find the right table name
152  $source = $query->getSource();
153  $this->initializeQueryBuilder($source);
154 
155  $constraint = $query->getConstraint();
156  if ($constraint instanceof Qom\ConstraintInterface) {
157  $wherePredicates = $this->parseConstraint($constraint, $source);
158  if (!empty($wherePredicates)) {
159  $this->queryBuilder->andWhere($wherePredicates);
160  }
161  }
162 
163  $this->parseOrderings($query->getOrderings(), $source);
164  $this->addTypo3Constraints($query);
165 
166  return $this->queryBuilder;
167  }
168 
174  protected function initializeQueryBuilder(Qom\SourceInterface $source)
175  {
176  if ($source instanceof Qom\SelectorInterface) {
177  $className = $source->getNodeTypeName();
178  $tableName = $this->dataMapper->getDataMap($className)->getTableName();
179  $this->tableName = $tableName;
180 
181  $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
182  ->getQueryBuilderForTable($tableName);
183 
184  $this->queryBuilder
185  ->getRestrictions()
186  ->removeAll();
187 
188  $tableAlias = $this->getUniqueAlias($tableName);
189 
190  $this->queryBuilder
191  ->select($tableAlias . '.*')
192  ->from($tableName, $tableAlias);
193 
194  $this->addRecordTypeConstraint($className);
195  } elseif ($source instanceof Qom\JoinInterface) {
196  $leftSource = $source->getLeft();
197  $leftTableName = $leftSource->getSelectorName();
198 
199  $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
200  ->getQueryBuilderForTable($leftTableName);
201  $leftTableAlias = $this->getUniqueAlias($leftTableName);
202  $this->queryBuilder
203  ->select($leftTableAlias . '.*')
204  ->from($leftTableName, $leftTableAlias);
205  $this->parseJoin($source, $leftTableAlias);
206  }
207  }
208 
217  protected function parseConstraint(Qom\ConstraintInterface $constraint, Qom\SourceInterface $source)
218  {
219  if ($constraint instanceof Qom\AndInterface) {
220  $constraint1 = $constraint->getConstraint1();
221  $constraint2 = $constraint->getConstraint2();
222  if (($constraint1 instanceof Qom\ConstraintInterface)
223  && ($constraint2 instanceof Qom\ConstraintInterface)
224  ) {
225  return $this->queryBuilder->expr()->andX(
226  $this->parseConstraint($constraint1, $source),
227  $this->parseConstraint($constraint2, $source)
228  );
229  }
230  return '';
231  }
232  if ($constraint instanceof Qom\OrInterface) {
233  $constraint1 = $constraint->getConstraint1();
234  $constraint2 = $constraint->getConstraint2();
235  if (($constraint1 instanceof Qom\ConstraintInterface)
236  && ($constraint2 instanceof Qom\ConstraintInterface)
237  ) {
238  return $this->queryBuilder->expr()->orX(
239  $this->parseConstraint($constraint->getConstraint1(), $source),
240  $this->parseConstraint($constraint->getConstraint2(), $source)
241  );
242  }
243  return '';
244  }
245  if ($constraint instanceof Qom\NotInterface) {
246  return ' NOT(' . $this->parseConstraint($constraint->getConstraint(), $source) . ')';
247  }
248  if ($constraint instanceof Qom\ComparisonInterface) {
249  return $this->parseComparison($constraint, $source);
250  }
251  throw new \RuntimeException('not implemented', 1476199898);
252  }
253 
261  protected function parseOrderings(array $orderings, Qom\SourceInterface $source)
262  {
263  foreach ($orderings as $propertyName => $order) {
265  throw new UnsupportedOrderException('Unsupported order encountered.', 1242816074);
266  }
267  $className = null;
268  $tableName = '';
269  if ($source instanceof Qom\SelectorInterface) {
270  $className = $source->getNodeTypeName();
271  $tableName = $this->dataMapper->convertClassNameToTableName($className);
272  $fullPropertyPath = '';
273  while (strpos($propertyName, '.') !== false) {
274  $this->addUnionStatement($className, $tableName, $propertyName, $fullPropertyPath);
275  }
276  } elseif ($source instanceof Qom\JoinInterface) {
277  $tableName = $source->getLeft()->getSelectorName();
278  }
279  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
280  if ($tableName !== '') {
281  $this->queryBuilder->addOrderBy($tableName . '.' . $columnName, $order);
282  } else {
283  $this->queryBuilder->addOrderBy($columnName, $order);
284  }
285  }
286  }
287 
293  protected function addTypo3Constraints(QueryInterface $query)
294  {
295  foreach ($this->tableAliasMap as $tableAlias => $tableName) {
296  $additionalWhereClauses = $this->getAdditionalWhereClause($query->getQuerySettings(), $tableName, $tableAlias);
297  $statement = $this->getVisibilityConstraintStatement($query->getQuerySettings(), $tableName, $tableAlias);
298  if ($statement !== '') {
299  $additionalWhereClauses[] = $statement;
300  }
301  if (!empty($additionalWhereClauses)) {
302  if (in_array($tableAlias, $this->unionTableAliasCache, true)) {
303  $this->queryBuilder->andWhere(
304  $this->queryBuilder->expr()->orX(
305  $this->queryBuilder->expr()->andX(...$additionalWhereClauses),
306  $this->queryBuilder->expr()->isNull($tableAlias . '.uid')
307  )
308  );
309  } else {
310  $this->queryBuilder->andWhere(...$additionalWhereClauses);
311  }
312  }
313  }
314  }
315 
326  protected function parseComparison(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)
327  {
328  if ($comparison->getOperator() === QueryInterface::OPERATOR_CONTAINS) {
329  if ($comparison->getOperand2() === null) {
330  throw new BadConstraintException('The value for the CONTAINS operator must not be null.', 1484828468);
331  }
332  $value = $this->dataMapper->getPlainValue($comparison->getOperand2());
333  if (!$source instanceof Qom\SelectorInterface) {
334  throw new \RuntimeException('Source is not of type "SelectorInterface"', 1395362539);
335  }
336  $className = $source->getNodeTypeName();
337  $tableName = $this->dataMapper->convertClassNameToTableName($className);
338  $operand1 = $comparison->getOperand1();
339  $propertyName = $operand1->getPropertyName();
340  $fullPropertyPath = '';
341  while (strpos($propertyName, '.') !== false) {
342  $this->addUnionStatement($className, $tableName, $propertyName, $fullPropertyPath);
343  }
344  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
345  $dataMap = $this->dataMapper->getDataMap($className);
346  $columnMap = $dataMap->getColumnMap($propertyName);
347  $typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
348  if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
349  $relationTableName = $columnMap->getRelationTableName();
350  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
351  $queryBuilderForSubselect
352  ->select($columnMap->getParentKeyFieldName())
353  ->from($relationTableName)
354  ->where(
355  $queryBuilderForSubselect->expr()->eq(
356  $columnMap->getChildKeyFieldName(),
357  $this->queryBuilder->createNamedParameter($value)
358  )
359  );
360  $additionalWhereForMatchFields = $this->getAdditionalMatchFieldsStatement($queryBuilderForSubselect->expr(), $columnMap, $relationTableName, $relationTableName);
361  if ($additionalWhereForMatchFields) {
362  $queryBuilderForSubselect->andWhere($additionalWhereForMatchFields);
363  }
364 
365  return $this->queryBuilder->expr()->comparison(
366  $this->queryBuilder->quoteIdentifier($tableName . '.uid'),
367  'IN',
368  '(' . $queryBuilderForSubselect->getSQL() . ')'
369  );
370  }
371  if ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
372  $parentKeyFieldName = $columnMap->getParentKeyFieldName();
373  if (isset($parentKeyFieldName)) {
374  $childTableName = $columnMap->getChildTableName();
375 
376  // Build the SQL statement of the subselect
377  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
378  $queryBuilderForSubselect
379  ->select($parentKeyFieldName)
380  ->from($childTableName)
381  ->where(
382  $queryBuilderForSubselect->expr()->eq(
383  'uid',
384  (int)$value
385  )
386  );
387 
388  // Add it to the main query
389  return $this->queryBuilder->expr()->eq(
390  $tableName . '.uid',
391  '(' . $queryBuilderForSubselect->getSQL() . ')'
392  );
393  }
394  return $this->queryBuilder->expr()->inSet(
395  $tableName . '.' . $columnName,
396  $this->queryBuilder->quote($value)
397  );
398  }
399  throw new RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
400  }
401  return $this->parseDynamicOperand($comparison, $source);
402  }
403 
413  protected function parseDynamicOperand(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)
414  {
415  $value = $comparison->getOperand2();
416  $fieldName = $this->parseOperand($comparison->getOperand1(), $source);
417  $expr = null;
418  $exprBuilder = $this->queryBuilder->expr();
419  switch ($comparison->getOperator()) {
421  $hasValue = false;
422  $plainValues = [];
423  foreach ($value as $singleValue) {
424  $plainValue = $this->dataMapper->getPlainValue($singleValue);
425  if ($plainValue !== null) {
426  $hasValue = true;
427  $plainValues[] = $this->createTypedNamedParameter($singleValue);
428  }
429  }
430  if (!$hasValue) {
431  throw new BadConstraintException(
432  'The IN operator needs a non-empty value list to compare against. ' .
433  'The given value list is empty.',
434  1484828466
435  );
436  }
437  $expr = $exprBuilder->comparison($fieldName, 'IN', '(' . implode(', ', $plainValues) . ')');
438  break;
440  if ($value === null) {
441  $expr = $fieldName . ' IS NULL';
442  } else {
443  $placeHolder = $this->createTypedNamedParameter($value);
444  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::EQ, $placeHolder);
445  }
446  break;
448  $expr = $fieldName . ' IS NULL';
449  break;
451  if ($value === null) {
452  $expr = $fieldName . ' IS NOT NULL';
453  } else {
454  $placeHolder = $this->createTypedNamedParameter($value);
455  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::NEQ, $placeHolder);
456  }
457  break;
459  $expr = $fieldName . ' IS NOT NULL';
460  break;
462  $placeHolder = $this->createTypedNamedParameter($value);
463  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::LT, $placeHolder);
464  break;
466  $placeHolder = $this->createTypedNamedParameter($value);
467  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::LTE, $placeHolder);
468  break;
470  $placeHolder = $this->createTypedNamedParameter($value);
471  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::GT, $placeHolder);
472  break;
474  $placeHolder = $this->createTypedNamedParameter($value);
475  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::GTE, $placeHolder);
476  break;
478  $placeHolder = $this->createTypedNamedParameter($value, \PDO::PARAM_STR);
479  $expr = $exprBuilder->comparison($fieldName, 'LIKE', $placeHolder);
480  break;
481  default:
482  throw new Exception(
483  'Unsupported operator encountered.',
484  1242816073
485  );
486  }
487  return $expr;
488  }
489 
498  protected function getParameterType($value): int
499  {
500  $parameterType = gettype($value);
501  switch ($parameterType) {
502  case 'integer':
503  return \PDO::PARAM_INT;
504  case 'string':
505  return \PDO::PARAM_STR;
506  default:
507  throw new \InvalidArgumentException(
508  'Unsupported parameter type encountered. Expected integer or string, ' . $parameterType . ' given.',
509  1494878863
510  );
511  }
512  }
513 
524  protected function createTypedNamedParameter($value, int $forceType = null): string
525  {
526  $plainValue = $this->dataMapper->getPlainValue($value);
527  $parameterType = $forceType ?? $this->getParameterType($plainValue);
528  $placeholder = $this->queryBuilder->createNamedParameter($plainValue, $parameterType);
529 
530  return $placeholder;
531  }
532 
539  protected function parseOperand(Qom\DynamicOperandInterface $operand, Qom\SourceInterface $source)
540  {
541  if ($operand instanceof Qom\LowerCaseInterface) {
542  $constraintSQL = 'LOWER(' . $this->parseOperand($operand->getOperand(), $source) . ')';
543  } elseif ($operand instanceof Qom\UpperCaseInterface) {
544  $constraintSQL = 'UPPER(' . $this->parseOperand($operand->getOperand(), $source) . ')';
545  } elseif ($operand instanceof Qom\PropertyValueInterface) {
546  $propertyName = $operand->getPropertyName();
547  $className = '';
548  if ($source instanceof Qom\SelectorInterface) {
549  $className = $source->getNodeTypeName();
550  $tableName = $this->dataMapper->convertClassNameToTableName($className);
551  $fullPropertyPath = '';
552  while (strpos($propertyName, '.') !== false) {
553  $this->addUnionStatement($className, $tableName, $propertyName, $fullPropertyPath);
554  }
555  } elseif ($source instanceof Qom\JoinInterface) {
556  $tableName = $source->getJoinCondition()->getSelector1Name();
557  }
558  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
559  $constraintSQL = (!empty($tableName) ? $tableName . '.' : '') . $columnName;
560  $constraintSQL = $this->queryBuilder->getConnection()->quoteIdentifier($constraintSQL);
561  } else {
562  throw new \InvalidArgumentException('Given operand has invalid type "' . get_class($operand) . '".', 1395710211);
563  }
564  return $constraintSQL;
565  }
566 
572  protected function addRecordTypeConstraint($className)
573  {
574  if ($className !== null) {
575  $dataMap = $this->dataMapper->getDataMap($className);
576  if ($dataMap->getRecordTypeColumnName() !== null) {
577  $recordTypes = [];
578  if ($dataMap->getRecordType() !== null) {
579  $recordTypes[] = $dataMap->getRecordType();
580  }
581  foreach ($dataMap->getSubclasses() as $subclassName) {
582  $subclassDataMap = $this->dataMapper->getDataMap($subclassName);
583  if ($subclassDataMap->getRecordType() !== null) {
584  $recordTypes[] = $subclassDataMap->getRecordType();
585  }
586  }
587  if (!empty($recordTypes)) {
588  $recordTypeStatements = [];
589  foreach ($recordTypes as $recordType) {
590  $tableName = $dataMap->getTableName();
591  $recordTypeStatements[] = $this->queryBuilder->expr()->eq(
592  $tableName . '.' . $dataMap->getRecordTypeColumnName(),
593  $this->queryBuilder->createNamedParameter($recordType)
594  );
595  }
596  $this->queryBuilder->andWhere(
597  $this->queryBuilder->expr()->orX(...$recordTypeStatements)
598  );
599  }
600  }
601  }
602  }
603 
614  protected function getAdditionalMatchFieldsStatement($exprBuilder, $columnMap, $childTableAlias, $parentTable = null)
615  {
616  $additionalWhereForMatchFields = [];
617  $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
618  if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
619  foreach ($relationTableMatchFields as $fieldName => $value) {
620  $additionalWhereForMatchFields[] = $exprBuilder->eq($childTableAlias . '.' . $fieldName, $this->queryBuilder->createNamedParameter($value));
621  }
622  }
623 
624  if (isset($parentTable)) {
625  $parentTableFieldName = $columnMap->getParentTableFieldName();
626  if (!empty($parentTableFieldName)) {
627  $additionalWhereForMatchFields[] = $exprBuilder->eq($childTableAlias . '.' . $parentTableFieldName, $this->queryBuilder->createNamedParameter($parentTable));
628  }
629  }
630 
631  if (!empty($additionalWhereForMatchFields)) {
632  return $exprBuilder->andX(...$additionalWhereForMatchFields);
633  }
634  return '';
635  }
636 
645  protected function getAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableName, $tableAlias = null)
646  {
647  $whereClause = [];
648  if ($querySettings->getRespectSysLanguage()) {
649  $systemLanguageStatement = $this->getSysLanguageStatement($tableName, $tableAlias, $querySettings);
650  if (!empty($systemLanguageStatement)) {
651  $whereClause[] = $systemLanguageStatement;
652  }
653  }
654 
655  if ($querySettings->getRespectStoragePage()) {
656  $pageIdStatement = $this->getPageIdStatement($tableName, $tableAlias, $querySettings->getStoragePageIds());
657  if (!empty($pageIdStatement)) {
658  $whereClause[] = $pageIdStatement;
659  }
660  }
661 
662  return $whereClause;
663  }
664 
673  protected function getVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, $tableAlias)
674  {
675  $statement = '';
676  if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
677  $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
678  $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
679  $includeDeleted = $querySettings->getIncludeDeleted();
680  if ($this->environmentService->isEnvironmentInFrontendMode()) {
681  $statement .= $this->getFrontendConstraintStatement($tableName, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
682  } else {
683  // TYPO3_MODE === 'BE'
684  $statement .= $this->getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted);
685  }
686  if (!empty($statement)) {
687  $statement = $this->replaceTableNameWithAlias($statement, $tableName, $tableAlias);
688  $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
689  }
690  }
691  return $statement;
692  }
693 
704  protected function getFrontendConstraintStatement($tableName, $ignoreEnableFields, array $enableFieldsToBeIgnored = [], $includeDeleted)
705  {
706  $statement = '';
707  if ($ignoreEnableFields && !$includeDeleted) {
708  if (!empty($enableFieldsToBeIgnored)) {
709  // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
710  $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
711  } else {
712  $statement .= $this->getPageRepository()->deleteClause($tableName);
713  }
714  } elseif (!$ignoreEnableFields && !$includeDeleted) {
715  $statement .= $this->getPageRepository()->enableFields($tableName);
716  } elseif (!$ignoreEnableFields && $includeDeleted) {
717  throw new InconsistentQuerySettingsException('Query setting "ignoreEnableFields=FALSE" can not be used together with "includeDeleted=TRUE" in frontend context.', 1460975922);
718  }
719  return $statement;
720  }
721 
730  protected function getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted)
731  {
732  $statement = '';
733  if (!$ignoreEnableFields) {
735  }
736  if (!$includeDeleted) {
738  }
739  return $statement;
740  }
741 
750  protected function getSysLanguageStatement($tableName, $tableAlias, $querySettings)
751  {
752  if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
753  if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
754  // Select all entries for the current language
755  // If any language is set -> get those entries which are not translated yet
756  // They will be removed by \TYPO3\CMS\Frontend\Page\PageRepository::getRecordOverlay if not matching overlay mode
757  $languageField = $GLOBALS['TCA'][$tableName]['ctrl']['languageField'];
758 
759  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
760  && $querySettings->getLanguageUid() > 0
761  ) {
762  $mode = $querySettings->getLanguageMode();
763 
764  if ($mode === 'strict') {
765  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
766  $queryBuilderForSubselect->getRestrictions()->removeAll()->add(new DeletedRestriction());
767  $queryBuilderForSubselect
768  ->select($tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
769  ->from($tableName)
770  ->where(
771  $queryBuilderForSubselect->expr()->andX(
772  $queryBuilderForSubselect->expr()->gt($tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'], 0),
773  $queryBuilderForSubselect->expr()->eq($tableName . '.' . $languageField, (int)$querySettings->getLanguageUid())
774  )
775  );
776  return $this->queryBuilder->expr()->orX(
777  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, -1),
778  $this->queryBuilder->expr()->andX(
779  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, (int)$querySettings->getLanguageUid()),
780  $this->queryBuilder->expr()->eq($tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'], 0)
781  ),
782  $this->queryBuilder->expr()->andX(
783  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, 0),
784  $this->queryBuilder->expr()->in(
785  $tableAlias . '.uid',
786  $queryBuilderForSubselect->getSQL()
787  )
788  )
789  );
790  }
791  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
792  $queryBuilderForSubselect->getRestrictions()->removeAll()->add(new DeletedRestriction());
793  $queryBuilderForSubselect
794  ->select($tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
795  ->from($tableName)
796  ->where(
797  $queryBuilderForSubselect->expr()->andX(
798  $queryBuilderForSubselect->expr()->gt($tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'], 0),
799  $queryBuilderForSubselect->expr()->eq($tableName . '.' . $languageField, (int)$querySettings->getLanguageUid())
800  )
801  );
802  return $this->queryBuilder->expr()->orX(
803  $this->queryBuilder->expr()->in($tableAlias . '.' . $languageField, [(int)$querySettings->getLanguageUid(), -1]),
804  $this->queryBuilder->expr()->andX(
805  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, 0),
806  $this->queryBuilder->expr()->notIn(
807  $tableAlias . '.uid',
808  $queryBuilderForSubselect->getSQL()
809  )
810  )
811  );
812  }
813  return $this->queryBuilder->expr()->in(
814  $tableAlias . '.' . $languageField,
815  [(int)$querySettings->getLanguageUid(), -1]
816  );
817  }
818  }
819  return '';
820  }
821 
831  protected function getPageIdStatement($tableName, $tableAlias, array $storagePageIds)
832  {
833  if (!is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
834  return '';
835  }
836 
837  $rootLevel = (int)$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'];
838  switch ($rootLevel) {
839  // Only in pid 0
840  case 1:
841  $storagePageIds = [0];
842  break;
843  // Pid 0 and pagetree
844  case -1:
845  if (empty($storagePageIds)) {
846  $storagePageIds = [0];
847  } else {
848  $storagePageIds[] = 0;
849  }
850  break;
851  // Only pagetree or not set
852  case 0:
853  if (empty($storagePageIds)) {
854  throw new InconsistentQuerySettingsException('Missing storage page ids.', 1365779762);
855  }
856  break;
857  // Invalid configuration
858  default:
859  return '';
860  }
861  $storagePageIds = array_map('intval', $storagePageIds);
862  if (count($storagePageIds) === 1) {
863  return $this->queryBuilder->expr()->eq($tableAlias . '.pid', reset($storagePageIds));
864  }
865  return $this->queryBuilder->expr()->in($tableAlias . '.pid', $storagePageIds);
866  }
867 
874  protected function parseJoin(Qom\JoinInterface $join, $leftTableAlias)
875  {
876  $leftSource = $join->getLeft();
877  $leftClassName = $leftSource->getNodeTypeName();
878  $this->addRecordTypeConstraint($leftClassName);
879  $rightSource = $join->getRight();
880  if ($rightSource instanceof Qom\JoinInterface) {
881  $left = $rightSource->getLeft();
882  $rightClassName = $left->getNodeTypeName();
883  $rightTableName = $left->getSelectorName();
884  } else {
885  $rightClassName = $rightSource->getNodeTypeName();
886  $rightTableName = $rightSource->getSelectorName();
887  $this->queryBuilder->addSelect($rightTableName . '.*');
888  }
889  $this->addRecordTypeConstraint($rightClassName);
890  $rightTableAlias = $this->getUniqueAlias($rightTableName);
891  $joinCondition = $join->getJoinCondition();
892  $joinConditionExpression = null;
893  $this->unionTableAliasCache[] = $rightTableAlias;
894  if ($joinCondition instanceof Qom\EquiJoinCondition) {
895  $column1Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty1Name(), $leftClassName);
896  $column2Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty2Name(), $rightClassName);
897 
898  $joinConditionExpression = $this->queryBuilder->expr()->eq(
899  $leftTableAlias . '.' . $column1Name,
900  $this->queryBuilder->quoteIdentifier($rightTableAlias . '.' . $column2Name)
901  );
902  }
903  $this->queryBuilder->leftJoin($leftTableAlias, $rightTableName, $rightTableAlias, $joinConditionExpression);
904  if ($rightSource instanceof Qom\JoinInterface) {
905  $this->parseJoin($rightSource, $rightTableAlias);
906  }
907  }
908 
917  protected function getUniqueAlias($tableName, $fullPropertyPath = null)
918  {
919  if (isset($fullPropertyPath) && isset($this->tablePropertyMap[$fullPropertyPath])) {
920  return $this->tablePropertyMap[$fullPropertyPath];
921  }
922 
923  $alias = $tableName;
924  $i = 0;
925  while (isset($this->tableAliasMap[$alias])) {
926  $alias = $tableName . $i;
927  $i++;
928  }
929 
930  $this->tableAliasMap[$alias] = $tableName;
931 
932  if (isset($fullPropertyPath)) {
933  $this->tablePropertyMap[$fullPropertyPath] = $alias;
934  }
935 
936  return $alias;
937  }
938 
951  protected function addUnionStatement(&$className, &$tableName, &$propertyPath, &$fullPropertyPath)
952  {
953  $explodedPropertyPath = explode('.', $propertyPath, 2);
954  $propertyName = $explodedPropertyPath[0];
955  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
956  $realTableName = $this->dataMapper->convertClassNameToTableName($className);
957  $tableName = isset($this->tablePropertyMap[$fullPropertyPath]) ? $this->tablePropertyMap[$fullPropertyPath] : $realTableName;
958  $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
959 
960  if ($columnMap === null) {
961  throw new MissingColumnMapException('The ColumnMap for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1355142232);
962  }
963 
964  $parentKeyFieldName = $columnMap->getParentKeyFieldName();
965  $childTableName = $columnMap->getChildTableName();
966 
967  if ($childTableName === null) {
968  throw new InvalidRelationConfigurationException('The relation information for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1353170925);
969  }
970 
971  $fullPropertyPath .= ($fullPropertyPath === '') ? $propertyName : '.' . $propertyName;
972  $childTableAlias = $this->getUniqueAlias($childTableName, $fullPropertyPath);
973 
974  // If there is already a union with the current identifier we do not need to build it again and exit early.
975  if (in_array($childTableAlias, $this->unionTableAliasCache, true)) {
976  $propertyPath = $explodedPropertyPath[1];
977  $tableName = $childTableAlias;
978  $className = $this->dataMapper->getType($className, $propertyName);
979  return;
980  }
981 
982  if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_ONE) {
983  if (isset($parentKeyFieldName)) {
984  // @todo: no test for this part yet
985  $joinConditionExpression = $this->queryBuilder->expr()->eq(
986  $tableName . '.uid',
987  $this->queryBuilder->quoteIdentifier($childTableAlias . '.' . $parentKeyFieldName)
988  );
989  } else {
990  $joinConditionExpression = $this->queryBuilder->expr()->eq(
991  $tableName . '.' . $columnName,
992  $this->queryBuilder->quoteIdentifier($childTableAlias . '.uid')
993  );
994  }
995  $this->queryBuilder->leftJoin($tableName, $childTableName, $childTableAlias, $joinConditionExpression);
996  $this->unionTableAliasCache[] = $childTableAlias;
997  $this->queryBuilder->andWhere(
998  $this->getAdditionalMatchFieldsStatement($this->queryBuilder->expr(), $columnMap, $childTableAlias, $realTableName)
999  );
1000  } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
1001  // @todo: no tests for this part yet
1002  if (isset($parentKeyFieldName)) {
1003  $joinConditionExpression = $this->queryBuilder->expr()->eq(
1004  $tableName . '.uid',
1005  $this->queryBuilder->quoteIdentifier($childTableAlias . '.' . $parentKeyFieldName)
1006  );
1007  } else {
1008  $joinConditionExpression = $this->queryBuilder->expr()->inSet(
1009  $tableName . '.' . $columnName,
1010  $this->queryBuilder->quoteIdentifier($childTableAlias . '.uid'),
1011  true
1012  );
1013  }
1014  $this->queryBuilder->leftJoin($tableName, $childTableName, $childTableAlias, $joinConditionExpression);
1015  $this->unionTableAliasCache[] = $childTableAlias;
1016  $this->queryBuilder->andWhere(
1017  $this->getAdditionalMatchFieldsStatement($this->queryBuilder->expr(), $columnMap, $childTableAlias, $realTableName)
1018  );
1019  $this->suggestDistinctQuery = true;
1020  } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
1021  $relationTableName = $columnMap->getRelationTableName();
1022  $relationTableAlias = $relationTableAlias = $this->getUniqueAlias($relationTableName, $fullPropertyPath . '_mm');
1023 
1024  $joinConditionExpression = $this->queryBuilder->expr()->andX(
1025  $this->queryBuilder->expr()->eq(
1026  $tableName . '.uid',
1027  $this->queryBuilder->quoteIdentifier(
1028  $relationTableAlias . '.' . $columnMap->getParentKeyFieldName()
1029  )
1030  ),
1031  $this->getAdditionalMatchFieldsStatement($this->queryBuilder->expr(), $columnMap, $relationTableAlias, $realTableName)
1032  );
1033  $this->queryBuilder->leftJoin($tableName, $relationTableName, $relationTableAlias, $joinConditionExpression);
1034  $joinConditionExpression = $this->queryBuilder->expr()->eq(
1035  $relationTableAlias . '.' . $columnMap->getChildKeyFieldName(),
1036  $this->queryBuilder->quoteIdentifier($childTableAlias . '.uid')
1037  );
1038  $this->queryBuilder->leftJoin($relationTableAlias, $childTableName, $childTableAlias, $joinConditionExpression);
1039  $this->unionTableAliasCache[] = $childTableAlias;
1040  $this->suggestDistinctQuery = true;
1041  } else {
1042  throw new Exception('Could not determine type of relation.', 1252502725);
1043  }
1044  $propertyPath = $explodedPropertyPath[1];
1045  $tableName = $childTableAlias;
1046  $className = $this->dataMapper->getType($className, $propertyName);
1047  }
1048 
1058  protected function replaceTableNameWithAlias($statement, $tableName, $tableAlias)
1059  {
1060  if ($tableAlias !== $tableName) {
1062  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
1063  $quotedTableName = $connection->quoteIdentifier($tableName);
1064  $quotedTableAlias = $connection->quoteIdentifier($tableAlias);
1065  $statement = str_replace(
1066  [$tableName . '.', $quotedTableName . '.'],
1067  [$tableAlias . '.', $quotedTableAlias . '.'],
1068  $statement
1069  );
1070  }
1071 
1072  return $statement;
1073  }
1074 
1078  protected function getPageRepository()
1079  {
1080  if (!$this->pageRepository instanceof PageRepository) {
1081  if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($this->getTSFE())) {
1082  $this->pageRepository = $this->getTSFE()->sys_page;
1083  } else {
1084  $this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
1085  }
1086  }
1087 
1088  return $this->pageRepository;
1089  }
1090 
1094  protected function getTSFE()
1095  {
1096  return $GLOBALS['TSFE'] ?? null;
1097  }
1098 }
addUnionStatement(&$className, &$tableName, &$propertyPath, &$fullPropertyPath)
getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted)
getVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, $tableAlias)
static BEenableFields($table, $inv=false)
parseConstraint(Qom\ConstraintInterface $constraint, Qom\SourceInterface $source)
getPageIdStatement($tableName, $tableAlias, array $storagePageIds)
getSysLanguageStatement($tableName, $tableAlias, $querySettings)
parseOrderings(array $orderings, Qom\SourceInterface $source)
static makeInstance($className,... $constructorArguments)
getAdditionalMatchFieldsStatement($exprBuilder, $columnMap, $childTableAlias, $parentTable=null)
getFrontendConstraintStatement($tableName, $ignoreEnableFields, array $enableFieldsToBeIgnored=[], $includeDeleted)
parseOperand(Qom\DynamicOperandInterface $operand, Qom\SourceInterface $source)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
getAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableName, $tableAlias=null)
parseDynamicOperand(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)
static deleteClause($table, $tableAlias='')
parseComparison(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)