TYPO3  7.6
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 
21 use TYPO3\CMS\Extbase\Persistence\Generic\Qom;
22 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException;
23 
28 {
34  protected $databaseHandle;
35 
39  protected $dataMapper;
40 
46  protected $pageRepository;
47 
51  protected $cacheManager;
52 
56  protected $tableColumnCache;
57 
62 
66  public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
67  {
68  $this->dataMapper = $dataMapper;
69  }
70 
74  public function injectCacheManager(\TYPO3\CMS\Core\Cache\CacheManager $cacheManager)
75  {
76  $this->cacheManager = $cacheManager;
77  }
78 
82  public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
83  {
84  $this->environmentService = $environmentService;
85  }
86 
95  protected $tablePropertyMap = array();
96 
100  public function __construct()
101  {
102  $this->databaseHandle = $GLOBALS['TYPO3_DB'];
103  }
104 
110  public function initializeObject()
111  {
112  $this->tableColumnCache = $this->cacheManager->getCache('extbase_typo3dbbackend_tablecolumns');
113  }
114 
121  public function preparseQuery(QueryInterface $query)
122  {
123  list($parameters, $operators) = $this->preparseComparison($query->getConstraint());
124  $hashPartials = array(
125  $query->getQuerySettings(),
126  $query->getSource(),
127  array_keys($parameters),
128  $operators,
129  $query->getOrderings(),
130  );
131  $hash = md5(serialize($hashPartials));
132 
133  return array($hash, $parameters);
134  }
135 
149  protected function preparseComparison($comparison, $qomPath = '')
150  {
151  $parameters = array();
152  $operators = array();
153  $objectsToParse = array();
154 
155  $delimiter = '';
156  if ($comparison instanceof Qom\AndInterface) {
157  $delimiter = 'AND';
158  $objectsToParse = array($comparison->getConstraint1(), $comparison->getConstraint2());
159  } elseif ($comparison instanceof Qom\OrInterface) {
160  $delimiter = 'OR';
161  $objectsToParse = array($comparison->getConstraint1(), $comparison->getConstraint2());
162  } elseif ($comparison instanceof Qom\NotInterface) {
163  $delimiter = 'NOT';
164  $objectsToParse = array($comparison->getConstraint());
165  } elseif ($comparison instanceof Qom\ComparisonInterface) {
166  $operand1 = $comparison->getOperand1();
167  $parameterIdentifier = $this->normalizeParameterIdentifier($qomPath . $operand1->getPropertyName());
168  $comparison->setParameterIdentifier($parameterIdentifier);
169  $operator = $comparison->getOperator();
170  $operand2 = $comparison->getOperand2();
171  if ($operator === QueryInterface::OPERATOR_IN) {
172  $items = array();
173  foreach ($operand2 as $value) {
174  $value = $this->dataMapper->getPlainValue($value);
175  if ($value !== null) {
176  $items[] = $value;
177  }
178  }
179  $parameters[$parameterIdentifier] = $items;
180  } else {
181  $parameters[$parameterIdentifier] = $operand2;
182  }
183  $operators[] = $operator;
184  } elseif (!is_object($comparison)) {
185  $parameters = array(array(), $comparison);
186  return array($parameters, $operators);
187  } else {
188  throw new \Exception('Can not hash Query Component "' . get_class($comparison) . '".', 1392840462);
189  }
190 
191  $childObjectIterator = 0;
192  foreach ($objectsToParse as $objectToParse) {
193  list($preparsedParameters, $preparsedOperators) = $this->preparseComparison($objectToParse, $qomPath . $delimiter . $childObjectIterator++);
194  if (!empty($preparsedParameters)) {
195  $parameters = array_merge($parameters, $preparsedParameters);
196  }
197  if (!empty($preparsedOperators)) {
198  $operators = array_merge($operators, $preparsedOperators);
199  }
200  }
201 
202  return array($parameters, $operators);
203  }
204 
212  public function normalizeParameterIdentifier($identifier)
213  {
214  return ':' . preg_replace('/[^A-Za-z0-9]/', '', $identifier);
215  }
216 
223  public function parseQuery(QueryInterface $query)
224  {
225  $this->tablePropertyMap = array();
226  $sql = array();
227  $sql['keywords'] = array();
228  $sql['tables'] = array();
229  $sql['unions'] = array();
230  $sql['fields'] = array();
231  $sql['where'] = array();
232  $sql['additionalWhereClause'] = array();
233  $sql['orderings'] = array();
234  $sql['limit'] = ((int)$query->getLimit() ?: null);
235  $sql['offset'] = ((int)$query->getOffset() ?: null);
236  $sql['tableAliasMap'] = array();
237  $source = $query->getSource();
238  $this->parseSource($source, $sql);
239  $this->parseConstraint($query->getConstraint(), $source, $sql);
240  $this->parseOrderings($query->getOrderings(), $source, $sql);
241 
242  foreach ($sql['tableAliasMap'] as $tableAlias => $tableName) {
243  $additionalWhereClause = $this->getAdditionalWhereClause($query->getQuerySettings(), $tableName, $tableAlias);
244  if ($additionalWhereClause !== '') {
245  $additionalWhereClause = $this->addNullConditionToStatementIfRequired($sql, $additionalWhereClause, $tableAlias);
246  $sql['additionalWhereClause'][] = $additionalWhereClause;
247  }
248  }
249 
250  return $sql;
251  }
252 
262  public function addDynamicQueryParts(QuerySettingsInterface $querySettings, array &$sql)
263  {
264  if (!isset($sql['additionalWhereClause'])) {
265  throw new \InvalidArgumentException('Invalid statement given.', 1399512421);
266  }
267  foreach ($sql['tableAliasMap'] as $tableAlias => $tableName) {
268  $statement = $this->getVisibilityConstraintStatement($querySettings, $tableName, $tableAlias);
269  if ($statement !== '') {
270  $statement = $this->addNullConditionToStatementIfRequired($sql, $statement, $tableAlias);
271  $sql['additionalWhereClause'][] = $statement;
272  }
273  }
274  }
275 
287  protected function addNullConditionToStatementIfRequired(array $sql, $statement, $tableAlias)
288  {
289  if (isset($sql['unions'][$tableAlias])) {
290  $statement = '((' . $statement . ') OR ' . $tableAlias . '.uid' . ' IS NULL)';
291  }
292 
293  return $statement;
294  }
295 
303  protected function parseSource(Qom\SourceInterface $source, array &$sql)
304  {
305  if ($source instanceof Qom\SelectorInterface) {
306  $className = $source->getNodeTypeName();
307  $tableName = $this->dataMapper->getDataMap($className)->getTableName();
308  $this->addRecordTypeConstraint($className, $sql);
309  $tableName = $this->getUniqueAlias($sql, $tableName);
310  $sql['fields'][$tableName] = $tableName . '.*';
311  $sql['tables'][$tableName] = $tableName;
312  } elseif ($source instanceof Qom\JoinInterface) {
313  $this->parseJoin($source, $sql);
314  }
315  }
316 
325  protected function parseConstraint(Qom\ConstraintInterface $constraint = null, Qom\SourceInterface $source, array &$sql)
326  {
327  if ($constraint instanceof Qom\AndInterface) {
328  $sql['where'][] = '(';
329  $this->parseConstraint($constraint->getConstraint1(), $source, $sql);
330  $sql['where'][] = ' AND ';
331  $this->parseConstraint($constraint->getConstraint2(), $source, $sql);
332  $sql['where'][] = ')';
333  } elseif ($constraint instanceof Qom\OrInterface) {
334  $sql['where'][] = '(';
335  $this->parseConstraint($constraint->getConstraint1(), $source, $sql);
336  $sql['where'][] = ' OR ';
337  $this->parseConstraint($constraint->getConstraint2(), $source, $sql);
338  $sql['where'][] = ')';
339  } elseif ($constraint instanceof Qom\NotInterface) {
340  $sql['where'][] = 'NOT (';
341  $this->parseConstraint($constraint->getConstraint(), $source, $sql);
342  $sql['where'][] = ')';
343  } elseif ($constraint instanceof Qom\ComparisonInterface) {
344  $this->parseComparison($constraint, $source, $sql);
345  }
346  }
347 
357  protected function parseOrderings(array $orderings, Qom\SourceInterface $source, array &$sql)
358  {
359  foreach ($orderings as $propertyName => $order) {
360  switch ($order) {
362  $order = 'ASC';
363  break;
365  $order = 'DESC';
366  break;
367  default:
368  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException('Unsupported order encountered.', 1242816074);
369  }
370  $className = '';
371  $tableName = '';
372  if ($source instanceof Qom\SelectorInterface) {
373  $className = $source->getNodeTypeName();
374  $tableName = $this->dataMapper->convertClassNameToTableName($className);
375  $fullPropertyPath = '';
376  while (strpos($propertyName, '.') !== false) {
377  $this->addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
378  }
379  } elseif ($source instanceof Qom\JoinInterface) {
380  $tableName = $source->getLeft()->getSelectorName();
381  }
382  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
383  if ($tableName !== '') {
384  $sql['orderings'][] = $tableName . '.' . $columnName . ' ' . $order;
385  } else {
386  $sql['orderings'][] = $columnName . ' ' . $order;
387  }
388  }
389  }
390 
401  protected function parseComparison(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source, array &$sql)
402  {
403  $parameterIdentifier = $this->normalizeParameterIdentifier($comparison->getParameterIdentifier());
404 
405  $operator = $comparison->getOperator();
406  $operand2 = $comparison->getOperand2();
407  if ($operator === QueryInterface::OPERATOR_IN) {
408  $hasValue = false;
409  foreach ($operand2 as $value) {
410  $value = $this->dataMapper->getPlainValue($value);
411  if ($value !== null) {
412  $parameters[] = $value;
413  $hasValue = true;
414  }
415  }
416  if ($hasValue === false) {
417  $sql['where'][] = '1<>1';
418  } else {
419  $this->parseDynamicOperand($comparison, $source, $sql);
420  }
421  } elseif ($operator === QueryInterface::OPERATOR_CONTAINS) {
422  if ($operand2 === null) {
423  $sql['where'][] = '1<>1';
424  } else {
425  if (!$source instanceof Qom\SelectorInterface) {
426  throw new \RuntimeException('Source is not of type "SelectorInterface"', 1395362539);
427  }
428  $className = $source->getNodeTypeName();
429  $tableName = $this->dataMapper->convertClassNameToTableName($className);
430  $operand1 = $comparison->getOperand1();
431  $propertyName = $operand1->getPropertyName();
432  $fullPropertyPath = '';
433  while (strpos($propertyName, '.') !== false) {
434  $this->addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
435  }
436  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
437  $dataMap = $this->dataMapper->getDataMap($className);
438  $columnMap = $dataMap->getColumnMap($propertyName);
439  $typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
440  if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
441  $relationTableName = $columnMap->getRelationTableName();
442  $additionalWhereForMatchFields = $this->getAdditionalMatchFieldsStatement($columnMap, $relationTableName, $relationTableName);
443  $sql['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=' . $parameterIdentifier . $additionalWhereForMatchFields . ')';
444  } elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
445  $parentKeyFieldName = $columnMap->getParentKeyFieldName();
446  if (isset($parentKeyFieldName)) {
447  $childTableName = $columnMap->getChildTableName();
448  $sql['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=' . $parameterIdentifier . ')';
449  } else {
450  $sql['where'][] = 'FIND_IN_SET(' . $parameterIdentifier . ', ' . $tableName . '.' . $columnName . ')';
451  }
452  } else {
453  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
454  }
455  }
456  } else {
457  $this->parseDynamicOperand($comparison, $source, $sql);
458  }
459  }
460 
469  protected function parseDynamicOperand(Qom\ComparisonInterface $comparison, Qom\SourceInterface $source, array &$sql)
470  {
471  $operator = $this->resolveOperator($comparison->getOperator());
472  $operand = $comparison->getOperand1();
473 
474  $constraintSQL = $this->parseOperand($operand, $source, $sql) . ' ' . $operator . ' ';
475 
476  $parameterIdentifier = $this->normalizeParameterIdentifier($comparison->getParameterIdentifier());
477  if ($operator === 'IN') {
478  $parameterIdentifier = '(' . $parameterIdentifier . ')';
479  }
480  $constraintSQL .= $parameterIdentifier;
481 
482  $sql['where'][] = $constraintSQL;
483  }
484 
492  protected function parseOperand(Qom\DynamicOperandInterface $operand, Qom\SourceInterface $source, array &$sql)
493  {
494  if ($operand instanceof Qom\LowerCaseInterface) {
495  $constraintSQL = 'LOWER(' . $this->parseOperand($operand->getOperand(), $source, $sql) . ')';
496  } elseif ($operand instanceof Qom\UpperCaseInterface) {
497  $constraintSQL = 'UPPER(' . $this->parseOperand($operand->getOperand(), $source, $sql) . ')';
498  } elseif ($operand instanceof Qom\PropertyValueInterface) {
499  $propertyName = $operand->getPropertyName();
500  $className = '';
501  if ($source instanceof Qom\SelectorInterface) {
502  // @todo Only necessary to differ from Join
503  $className = $source->getNodeTypeName();
504  $tableName = $this->dataMapper->convertClassNameToTableName($className);
505  $fullPropertyPath = '';
506  while (strpos($propertyName, '.') !== false) {
507  $this->addUnionStatement($className, $tableName, $propertyName, $sql, $fullPropertyPath);
508  }
509  } elseif ($source instanceof Qom\JoinInterface) {
510  $tableName = $source->getJoinCondition()->getSelector1Name();
511  }
512  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
513  $constraintSQL = (!empty($tableName) ? $tableName . '.' : '') . $columnName;
514  } else {
515  throw new \InvalidArgumentException('Given operand has invalid type "' . get_class($operand) . '".', 1395710211);
516  }
517  return $constraintSQL;
518  }
519 
527  protected function addRecordTypeConstraint($className, &$sql)
528  {
529  if ($className !== null) {
530  $dataMap = $this->dataMapper->getDataMap($className);
531  if ($dataMap->getRecordTypeColumnName() !== null) {
532  $recordTypes = array();
533  if ($dataMap->getRecordType() !== null) {
534  $recordTypes[] = $dataMap->getRecordType();
535  }
536  foreach ($dataMap->getSubclasses() as $subclassName) {
537  $subclassDataMap = $this->dataMapper->getDataMap($subclassName);
538  if ($subclassDataMap->getRecordType() !== null) {
539  $recordTypes[] = $subclassDataMap->getRecordType();
540  }
541  }
542  if (!empty($recordTypes)) {
543  $recordTypeStatements = array();
544  foreach ($recordTypes as $recordType) {
545  $tableName = $dataMap->getTableName();
546  $recordTypeStatements[] = $tableName . '.' . $dataMap->getRecordTypeColumnName() . '=' . $this->databaseHandle->fullQuoteStr($recordType, $tableName);
547  }
548  $sql['additionalWhereClause'][] = '(' . implode(' OR ', $recordTypeStatements) . ')';
549  }
550  }
551  }
552  }
553 
564  protected function getAdditionalMatchFieldsStatement($columnMap, $childTableName, $childTableAlias, $parentTable = null)
565  {
566  $additionalWhereForMatchFields = '';
567 
568  $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
569  if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
570  $additionalWhere = array();
571  foreach ($relationTableMatchFields as $fieldName => $value) {
572  $additionalWhere[] = $childTableAlias . '.' . $fieldName . ' = ' . $this->databaseHandle->fullQuoteStr($value, $childTableName);
573  }
574  $additionalWhereForMatchFields .= ' AND ' . implode(' AND ', $additionalWhere);
575  }
576 
577  if (isset($parentTable)) {
578  $parentTableFieldName = $columnMap->getParentTableFieldName();
579  if (isset($parentTableFieldName) && $parentTableFieldName !== '') {
580  $additionalWhereForMatchFields .= ' AND ' . $childTableAlias . '.' . $parentTableFieldName . ' = ' . $this->databaseHandle->fullQuoteStr($parentTable, $childTableAlias);
581  }
582  }
583 
584  return $additionalWhereForMatchFields;
585  }
586 
595  protected function getAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableName, $tableAlias = null)
596  {
597  $sysLanguageStatement = '';
598  if ($querySettings->getRespectSysLanguage()) {
599  $sysLanguageStatement = $this->getSysLanguageStatement($tableName, $tableAlias, $querySettings);
600  }
601 
602  $pageIdStatement = '';
603  if ($querySettings->getRespectStoragePage()) {
604  $pageIdStatement = $this->getPageIdStatement($tableName, $tableAlias, $querySettings->getStoragePageIds());
605  }
606 
607  if ($sysLanguageStatement !== '' && $pageIdStatement !== '') {
608  $whereClause = $sysLanguageStatement . ' AND ' . $pageIdStatement;
609  } elseif ($sysLanguageStatement !== '') {
610  $whereClause = $sysLanguageStatement;
611  } else {
612  $whereClause = $pageIdStatement;
613  }
614 
615  return $whereClause;
616  }
617 
626  protected function getVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableName, $tableAlias)
627  {
628  $statement = '';
629  if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
630  $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
631  $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
632  $includeDeleted = $querySettings->getIncludeDeleted();
633  if ($this->environmentService->isEnvironmentInFrontendMode()) {
634  $statement .= $this->getFrontendConstraintStatement($tableName, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
635  } else {
636  // TYPO3_MODE === 'BE'
637  $statement .= $this->getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted);
638  }
639  if (!empty($statement)) {
640  $statement = $this->replaceTableNameWithAlias($statement, $tableName, $tableAlias);
641  $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
642  }
643  }
644  return $statement;
645  }
646 
657  protected function getFrontendConstraintStatement($tableName, $ignoreEnableFields, array $enableFieldsToBeIgnored = array(), $includeDeleted)
658  {
659  $statement = '';
660  if ($ignoreEnableFields && !$includeDeleted) {
661  if (!empty($enableFieldsToBeIgnored)) {
662  // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
663  $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
664  } else {
665  $statement .= $this->getPageRepository()->deleteClause($tableName);
666  }
667  } elseif (!$ignoreEnableFields && !$includeDeleted) {
668  $statement .= $this->getPageRepository()->enableFields($tableName);
669  } elseif (!$ignoreEnableFields && $includeDeleted) {
670  throw new InconsistentQuerySettingsException('Query setting "ignoreEnableFields=FALSE" can not be used together with "includeDeleted=TRUE" in frontend context.', 1327678173);
671  }
672  return $statement;
673  }
674 
683  protected function getBackendConstraintStatement($tableName, $ignoreEnableFields, $includeDeleted)
684  {
685  $statement = '';
686  if (!$ignoreEnableFields) {
687  $statement .= BackendUtility::BEenableFields($tableName);
688  }
689  if (!$includeDeleted) {
690  $statement .= BackendUtility::deleteClause($tableName);
691  }
692  return $statement;
693  }
694 
703  protected function getSysLanguageStatement($tableName, $tableAlias, $querySettings)
704  {
705  $sysLanguageStatement = '';
706  if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
707  if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
708  // Select all entries for the current language
709  $additionalWhereClause = $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . (int)$querySettings->getLanguageUid() . ',-1)';
710  // If any language is set -> get those entries which are not translated yet
711  // They will be removed by \TYPO3\CMS\Frontend\Page\PageRepository::getRecordOverlay if not matching overlay mode
712  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
713  && $querySettings->getLanguageUid() > 0
714  ) {
715  $mode = $querySettings->getLanguageMode();
716  if ($mode === 'strict') {
717  $additionalWhereClause = $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=-1' .
718  ' OR (' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = ' . (int)$querySettings->getLanguageUid() .
719  ' AND ' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '=0' .
720  ') OR (' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
721  ' AND ' . $tableAlias . '.uid IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
722  ' FROM ' . $tableName .
723  ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
724  ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=' . (int)$querySettings->getLanguageUid();
725  } else {
726  $additionalWhereClause .= ' OR (' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
727  ' AND ' . $tableAlias . '.uid NOT IN (SELECT ' . $tableAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
728  ' FROM ' . $tableName .
729  ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
730  ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=' . (int)$querySettings->getLanguageUid();
731  }
732 
733  // Add delete clause to ensure all entries are loaded
734  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
735  $additionalWhereClause .= ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
736  }
737  $additionalWhereClause .= '))';
738  }
739  $sysLanguageStatement = '(' . $additionalWhereClause . ')';
740  }
741  }
742  return $sysLanguageStatement;
743  }
744 
754  protected function getPageIdStatement($tableName, $tableAlias, array $storagePageIds)
755  {
756  $pageIdStatement = '';
757  $tableColumns = $this->tableColumnCache->get($tableName);
758  if ($tableColumns === false) {
759  $tableColumns = $this->databaseHandle->admin_get_fields($tableName);
760  $this->tableColumnCache->set($tableName, $tableColumns);
761  }
762  if (is_array($GLOBALS['TCA'][$tableName]['ctrl']) && array_key_exists('pid', $tableColumns)) {
763  $rootLevel = (int)$GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'];
764  switch ($rootLevel) {
765  // Only in pid 0
766  case 1:
767  return $tableAlias . '.pid = 0';
768  // Pid 0 and pagetree
769  case -1:
770  if (empty($storagePageIds)) {
771  return $tableAlias . '.pid = 0';
772  }
773  $storagePageIds[] = 0;
774  break;
775  // Only pagetree or not set
776  case 0:
777  if (empty($storagePageIds)) {
778  throw new InconsistentQuerySettingsException('Missing storage page ids.', 1365779762);
779  }
780  break;
781  // Invalid configuration
782  default:
783  return '';
784  }
785  $pageIdStatement = $tableAlias . '.pid IN (' . implode(',', $this->databaseHandle->cleanIntArray($storagePageIds)) . ')';
786  }
787  return $pageIdStatement;
788  }
789 
797  protected function parseJoin(Qom\JoinInterface $join, array &$sql)
798  {
799  $leftSource = $join->getLeft();
800  $leftClassName = $leftSource->getNodeTypeName();
801  $leftTableName = $leftSource->getSelectorName();
802  $this->addRecordTypeConstraint($leftClassName, $sql);
803  $rightSource = $join->getRight();
804  if ($rightSource instanceof Qom\JoinInterface) {
805  $left = $rightSource->getLeft();
806  $rightClassName = $left->getNodeTypeName();
807  $rightTableName = $left->getSelectorName();
808  } else {
809  $rightClassName = $rightSource->getNodeTypeName();
810  $rightTableName = $rightSource->getSelectorName();
811  $sql['fields'][$leftTableName] = $rightTableName . '.*';
812  }
813  $this->addRecordTypeConstraint($rightClassName, $sql);
814  $leftTableName = $this->getUniqueAlias($sql, $leftTableName);
815  $sql['tables'][$leftTableName] = $leftTableName;
816  $rightTableName = $this->getUniqueAlias($sql, $rightTableName);
817  $sql['unions'][$rightTableName] = 'LEFT JOIN ' . $rightTableName;
818  $joinCondition = $join->getJoinCondition();
819  if ($joinCondition instanceof Qom\EquiJoinCondition) {
820  $column1Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty1Name(), $leftClassName);
821  $column2Name = $this->dataMapper->convertPropertyNameToColumnName($joinCondition->getProperty2Name(), $rightClassName);
822  $sql['unions'][$rightTableName] .= ' ON ' . $leftTableName . '.' . $column1Name . ' = ' . $rightTableName . '.' . $column2Name;
823  }
824  if ($rightSource instanceof Qom\JoinInterface) {
825  $this->parseJoin($rightSource, $sql);
826  }
827  }
828 
838  protected function getUniqueAlias(array &$sql, $tableName, $fullPropertyPath = null)
839  {
840  if (isset($fullPropertyPath) && isset($this->tablePropertyMap[$fullPropertyPath])) {
841  return $this->tablePropertyMap[$fullPropertyPath];
842  }
843 
844  $alias = $tableName;
845  $i = 0;
846  while (isset($sql['tableAliasMap'][$alias])) {
847  $alias = $tableName . $i;
848  $i++;
849  }
850 
851  $sql['tableAliasMap'][$alias] = $tableName;
852 
853  if (isset($fullPropertyPath)) {
854  $this->tablePropertyMap[$fullPropertyPath] = $alias;
855  }
856 
857  return $alias;
858  }
859 
873  protected function addUnionStatement(&$className, &$tableName, &$propertyPath, array &$sql, &$fullPropertyPath)
874  {
875  $explodedPropertyPath = explode('.', $propertyPath, 2);
876  $propertyName = $explodedPropertyPath[0];
877  $columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
878  $realTableName = $this->dataMapper->convertClassNameToTableName($className);
879  $tableName = isset($this->tablePropertyMap[$fullPropertyPath]) ? $this->tablePropertyMap[$fullPropertyPath] : $realTableName;
880  $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
881 
882  if ($columnMap === null) {
883  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\MissingColumnMapException('The ColumnMap for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1355142232);
884  }
885 
886  $parentKeyFieldName = $columnMap->getParentKeyFieldName();
887  $childTableName = $columnMap->getChildTableName();
888 
889  if ($childTableName === null) {
890  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException('The relation information for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1353170925);
891  }
892 
893  $fullPropertyPath .= ($fullPropertyPath === '') ? $propertyName : '.' . $propertyName;
894  $childTableAlias = $this->getUniqueAlias($sql, $childTableName, $fullPropertyPath);
895 
896  // If there is already exists a union with the current identifier we do not need to build it again and exit early.
897  if (isset($sql['unions'][$childTableAlias])) {
898  $propertyPath = $explodedPropertyPath[1];
899  $tableName = $childTableAlias;
900  $className = $this->dataMapper->getType($className, $propertyName);
901  return;
902  }
903 
904  if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_ONE) {
905  if (isset($parentKeyFieldName)) {
906  $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $tableName . '.uid=' . $childTableAlias . '.' . $parentKeyFieldName;
907  } else {
908  $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $tableName . '.' . $columnName . '=' . $childTableAlias . '.uid';
909  }
910  $sql['unions'][$childTableAlias] .= $this->getAdditionalMatchFieldsStatement($columnMap, $childTableName, $childTableAlias, $realTableName);
911  } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) {
912  if (isset($parentKeyFieldName)) {
913  $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $tableName . '.uid=' . $childTableAlias . '.' . $parentKeyFieldName;
914  } else {
915  $onStatement = '(FIND_IN_SET(' . $childTableAlias . '.uid, ' . $tableName . '.' . $columnName . '))';
916  $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $onStatement;
917  }
918  $sql['unions'][$childTableAlias] .= $this->getAdditionalMatchFieldsStatement($columnMap, $childTableName, $childTableAlias, $realTableName);
919  } elseif ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
920  $relationTableName = $columnMap->getRelationTableName();
921  $relationTableAlias = $relationTableAlias = $this->getUniqueAlias($sql, $relationTableName, $fullPropertyPath . '_mm');
922  $sql['unions'][$relationTableAlias] = 'LEFT JOIN ' . $relationTableName . ' AS ' . $relationTableAlias . ' ON ' . $tableName . '.uid=' . $relationTableAlias . '.' . $columnMap->getParentKeyFieldName();
923  $sql['unions'][$childTableAlias] = 'LEFT JOIN ' . $childTableName . ' AS ' . $childTableAlias . ' ON ' . $relationTableAlias . '.' . $columnMap->getChildKeyFieldName() . '=' . $childTableAlias . '.uid';
924  $sql['unions'][$childTableAlias] .= $this->getAdditionalMatchFieldsStatement($columnMap, $relationTableName, $relationTableAlias, $realTableName);
925  } else {
926  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Could not determine type of relation.', 1252502725);
927  }
928  // @todo check if there is another solution for this
929  $sql['keywords']['distinct'] = 'DISTINCT';
930  $propertyPath = $explodedPropertyPath[1];
931  $tableName = $childTableAlias;
932  $className = $this->dataMapper->getType($className, $propertyName);
933  }
934 
944  protected function replaceTableNameWithAlias($statement, $tableName, $tableAlias)
945  {
946  if ($tableAlias !== $tableName) {
947  $statement = str_replace($tableName . '.', $tableAlias . '.', $statement);
948  }
949 
950  return $statement;
951  }
952 
960  protected function resolveOperator($operator)
961  {
962  switch ($operator) {
964  $operator = 'IN';
965  break;
967  $operator = '=';
968  break;
970  $operator = 'IS';
971  break;
973  $operator = '!=';
974  break;
976  $operator = 'IS NOT';
977  break;
979  $operator = '<';
980  break;
982  $operator = '<=';
983  break;
985  $operator = '>';
986  break;
988  $operator = '>=';
989  break;
991  $operator = 'LIKE';
992  break;
993  default:
994  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Unsupported operator encountered.', 1242816073);
995  }
996  return $operator;
997  }
998 
1002  protected function getPageRepository()
1003  {
1004  if (!$this->pageRepository instanceof \TYPO3\CMS\Frontend\Page\PageRepository) {
1005  if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
1006  $this->pageRepository = $GLOBALS['TSFE']->sys_page;
1007  } else {
1008  $this->pageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
1009  }
1010  }
1011 
1012  return $this->pageRepository;
1013  }
1014 }