TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
Typo3DbQueryParser.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic\Storage;
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 
28 use TYPO3\CMS\Extbase\Persistence\Generic\Qom;
32 
37 {
41  protected $dataMapper;
42 
48  protected $pageRepository;
49 
54 
60  protected $queryBuilder;
61 
70  protected $tablePropertyMap = [];
71 
78  protected $tableAliasMap = [];
79 
85  protected $unionTableAliasCache = [];
86 
90  protected $tableName = '';
91 
95  public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
96  {
97  $this->dataMapper = $dataMapper;
98  }
99 
103  public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
104  {
105  $this->environmentService = $environmentService;
106  }
107 
115  {
116  // Reset all properties
117  $this->tablePropertyMap = [];
118  $this->tableAliasMap = [];
119  $this->unionTableAliasCache = [];
120  $this->tableName = '';
121  // Find the right table name
122  $source = $query->getSource();
123  $this->initializeQueryBuilder($source);
124 
125  $constraint = $query->getConstraint();
126  if ($constraint instanceof Qom\ConstraintInterface) {
127  $wherePredicates = $this->parseConstraint($constraint, $source);
128  if (!empty($wherePredicates)) {
129  $this->queryBuilder->andWhere($wherePredicates);
130  }
131  }
132 
133  $this->parseOrderings($query->getOrderings(), $source);
134  $this->addTypo3Constraints($query);
135 
136  return $this->queryBuilder;
137  }
138 
145  protected function initializeQueryBuilder(Qom\SourceInterface $source)
146  {
147  if ($source instanceof Qom\SelectorInterface) {
148  $className = $source->getNodeTypeName();
149  $tableName = $this->dataMapper->getDataMap($className)->getTableName();
150  $this->tableName = $tableName;
151 
152  $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
153  ->getQueryBuilderForTable($tableName);
154 
155  $this->queryBuilder
156  ->getRestrictions()
157  ->removeAll();
158 
159  $tableAlias = $this->getUniqueAlias($tableName);
160 
161  $this->queryBuilder
162  ->select($tableAlias . '.*')
163  ->from($tableName, $tableAlias);
164 
165  $this->addRecordTypeConstraint($className);
166  } elseif ($source instanceof Qom\JoinInterface) {
167  $leftSource = $source->getLeft();
168  $leftTableName = $leftSource->getSelectorName();
169 
170  $this->queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
171  ->getQueryBuilderForTable($leftTableName);
172  $leftTableAlias = $this->getUniqueAlias($leftTableName);
173  $this->queryBuilder
174  ->select($leftTableAlias . '.*')
175  ->from($leftTableName, $leftTableAlias);
176  $this->parseJoin($source, $leftTableAlias);
177  }
178  }
179 
188  protected function parseConstraint(Qom\ConstraintInterface $constraint, Qom\SourceInterface $source)
189  {
190  if ($constraint instanceof Qom\AndInterface) {
191  $constraint1 = $constraint->getConstraint1();
192  $constraint2 = $constraint->getConstraint2();
193  if (($constraint1 instanceof Qom\ConstraintInterface)
194  && ($constraint2 instanceof Qom\ConstraintInterface)
195  ) {
196  return $this->queryBuilder->expr()->andX(
197  $this->parseConstraint($constraint1, $source),
198  $this->parseConstraint($constraint2, $source)
199  );
200  } else {
201  return '';
202  }
203  } elseif ($constraint instanceof Qom\OrInterface) {
204  $constraint1 = $constraint->getConstraint1();
205  $constraint2 = $constraint->getConstraint2();
206  if (($constraint1 instanceof Qom\ConstraintInterface)
207  && ($constraint2 instanceof Qom\ConstraintInterface)
208  ) {
209  return $this->queryBuilder->expr()->orX(
210  $this->parseConstraint($constraint->getConstraint1(), $source),
211  $this->parseConstraint($constraint->getConstraint2(), $source)
212  );
213  } else {
214  return '';
215  }
216  } elseif ($constraint instanceof Qom\NotInterface) {
217  return ' NOT(' . $this->parseConstraint($constraint->getConstraint(), $source) . ')';
218  } elseif ($constraint instanceof Qom\ComparisonInterface) {
219  return $this->parseComparison($constraint, $source);
220  } else {
221  throw new \RuntimeException('not implemented', 1476199898);
222  }
223  }
224 
233  protected function parseOrderings(array $orderings, Qom\SourceInterface $source)
234  {
235  foreach ($orderings as $propertyName => $order) {
237  throw new UnsupportedOrderException('Unsupported order encountered.', 1242816074);
238  }
239  $className = null;
240  $tableName = '';
241  if ($source instanceof Qom\SelectorInterface) {
242  $className = $source->getNodeTypeName();
243  $tableName = $this->dataMapper->convertClassNameToTableName($className);
244  $fullPropertyPath = '';
245  while (strpos($propertyName, '.') !== false) {
246  $this->addUnionStatement($className, $tableName, $propertyName, $fullPropertyPath);
247  }
248  } elseif ($source instanceof Qom\JoinInterface) {
249  $tableName = $source->getLeft()->getSelectorName();
250  }
251  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
252  if ($tableName !== '') {
253  $this->queryBuilder->addOrderBy($tableName . '.' . $columnName, $order);
254  } else {
255  $this->queryBuilder->addOrderBy($columnName, $order);
256  }
257  }
258  }
259 
266  protected function addTypo3Constraints(QueryInterface $query)
267  {
268  foreach ($this->tableAliasMap as $tableAlias => $tableName) {
269  $additionalWhereClauses = $this->getAdditionalWhereClause($query->getQuerySettings(), $tableName, $tableAlias);
270  $statement = $this->getVisibilityConstraintStatement($query->getQuerySettings(), $tableName, $tableAlias);
271  if ($statement !== '') {
272  $additionalWhereClauses[] = $statement;
273  }
274  if (!empty($additionalWhereClauses)) {
275  if (in_array($tableAlias, $this->unionTableAliasCache, true)) {
276  $this->queryBuilder->andWhere(
277  $this->queryBuilder->expr()->orX(
278  $this->queryBuilder->expr()->andX(...$additionalWhereClauses),
279  $this->queryBuilder->expr()->isNull($tableAlias . '.uid')
280  )
281  );
282  } else {
283  $this->queryBuilder->andWhere(...$additionalWhereClauses);
284  }
285  }
286  }
287  }
288 
298  protected function parseComparison(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)
299  {
300  if ($comparison->getOperator() === QueryInterface::OPERATOR_CONTAINS) {
301  if ($comparison->getOperand2() === null) {
302  return '1<>1';
303  } else {
304  $value = $this->dataMapper->getPlainValue($comparison->getOperand2());
305  if (!$source instanceof Qom\SelectorInterface) {
306  throw new \RuntimeException('Source is not of type "SelectorInterface"', 1395362539);
307  }
308  $className = $source->getNodeTypeName();
309  $tableName = $this->dataMapper->convertClassNameToTableName($className);
310  $operand1 = $comparison->getOperand1();
311  $propertyName = $operand1->getPropertyName();
312  $fullPropertyPath = '';
313  while (strpos($propertyName, '.') !== false) {
314  $this->addUnionStatement($className, $tableName, $propertyName, $fullPropertyPath);
315  }
316  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
317  $dataMap = $this->dataMapper->getDataMap($className);
318  $columnMap = $dataMap->getColumnMap($propertyName);
319  $typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
320  if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
321  $relationTableName = $columnMap->getRelationTableName();
322  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
323  $queryBuilderForSubselect
324  ->select($columnMap->getParentKeyFieldName())
325  ->from($relationTableName)
326  ->where(
327  $queryBuilderForSubselect->expr()->eq(
328  $columnMap->getChildKeyFieldName(),
329  $this->queryBuilder->createNamedParameter($value)
330  )
331  );
332  $additionalWhereForMatchFields = $this->getAdditionalMatchFieldsStatement($queryBuilderForSubselect->expr(), $columnMap, $relationTableName, $relationTableName);
333  if ($additionalWhereForMatchFields) {
334  $queryBuilderForSubselect->andWhere($additionalWhereForMatchFields);
335  }
336 
337  return $this->queryBuilder->expr()->comparison(
338  $this->queryBuilder->quoteIdentifier($tableName . '.uid'),
339  'IN',
340  '(' . $queryBuilderForSubselect->getSQL() . ')'
341  );
342  } elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
343  $parentKeyFieldName = $columnMap->getParentKeyFieldName();
344  if (isset($parentKeyFieldName)) {
345  $childTableName = $columnMap->getChildTableName();
346 
347  // Build the SQL statement of the subselect
348  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
349  $queryBuilderForSubselect
350  ->select($parentKeyFieldName)
351  ->from($childTableName)
352  ->where(
353  $queryBuilderForSubselect->expr()->eq(
354  'uid',
355  (int)$value
356  )
357  );
358 
359  // Add it to the main query
360  return $this->queryBuilder->expr()->eq(
361  $tableName . '.uid',
362  $queryBuilderForSubselect->getSQL()
363  );
364  } else {
365  return $this->queryBuilder->expr()->inSet(
366  $tableName . '.' . $columnName,
367  $this->queryBuilder->createNamedParameter($value)
368  );
369  }
370  } else {
371  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
372  }
373  }
374  } else {
375  return $this->parseDynamicOperand($comparison, $source);
376  }
377  }
378 
387  protected function parseDynamicOperand(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)
388  {
389  $value = $comparison->getOperand2();
390  $fieldName = $this->parseOperand($comparison->getOperand1(), $source);
391  $expr = null;
392  $exprBuilder = $this->queryBuilder->expr();
393  switch ($comparison->getOperator()) {
395  $hasValue = false;
396  $plainValues = [];
397  foreach ($value as $singleValue) {
398  $plainValue = $this->dataMapper->getPlainValue($singleValue);
399  if ($plainValue !== null) {
400  $hasValue = true;
401  $plainValues[] = $plainValue;
402  }
403  }
404  if ($hasValue) {
405  $expr = $exprBuilder->comparison($fieldName, 'IN', '(' . implode(', ', $plainValues) . ')');
406  } else {
407  $expr = '1<>1';
408  }
409  break;
411  if ($value === null) {
412  $expr = $fieldName . ' IS NULL';
413  } else {
414  $value = $this->queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($value));
415  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::EQ, $value);
416  }
417  break;
419  $expr = $fieldName . ' IS NULL';
420  break;
422  if ($value === null) {
423  $expr = $fieldName . ' IS NOT NULL';
424  } else {
425  $value = $this->queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($value));
426  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::NEQ, $value);
427  }
428  break;
430  $expr = $fieldName . ' IS NOT NULL';
431  break;
433  $value = $this->queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($value), \PDO::PARAM_INT);
434  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::LT, $value);
435  break;
437  $value = $this->queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($value), \PDO::PARAM_INT);
438  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::LTE, $value);
439  break;
441  $value = $this->queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($value), \PDO::PARAM_INT);
442  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::GT, $value);
443  break;
445  $value = $this->queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($value), \PDO::PARAM_INT);
446  $expr = $exprBuilder->comparison($fieldName, $exprBuilder::GTE, $value);
447  break;
449  $value = $this->queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($value));
450  $expr = $exprBuilder->comparison($fieldName, 'LIKE', $value);
451  break;
452  default:
453  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Unsupported operator encountered.', 1242816073);
454  }
455  return $expr;
456  }
457 
464  protected function parseOperand(Qom\DynamicOperandInterface $operand, Qom\SourceInterface $source)
465  {
466  if ($operand instanceof Qom\LowerCaseInterface) {
467  $constraintSQL = 'LOWER(' . $this->parseOperand($operand->getOperand(), $source) . ')';
468  } elseif ($operand instanceof Qom\UpperCaseInterface) {
469  $constraintSQL = 'UPPER(' . $this->parseOperand($operand->getOperand(), $source) . ')';
470  } elseif ($operand instanceof Qom\PropertyValueInterface) {
471  $propertyName = $operand->getPropertyName();
472  $className = '';
473  if ($source instanceof Qom\SelectorInterface) {
474  $className = $source->getNodeTypeName();
475  $tableName = $this->dataMapper->convertClassNameToTableName($className);
476  $fullPropertyPath = '';
477  while (strpos($propertyName, '.') !== false) {
478  $this->addUnionStatement($className, $tableName, $propertyName, $fullPropertyPath);
479  }
480  } elseif ($source instanceof Qom\JoinInterface) {
481  $tableName = $source->getJoinCondition()->getSelector1Name();
482  }
483  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
484  $constraintSQL = (!empty($tableName) ? $tableName . '.' : '') . $columnName;
485  $constraintSQL = $this->queryBuilder->getConnection()->quoteIdentifier($constraintSQL);
486  } else {
487  throw new \InvalidArgumentException('Given operand has invalid type "' . get_class($operand) . '".', 1395710211);
488  }
489  return $constraintSQL;
490  }
491 
498  protected function addRecordTypeConstraint($className)
499  {
500  if ($className !== null) {
501  $dataMap = $this->dataMapper->getDataMap($className);
502  if ($dataMap->getRecordTypeColumnName() !== null) {
503  $recordTypes = [];
504  if ($dataMap->getRecordType() !== null) {
505  $recordTypes[] = $dataMap->getRecordType();
506  }
507  foreach ($dataMap->getSubclasses() as $subclassName) {
508  $subclassDataMap = $this->dataMapper->getDataMap($subclassName);
509  if ($subclassDataMap->getRecordType() !== null) {
510  $recordTypes[] = $subclassDataMap->getRecordType();
511  }
512  }
513  if (!empty($recordTypes)) {
514  $recordTypeStatements = [];
515  foreach ($recordTypes as $recordType) {
516  $tableName = $dataMap->getTableName();
517  $recordTypeStatements[] = $this->queryBuilder->expr()->eq(
518  $tableName . '.' . $dataMap->getRecordTypeColumnName(),
519  $this->queryBuilder->createNamedParameter($recordType)
520  );
521  }
522  $this->queryBuilder->andWhere(
523  $this->queryBuilder->expr()->orX(...$recordTypeStatements)
524  );
525  }
526  }
527  }
528  }
529 
540  protected function getAdditionalMatchFieldsStatement($exprBuilder, $columnMap, $childTableAlias, $parentTable = null)
541  {
542  $additionalWhereForMatchFields = [];
543  $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
544  if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
545  foreach ($relationTableMatchFields as $fieldName => $value) {
546  $additionalWhereForMatchFields[] = $exprBuilder->eq($childTableAlias . '.' . $fieldName, $this->queryBuilder->createNamedParameter($value));
547  }
548  }
549 
550  if (isset($parentTable)) {
551  $parentTableFieldName = $columnMap->getParentTableFieldName();
552  if (!empty($parentTableFieldName)) {
553  $additionalWhereForMatchFields[] = $exprBuilder->eq($childTableAlias . '.' . $parentTableFieldName, $this->queryBuilder->createNamedParameter($parentTable));
554  }
555  }
556 
557  if (!empty($additionalWhereForMatchFields)) {
558  return $exprBuilder->andX(...$additionalWhereForMatchFields);
559  } else {
560  return '';
561  }
562  }
563 
572  protected function getAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableName, $tableAlias = null)
573  {
574  $whereClause = [];
575  if ($querySettings->getRespectSysLanguage()) {
576  $systemLanguageStatement = $this->getSysLanguageStatement($tableName, $tableAlias, $querySettings);
577  if (!empty($systemLanguageStatement)) {
578  $whereClause[] = $systemLanguageStatement;
579  }
580  }
581 
582  if ($querySettings->getRespectStoragePage()) {
583  $pageIdStatement = $this->getPageIdStatement($tableName, $tableAlias, $querySettings->getStoragePageIds());
584  if (!empty($pageIdStatement)) {
585  $whereClause[] = $pageIdStatement;
586  }
587  }
588 
589  return $whereClause;
590  }
591 
600  protected function getVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, $tableAlias)
601  {
602  $statement = '';
603  if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
604  $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
605  $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
606  $includeDeleted = $querySettings->getIncludeDeleted();
607  if ($this->environmentService->isEnvironmentInFrontendMode()) {
608  $statement .= $this->getFrontendConstraintStatement($tableName, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
609  } else {
610  // TYPO3_MODE === 'BE'
611  $statement .= $this->getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted);
612  }
613  if (!empty($statement)) {
614  $statement = $this->replaceTableNameWithAlias($statement, $tableName, $tableAlias);
615  $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
616  }
617  }
618  return $statement;
619  }
620 
631  protected function getFrontendConstraintStatement($tableName, $ignoreEnableFields, array $enableFieldsToBeIgnored = [], $includeDeleted)
632  {
633  $statement = '';
634  if ($ignoreEnableFields && !$includeDeleted) {
635  if (!empty($enableFieldsToBeIgnored)) {
636  // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
637  $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
638  } else {
639  $statement .= $this->getPageRepository()->deleteClause($tableName);
640  }
641  } elseif (!$ignoreEnableFields && !$includeDeleted) {
642  $statement .= $this->getPageRepository()->enableFields($tableName);
643  } elseif (!$ignoreEnableFields && $includeDeleted) {
644  throw new InconsistentQuerySettingsException('Query setting "ignoreEnableFields=FALSE" can not be used together with "includeDeleted=TRUE" in frontend context.', 1460975922);
645  }
646  return $statement;
647  }
648 
657  protected function getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted)
658  {
659  $statement = '';
660  if (!$ignoreEnableFields) {
662  }
663  if (!$includeDeleted) {
665  }
666  return $statement;
667  }
668 
677  protected function getSysLanguageStatement($tableName, $tableAlias, $querySettings)
678  {
679  if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
680  if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
681  // Select all entries for the current language
682  // If any language is set -> get those entries which are not translated yet
683  // They will be removed by \TYPO3\CMS\Frontend\Page\PageRepository::getRecordOverlay if not matching overlay mode
684  $languageField = $GLOBALS['TCA'][$tableName]['ctrl']['languageField'];
685 
686  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
687  && $querySettings->getLanguageUid() > 0
688  ) {
689  $mode = $querySettings->getLanguageMode();
690 
691  if ($mode === 'strict') {
692  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
693  $queryBuilderForSubselect
694  ->select($tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
695  ->from($tableName)
696  ->where(
697  $queryBuilderForSubselect->expr()->andX(
698  $queryBuilderForSubselect->expr()->gt($tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'], 0),
699  $queryBuilderForSubselect->expr()->eq($tableName . '.' . $languageField, (int)$querySettings->getLanguageUid())
700  )
701  );
702  return $this->queryBuilder->expr()->orX(
703  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, -1),
704  $this->queryBuilder->expr()->andX(
705  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, (int)$querySettings->getLanguageUid()),
706  $this->queryBuilder->expr()->eq($tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'], 0)
707  ),
708  $this->queryBuilder->expr()->andX(
709  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, 0),
710  $this->queryBuilder->expr()->in(
711  $tableAlias . '.uid',
712  $queryBuilderForSubselect->getSQL()
713 
714  )
715  )
716  );
717  } else {
718  $queryBuilderForSubselect = $this->queryBuilder->getConnection()->createQueryBuilder();
719  $queryBuilderForSubselect
720  ->select($tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
721  ->from($tableName)
722  ->where(
723  $queryBuilderForSubselect->expr()->andX(
724  $queryBuilderForSubselect->expr()->gt($tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'], 0),
725  $queryBuilderForSubselect->expr()->eq($tableName . '.' . $languageField, (int)$querySettings->getLanguageUid())
726  )
727  );
728  return $this->queryBuilder->expr()->orX(
729  $this->queryBuilder->expr()->in($tableAlias . '.' . $languageField, [(int)$querySettings->getLanguageUid(), -1]),
730  $this->queryBuilder->expr()->andX(
731  $this->queryBuilder->expr()->eq($tableAlias . '.' . $languageField, 0),
732  $this->queryBuilder->expr()->notIn(
733  $tableAlias . '.uid',
734  $queryBuilderForSubselect->getSQL()
735 
736  )
737  )
738  );
739  }
740  } else {
741  return $this->queryBuilder->expr()->in(
742  $tableAlias . '.' . $languageField,
743  [(int)$querySettings->getLanguageUid(), -1]
744  );
745  }
746  }
747  }
748  return '';
749  }
750 
760  protected function getPageIdStatement($tableName, $tableAlias, array $storagePageIds)
761  {
762  if (!is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
763  return '';
764  }
765 
766  $rootLevel = (int)$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'];
767  switch ($rootLevel) {
768  // Only in pid 0
769  case 1:
770  $storagePageIds = [0];
771  break;
772  // Pid 0 and pagetree
773  case -1:
774  if (empty($storagePageIds)) {
775  $storagePageIds = [0];
776  } else {
777  $storagePageIds[] = 0;
778  }
779  break;
780  // Only pagetree or not set
781  case 0:
782  if (empty($storagePageIds)) {
783  throw new InconsistentQuerySettingsException('Missing storage page ids.', 1365779762);
784  }
785  break;
786  // Invalid configuration
787  default:
788  return '';
789  }
790  $storagePageIds = array_map('intval', $storagePageIds);
791  if (count($storagePageIds) === 1) {
792  return $this->queryBuilder->expr()->eq($tableAlias . '.pid', reset($storagePageIds));
793  } else {
794  return $this->queryBuilder->expr()->in($tableAlias . '.pid', $storagePageIds);
795  }
796  }
797 
805  protected function parseJoin(Qom\JoinInterface $join, $leftTableAlias)
806  {
807  $leftSource = $join->getLeft();
808  $leftClassName = $leftSource->getNodeTypeName();
809  $this->addRecordTypeConstraint($leftClassName);
810  $rightSource = $join->getRight();
811  if ($rightSource instanceof Qom\JoinInterface) {
812  $left = $rightSource->getLeft();
813  $rightClassName = $left->getNodeTypeName();
814  $rightTableName = $left->getSelectorName();
815  } else {
816  $rightClassName = $rightSource->getNodeTypeName();
817  $rightTableName = $rightSource->getSelectorName();
818  $this->queryBuilder->addSelect($rightTableName . '.*');
819  }
820  $this->addRecordTypeConstraint($rightClassName);
821  $rightTableAlias = $this->getUniqueAlias($rightTableName);
822  $joinCondition = $join->getJoinCondition();
823  $joinConditionExpression = null;
824  $this->unionTableAliasCache[] = $rightTableAlias;
825  if ($joinCondition instanceof Qom\EquiJoinCondition) {
826  $column1Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty1Name(), $leftClassName);
827  $column2Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty2Name(), $rightClassName);
828 
829  $joinConditionExpression = $this->queryBuilder->expr()->eq(
830  $leftTableAlias . '.' . $column1Name,
831  $rightTableAlias . '.' . $column2Name
832  );
833  }
834  $this->queryBuilder->leftJoin($leftTableAlias, $rightTableName, $rightTableAlias, $joinConditionExpression);
835  if ($rightSource instanceof Qom\JoinInterface) {
836  $this->parseJoin($rightSource, $rightTableAlias);
837  }
838  }
839 
848  protected function getUniqueAlias($tableName, $fullPropertyPath = null)
849  {
850  if (isset($fullPropertyPath) && isset($this->tablePropertyMap[$fullPropertyPath])) {
851  return $this->tablePropertyMap[$fullPropertyPath];
852  }
853 
854  $alias = $tableName;
855  $i = 0;
856  while (isset($this->tableAliasMap[$alias])) {
857  $alias = $tableName . $i;
858  $i++;
859  }
860 
861  $this->tableAliasMap[$alias] = $tableName;
862 
863  if (isset($fullPropertyPath)) {
864  $this->tablePropertyMap[$fullPropertyPath] = $alias;
865  }
866 
867  return $alias;
868  }
869 
882  protected function addUnionStatement(&$className, &$tableName, &$propertyPath, &$fullPropertyPath)
883  {
884  $explodedPropertyPath = explode('.', $propertyPath, 2);
885  $propertyName = $explodedPropertyPath[0];
886  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
887  $realTableName = $this->dataMapper->convertClassNameToTableName($className);
888  $tableName = isset($this->tablePropertyMap[$fullPropertyPath]) ? $this->tablePropertyMap[$fullPropertyPath] : $realTableName;
889  $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
890 
891  if ($columnMap === null) {
892  throw new MissingColumnMapException('The ColumnMap for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1355142232);
893  }
894 
895  $parentKeyFieldName = $columnMap->getParentKeyFieldName();
896  $childTableName = $columnMap->getChildTableName();
897 
898  if ($childTableName === null) {
899  throw new InvalidRelationConfigurationException('The relation information for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1353170925);
900  }
901 
902  $fullPropertyPath .= ($fullPropertyPath === '') ? $propertyName : '.' . $propertyName;
903  $childTableAlias = $this->getUniqueAlias($childTableName, $fullPropertyPath);
904 
905  // If there is already exists a union with the current identifier we do not need to build it again and exit early.
906  if (in_array($childTableAlias, $this->unionTableAliasCache, true)) {
907  return;
908  }
909 
910  if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_ONE) {
911  if (isset($parentKeyFieldName)) {
912  // @todo: no test for this part yet
913  $joinConditionExpression = $this->queryBuilder->expr()->eq(
914  $tableName . '.uid',
915  $childTableAlias . '.' . $parentKeyFieldName
916  );
917  } else {
918  $joinConditionExpression = $this->queryBuilder->expr()->eq(
919  $tableName . '.' . $columnName,
920  $childTableAlias . '.uid'
921  );
922  }
923  $this->queryBuilder->leftJoin($tableName, $childTableName, $childTableAlias, $joinConditionExpression);
924  $this->unionTableAliasCache[] = $childTableAlias;
925  $this->queryBuilder->andWhere(
926  $this->getAdditionalMatchFieldsStatement($this->queryBuilder->expr(), $columnMap, $childTableAlias, $realTableName)
927  );
928  } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
929  // @todo: no tests for this part yet
930  if (isset($parentKeyFieldName)) {
931  $joinConditionExpression = $this->queryBuilder->expr()->eq(
932  $tableName . '.uid',
933  $childTableAlias . '.' . $parentKeyFieldName
934  );
935  } else {
936  $joinConditionExpression = $this->queryBuilder->expr()->inSet(
937  $tableName . '.' . $columnName,
938  $childTableAlias . '.uid'
939  );
940  }
941  $this->queryBuilder->leftJoin($tableName, $childTableName, $childTableAlias, $joinConditionExpression);
942  $this->unionTableAliasCache[] = $childTableAlias;
943  $this->queryBuilder->andWhere(
944  $this->getAdditionalMatchFieldsStatement($this->queryBuilder->expr(), $columnMap, $childTableAlias, $realTableName)
945  );
946  } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
947  $relationTableName = $columnMap->getRelationTableName();
948  $relationTableAlias = $relationTableAlias = $this->getUniqueAlias($relationTableName, $fullPropertyPath . '_mm');
949 
950  $joinConditionExpression = $this->queryBuilder->expr()->eq(
951  $tableName . '.uid',
952  $relationTableAlias . '.' . $columnMap->getParentKeyFieldName()
953  );
954  $this->queryBuilder->leftJoin($tableName, $relationTableName, $relationTableAlias, $joinConditionExpression);
955  $joinConditionExpression = $this->queryBuilder->expr()->eq(
956  $relationTableAlias . '.' . $columnMap->getChildKeyFieldName(),
957  $childTableAlias . '.uid'
958  );
959  $this->queryBuilder->leftJoin($relationTableAlias, $childTableName, $childTableAlias, $joinConditionExpression);
960  $this->queryBuilder->andWhere(
961  $this->getAdditionalMatchFieldsStatement($this->queryBuilder->expr(), $columnMap, $relationTableAlias, $realTableName)
962  );
963  $this->unionTableAliasCache[] = $childTableAlias;
964  $this->queryBuilder->addGroupBy($this->tableName . '.uid');
965  } else {
966  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Could not determine type of relation.', 1252502725);
967  }
968  $propertyPath = $explodedPropertyPath[1];
969  $tableName = $childTableAlias;
970  $className = $this->dataMapper->getType($className, $propertyName);
971  }
972 
982  protected function replaceTableNameWithAlias($statement, $tableName, $tableAlias)
983  {
984  if ($tableAlias !== $tableName) {
985  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
986  $quotedTableName = $connection->quoteIdentifier($tableName);
987  $quotedTableAlias = $connection->quoteIdentifier($tableAlias);
988  $statement = str_replace(
989  [$tableName . '.', $quotedTableName . '.'],
990  [$tableAlias . '.', $quotedTableAlias . '.'],
991  $statement
992  );
993  }
994 
995  return $statement;
996  }
997 
1001  protected function getPageRepository()
1002  {
1003  if (!$this->pageRepository instanceof PageRepository) {
1004  if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
1005  $this->pageRepository = $GLOBALS['TSFE']->sys_page;
1006  } else {
1007  $this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
1008  }
1009  }
1010 
1011  return $this->pageRepository;
1012  }
1013 }
addUnionStatement(&$className, &$tableName, &$propertyPath, &$fullPropertyPath)
getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted)
parseOrderings(array $orderings, Qom\SourceInterface $source)
getAdditionalMatchFieldsStatement($exprBuilder, $columnMap, $childTableAlias, $parentTable=null)
getSysLanguageStatement($tableName, $tableAlias, $querySettings)
getPageIdStatement($tableName, $tableAlias, array $storagePageIds)
getFrontendConstraintStatement($tableName, $ignoreEnableFields, array $enableFieldsToBeIgnored=[], $includeDeleted)
parseComparison(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)
parseConstraint(Qom\ConstraintInterface $constraint, Qom\SourceInterface $source)
injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
getAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableName, $tableAlias=null)
parseOperand(Qom\DynamicOperandInterface $operand, Qom\SourceInterface $source)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
parseDynamicOperand(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source)
static makeInstance($className,...$constructorArguments)
getVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, $tableAlias)
injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)