Completed
Push — master ( 7b8268...c74b79 )
by Fabien
95:17 queued 40:16
created
Classes/Persistence/Storage/VidiDbBackend.php 2 patches
Spacing   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
             if (isset($statementParts['keywords']['distinct'])) {
128 128
                 unset($statementParts['keywords']['distinct']);
129 129
                 $distinctField = $this->query->getDistinct() ? $this->query->getDistinct() : 'uid';
130
-                $statementParts['fields'] = array('COUNT(DISTINCT ' . $statementParts['mainTable'] . '.' . $distinctField . ')');
130
+                $statementParts['fields'] = array('COUNT(DISTINCT '.$statementParts['mainTable'].'.'.$distinctField.')');
131 131
             }
132 132
 
133 133
             $sql = $this->buildQuery($statementParts);
@@ -190,14 +190,14 @@  discard block
 block discarded – undo
190 190
             // tx_domain_model_foo0 (the alias) <--> tx_domain_model_foo (the origin table name)
191 191
             $values = [];
192 192
             foreach ($statementParts['fields'] as $key => $value) {
193
-                $values[$key] = str_replace($tableName, $tableName . '0', $value);
193
+                $values[$key] = str_replace($tableName, $tableName.'0', $value);
194 194
             }
195 195
             $statementParts['fields'] = $values;
196 196
 
197 197
             // Same comment as above.
198 198
             $values = [];
199 199
             foreach ($statementParts['where'] as $key => $value) {
200
-                $values[$key] = str_replace($tableName . '0', $tableName, $value);
200
+                $values[$key] = str_replace($tableName.'0', $tableName, $value);
201 201
             }
202 202
             $statementParts['where'] = $values;
203 203
 
@@ -236,25 +236,25 @@  discard block
 block discarded – undo
236 236
         if (!empty($statementParts['unions'])) {
237 237
             foreach ($statementParts['unions'] as $tableName => $unionPart) {
238 238
                 if (!empty($statementParts['additionalWhereClause'][$tableName])) {
239
-                    $statementParts['unions'][$tableName] .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
239
+                    $statementParts['unions'][$tableName] .= ' AND '.implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
240 240
                 }
241 241
             }
242 242
         }
243 243
 
244
-        $statement = 'SELECT ' . implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']) . ' FROM ' . implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']);
244
+        $statement = 'SELECT '.implode(' ', $statementParts['keywords']).' '.implode(',', $statementParts['fields']).' FROM '.implode(' ', $statementParts['tables']).' '.implode(' ', $statementParts['unions']);
245 245
         if (!empty($statementParts['where'])) {
246
-            $statement .= ' WHERE ' . implode('', $statementParts['where']);
246
+            $statement .= ' WHERE '.implode('', $statementParts['where']);
247 247
             if (!empty($statementParts['additionalWhereClause'][$this->query->getType()])) {
248
-                $statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
248
+                $statement .= ' AND '.implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
249 249
             }
250 250
         } elseif (!empty($statementParts['additionalWhereClause'])) {
251
-            $statement .= ' WHERE ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
251
+            $statement .= ' WHERE '.implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
252 252
         }
253 253
         if (!empty($statementParts['orderings'])) {
254
-            $statement .= ' ORDER BY ' . implode(', ', $statementParts['orderings']);
254
+            $statement .= ' ORDER BY '.implode(', ', $statementParts['orderings']);
255 255
         }
256 256
         if (!empty($statementParts['limit'])) {
257
-            $statement .= ' LIMIT ' . $statementParts['limit'];
257
+            $statement .= ' LIMIT '.$statementParts['limit'];
258 258
         }
259 259
 
260 260
         return $statement;
@@ -270,9 +270,9 @@  discard block
 block discarded – undo
270 270
     protected function parseSource(SourceInterface $source, array &$sql)
271 271
     {
272 272
         $tableName = $this->getTableName();
273
-        $sql['fields'][$tableName] = $tableName . '.*';
273
+        $sql['fields'][$tableName] = $tableName.'.*';
274 274
         if ($this->query->getDistinct()) {
275
-            $sql['fields'][$tableName] = $tableName . '.' . $this->query->getDistinct();
275
+            $sql['fields'][$tableName] = $tableName.'.'.$this->query->getDistinct();
276 276
             $sql['keywords']['distinct'] = 'DISTINCT';
277 277
         }
278 278
         $sql['tables'][$tableName] = $tableName;
@@ -410,7 +410,7 @@  discard block
 block discarded – undo
410 410
             if ($realInput instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
411 411
                 return $realInput->getUid();
412 412
             } else {
413
-                throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An object of class "' . get_class($realInput) . '" could not be converted to a plain value.', 1274799934);
413
+                throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An object of class "'.get_class($realInput).'" could not be converted to a plain value.', 1274799934);
414 414
             }
415 415
         } elseif (is_bool($input)) {
416 416
             return $input === true ? 1 : 0;
@@ -460,9 +460,9 @@  discard block
 block discarded – undo
460 460
                 : '?';
461 461
 
462 462
             if ($valueFunction === null) {
463
-                $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $resolvedOperator . ' ' . $marker;
463
+                $constraintSQL .= (!empty($tableName) ? $tableName.'.' : '').$columnName.' '.$resolvedOperator.' '.$marker;
464 464
             } else {
465
-                $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $resolvedOperator . ' ' . $marker;
465
+                $constraintSQL .= $valueFunction.'('.(!empty($tableName) ? $tableName.'.' : '').$columnName.') '.$resolvedOperator.' '.$marker;
466 466
             }
467 467
 
468 468
             if (isset($tableName) && !empty($this->currentChildTableNameAlias)) {
@@ -489,7 +489,7 @@  discard block
 block discarded – undo
489 489
         // to determine the relation type. Example for sys_category, property path will look like "items.sys_file"
490 490
         $parts = explode('.', $propertyPath, 3);
491 491
         if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
492
-            $explodedPropertyPath[0] = $parts[0] . '.' . $parts[1];
492
+            $explodedPropertyPath[0] = $parts[0].'.'.$parts[1];
493 493
             $explodedPropertyPath[1] = $parts[2];
494 494
             $fieldName = $explodedPropertyPath[0];
495 495
         }
@@ -498,16 +498,16 @@  discard block
 block discarded – undo
498 498
         $childTableName = $table->field($fieldName)->getForeignTable();
499 499
 
500 500
         if ($childTableName === null) {
501
-            throw new Exception\InvalidRelationConfigurationException('The relation information for property "' . $fieldName . '" of class "' . $tableName . '" is missing.', 1353170925);
501
+            throw new Exception\InvalidRelationConfigurationException('The relation information for property "'.$fieldName.'" of class "'.$tableName.'" is missing.', 1353170925);
502 502
         }
503 503
 
504 504
         if ($table->field($fieldName)->hasOne()) { // includes relation "one-to-one" and "many-to-one"
505 505
             // sometimes the opposite relation is not defined. We don't want to force this config for backward compatibility reasons.
506 506
             // $parentKeyFieldName === null does the trick somehow. Before condition was if (isset($parentKeyFieldName))
507 507
             if ($table->field($fieldName)->hasRelationManyToOne() || $parentKeyFieldName === null) {
508
-                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $fieldName . '=' . $childTableName . '.uid';
508
+                $statementParts['unions'][$childTableName] = 'LEFT JOIN '.$childTableName.' ON '.$tableName.'.'.$fieldName.'='.$childTableName.'.uid';
509 509
             } else {
510
-                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
510
+                $statementParts['unions'][$childTableName] = 'LEFT JOIN '.$childTableName.' ON '.$tableName.'.uid='.$childTableName.'.'.$parentKeyFieldName;
511 511
             }
512 512
         } elseif ($table->field($fieldName)->hasRelationManyToMany()) {
513 513
             $relationTableName = $table->field($fieldName)->getManyToManyTable();
@@ -737,18 +737,18 @@  discard block
 block discarded – undo
737 737
         // If the table is found to have "workspace" support, add the corresponding fields in the statement.
738 738
         if (Tca::table($tableName)->hasWorkspaceSupport()) {
739 739
             if ($this->getBackendUser()->workspace === 0) {
740
-                $statement .= ' AND ' . $tableName . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
740
+                $statement .= ' AND '.$tableName.'.t3ver_state<='.new VersionState(VersionState::DEFAULT_STATE);
741 741
             } else {
742 742
                 // Show only records of live and of the current workspace
743 743
                 // In case we are in a Versioning preview
744
-                $statement .= ' AND (' .
745
-                    $tableName . '.t3ver_wsid=0 OR ' .
746
-                    $tableName . '.t3ver_wsid=' . (int)$this->getBackendUser()->workspace .
744
+                $statement .= ' AND ('.
745
+                    $tableName.'.t3ver_wsid=0 OR '.
746
+                    $tableName.'.t3ver_wsid='.(int)$this->getBackendUser()->workspace.
747 747
                     ')';
748 748
             }
749 749
 
750 750
             // Check if this segment make sense here or whether it should be in the "if" part when we have workspace = 0
751
-            $statement .= ' AND ' . $tableName . '.pid<>-1';
751
+            $statement .= ' AND '.$tableName.'.pid<>-1';
752 752
         }
753 753
 
754 754
         if (!$includeDeleted) {
@@ -773,25 +773,25 @@  discard block
 block discarded – undo
773 773
         if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
774 774
             if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
775 775
                 // Select all entries for the current language
776
-                $additionalWhereClause = $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getLanguageUid()) . ',-1)';
776
+                $additionalWhereClause = $tableNameOrAlias.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['languageField'].' IN ('.intval($querySettings->getLanguageUid()).',-1)';
777 777
                 // If any language is set -> get those entries which are not translated yet
778 778
                 // They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
779 779
                 if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
780 780
                     && $querySettings->getLanguageUid() > 0
781 781
                 ) {
782
-                    $additionalWhereClause .= ' OR (' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
783
-                        ' AND ' . $tableNameOrAlias . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
784
-                        ' FROM ' . $tableName .
785
-                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
786
-                        ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
782
+                    $additionalWhereClause .= ' OR ('.$tableNameOrAlias.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['languageField'].'=0'.
783
+                        ' AND '.$tableNameOrAlias.'.uid NOT IN (SELECT '.$tableName.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'].
784
+                        ' FROM '.$tableName.
785
+                        ' WHERE '.$tableName.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'].'>0'.
786
+                        ' AND '.$tableName.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['languageField'].'>0';
787 787
 
788 788
                     // Add delete clause to ensure all entries are loaded
789 789
                     if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
790
-                        $additionalWhereClause .= ' AND ' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
790
+                        $additionalWhereClause .= ' AND '.$tableNameOrAlias.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['delete'].'=0';
791 791
                     }
792 792
                     $additionalWhereClause .= '))';
793 793
                 }
794
-                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = '(' . $additionalWhereClause . ')';
794
+                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = '('.$additionalWhereClause.')';
795 795
             }
796 796
         }
797 797
     }
@@ -836,7 +836,7 @@  discard block
 block discarded – undo
836 836
     protected function parseLimitAndOffset($limit, $offset, array &$statementParts)
837 837
     {
838 838
         if ($limit !== null && $offset !== null) {
839
-            $statementParts['limit'] = intval($offset) . ', ' . intval($limit);
839
+            $statementParts['limit'] = intval($offset).', '.intval($limit);
840 840
         } elseif ($limit !== null) {
841 841
             $statementParts['limit'] = intval($limit);
842 842
         }
@@ -904,11 +904,11 @@  discard block
 block discarded – undo
904 904
             ) {
905 905
                 $queryBuilder = $this->getQueryBuilder();
906 906
                 $row = $queryBuilder
907
-                    ->select($tableName . '.*')
907
+                    ->select($tableName.'.*')
908 908
                     ->from($tableName)
909 909
                     ->andWhere(
910
-                        $tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
911
-                        $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = 0'
910
+                        $tableName.'.uid='.(int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
911
+                        $tableName.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['languageField'].' = 0'
912 912
                     )
913 913
                     ->execute()
914 914
                     ->fetch();
@@ -969,7 +969,7 @@  discard block
 block discarded – undo
969 969
         }
970 970
 
971 971
         $numberOfAliases = $this->tableNameAliases['aliasIncrement'][$tableName];
972
-        $tableNameAlias = $tableName . $numberOfAliases;
972
+        $tableNameAlias = $tableName.$numberOfAliases;
973 973
 
974 974
         $this->tableNameAliases['aliasIncrement'][$tableName]++;
975 975
         $this->tableNameAliases['aliases'][$tableNameAlias] = $tableName;
Please login to merge, or discard this patch.
Indentation   +1053 added lines, -1053 removed lines patch added patch discarded remove patch
@@ -39,1058 +39,1058 @@
 block discarded – undo
39 39
 class VidiDbBackend
40 40
 {
41 41
 
42
-    const OPERATOR_EQUAL_TO_NULL = 'operatorEqualToNull';
43
-    const OPERATOR_NOT_EQUAL_TO_NULL = 'operatorNotEqualToNull';
44
-
45
-    /**
46
-     * The TYPO3 page repository. Used for language and workspace overlay
47
-     *
48
-     * @var PageRepository
49
-     */
50
-    protected $pageRepository;
51
-
52
-    /**
53
-     * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
54
-     * @inject
55
-     */
56
-    protected $environmentService;
57
-
58
-    /**
59
-     * @var \Fab\Vidi\Persistence\Query
60
-     */
61
-    protected $query;
62
-
63
-    /**
64
-     * Store some info related to table name and its aliases.
65
-     *
66
-     * @var array
67
-     */
68
-    protected $tableNameAliases = array(
69
-        'aliases' => [],
70
-        'aliasIncrement' => [],
71
-    );
72
-
73
-    /**
74
-     * Use to store the current foreign table name alias.
75
-     *
76
-     * @var string
77
-     */
78
-    protected $currentChildTableNameAlias = '';
79
-
80
-    /**
81
-     * @param Query $query
82
-     */
83
-    public function __construct(Query $query)
84
-    {
85
-        $this->query = $query;
86
-    }
87
-
88
-    /**
89
-     * @param $parameters
90
-     * @return array
91
-     */
92
-    protected static function getTypes($parameters)
93
-    {
94
-        $types = [];
95
-        foreach ($parameters as $parameter) {
96
-            if (is_array($parameter)) {
97
-                if (MathUtility::canBeInterpretedAsInteger($parameter[0])) {
98
-                    $types[] = \Doctrine\DBAL\Connection::PARAM_INT_ARRAY;
99
-                } else {
100
-                    $types[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
101
-                }
102
-            } else {
103
-                if (MathUtility::canBeInterpretedAsInteger($parameter)) {
104
-                    $types[] = ParameterType::INTEGER;
105
-                } else {
106
-                    $types[] = ParameterType::STRING;
107
-                }
108
-            }
109
-        }
110
-        return $types;
111
-    }
112
-
113
-    /**
114
-     * Returns the result of the query
115
-     */
116
-    public function fetchResult()
117
-    {
118
-        $parameters = [];
119
-        $statementParts = $this->parseQuery($parameters);
120
-        $statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
121
-        $sql = $this->buildQuery($statementParts);
122
-        //print $sql; exit();
123
-
124
-        $rows = $this->getConnection()
125
-            ->executeQuery($sql, $parameters, self::getTypes($parameters))
126
-            ->fetchAll();
127
-
128
-        return $this->getContentObjects($rows);
129
-    }
130
-
131
-    /**
132
-     * Returns the number of tuples matching the query.
133
-     *
134
-     * @return int The number of matching tuples
135
-     */
136
-    public function countResult()
137
-    {
138
-        $parameters = [];
139
-        $statementParts = $this->parseQuery($parameters);
140
-        $statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
141
-        $types = self::getTypes($parameters);
142
-
143
-        // if limit is set, we need to count the rows "manually" as COUNT(*) ignores LIMIT constraints
144
-        if (!empty($statementParts['limit'])) {
145
-            $sql = $this->buildQuery($statementParts);
146
-
147
-            $count = $this
148
-                ->getConnection()
149
-                ->executeQuery($sql, $parameters, $types)
150
-                ->rowCount();
151
-        } else {
152
-            $statementParts['fields'] = array('COUNT(*)');
153
-            // having orderings without grouping is not compatible with non-MySQL DBMS
154
-            $statementParts['orderings'] = [];
155
-            if (isset($statementParts['keywords']['distinct'])) {
156
-                unset($statementParts['keywords']['distinct']);
157
-                $distinctField = $this->query->getDistinct() ? $this->query->getDistinct() : 'uid';
158
-                $statementParts['fields'] = array('COUNT(DISTINCT ' . $statementParts['mainTable'] . '.' . $distinctField . ')');
159
-            }
160
-
161
-            $sql = $this->buildQuery($statementParts);
162
-            $count = $this
163
-                ->getConnection()
164
-                ->executeQuery($sql, $parameters, $types)
165
-                ->fetchColumn(0);
166
-        }
167
-        return (int)$count;
168
-    }
169
-
170
-    /**
171
-     * Parses the query and returns the SQL statement parts.
172
-     *
173
-     * @param array &$parameters
174
-     * @return array
175
-     */
176
-    public function parseQuery(array &$parameters)
177
-    {
178
-        $statementParts = [];
179
-        $statementParts['keywords'] = [];
180
-        $statementParts['tables'] = [];
181
-        $statementParts['unions'] = [];
182
-        $statementParts['fields'] = [];
183
-        $statementParts['where'] = [];
184
-        $statementParts['additionalWhereClause'] = [];
185
-        $statementParts['orderings'] = [];
186
-        $statementParts['limit'] = [];
187
-        $query = $this->query;
188
-        $source = $query->getSource();
189
-        $this->parseSource($source, $statementParts);
190
-        $this->parseConstraint($query->getConstraint(), $source, $statementParts, $parameters);
191
-        $this->parseOrderings($query->getOrderings(), $source, $statementParts);
192
-        $this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $statementParts);
193
-        $tableNames = array_unique(array_keys($statementParts['tables'] + $statementParts['unions']));
194
-        foreach ($tableNames as $tableNameOrAlias) {
195
-            if (is_string($tableNameOrAlias) && strlen($tableNameOrAlias) > 0) {
196
-                $this->addAdditionalWhereClause($query->getQuerySettings(), $tableNameOrAlias, $statementParts);
197
-            }
198
-        }
199
-
200
-        return $statementParts;
201
-    }
202
-
203
-    /**
204
-     * Fiddle with the statement structure to handle recursive MM relations.
205
-     * For the recursive MM query to work, we must invert some values.
206
-     * Let see if that is the best way of doing that...
207
-     *
208
-     * @param array $statementParts
209
-     * @return array
210
-     */
211
-    public function processStatementStructureForRecursiveMMRelation(array $statementParts)
212
-    {
213
-
214
-        if ($this->hasRecursiveMMRelation()) {
215
-            $tableName = $this->query->getType();
216
-
217
-            // In order the MM query to work for a recursive MM query, we must invert some values.
218
-            // tx_domain_model_foo0 (the alias) <--> tx_domain_model_foo (the origin table name)
219
-            $values = [];
220
-            foreach ($statementParts['fields'] as $key => $value) {
221
-                $values[$key] = str_replace($tableName, $tableName . '0', $value);
222
-            }
223
-            $statementParts['fields'] = $values;
224
-
225
-            // Same comment as above.
226
-            $values = [];
227
-            foreach ($statementParts['where'] as $key => $value) {
228
-                $values[$key] = str_replace($tableName . '0', $tableName, $value);
229
-            }
230
-            $statementParts['where'] = $values;
231
-
232
-            // We must be more restrictive by transforming the "left" union by "inner"
233
-            $values = [];
234
-            foreach ($statementParts['unions'] as $key => $value) {
235
-                $values[$key] = str_replace('LEFT JOIN', 'INNER JOIN', $value);
236
-            }
237
-            $statementParts['unions'] = $values;
238
-        }
239
-
240
-        return $statementParts;
241
-    }
242
-
243
-    /**
244
-     * Tell whether there is a recursive MM relation.
245
-     *
246
-     * @return bool
247
-     */
248
-    public function hasRecursiveMMRelation()
249
-    {
250
-        return isset($this->tableNameAliases['aliasIncrement'][$this->query->getType()]);
251
-
252
-    }
253
-
254
-    /**
255
-     * Returns the statement, ready to be executed.
256
-     *
257
-     * @param array $statementParts The SQL statement parts
258
-     * @return string The SQL statement
259
-     */
260
-    public function buildQuery(array $statementParts)
261
-    {
262
-
263
-        // Add more statement to the UNION part.
264
-        if (!empty($statementParts['unions'])) {
265
-            foreach ($statementParts['unions'] as $tableName => $unionPart) {
266
-                if (!empty($statementParts['additionalWhereClause'][$tableName])) {
267
-                    $statementParts['unions'][$tableName] .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
268
-                }
269
-            }
270
-        }
271
-
272
-        $statement = 'SELECT ' . implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']) . ' FROM ' . implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']);
273
-        if (!empty($statementParts['where'])) {
274
-            $statement .= ' WHERE ' . implode('', $statementParts['where']);
275
-            if (!empty($statementParts['additionalWhereClause'][$this->query->getType()])) {
276
-                $statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
277
-            }
278
-        } elseif (!empty($statementParts['additionalWhereClause'])) {
279
-            $statement .= ' WHERE ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
280
-        }
281
-        if (!empty($statementParts['orderings'])) {
282
-            $statement .= ' ORDER BY ' . implode(', ', $statementParts['orderings']);
283
-        }
284
-        if (!empty($statementParts['limit'])) {
285
-            $statement .= ' LIMIT ' . $statementParts['limit'];
286
-        }
287
-
288
-        return $statement;
289
-    }
290
-
291
-    /**
292
-     * Transforms a Query Source into SQL and parameter arrays
293
-     *
294
-     * @param SourceInterface $source The source
295
-     * @param array &$sql
296
-     * @return void
297
-     */
298
-    protected function parseSource(SourceInterface $source, array &$sql)
299
-    {
300
-        $tableName = $this->getTableName();
301
-        $sql['fields'][$tableName] = $tableName . '.*';
302
-        if ($this->query->getDistinct()) {
303
-            $sql['fields'][$tableName] = $tableName . '.' . $this->query->getDistinct();
304
-            $sql['keywords']['distinct'] = 'DISTINCT';
305
-        }
306
-        $sql['tables'][$tableName] = $tableName;
307
-        $sql['mainTable'] = $tableName;
308
-    }
309
-
310
-    /**
311
-     * Transforms a constraint into SQL and parameter arrays
312
-     *
313
-     * @param ConstraintInterface $constraint The constraint
314
-     * @param SourceInterface $source The source
315
-     * @param array &$statementParts The query parts
316
-     * @param array &$parameters The parameters that will replace the markers
317
-     * @return void
318
-     */
319
-    protected function parseConstraint(ConstraintInterface $constraint = null, SourceInterface $source, array &$statementParts, array &$parameters)
320
-    {
321
-        if ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface) {
322
-            $statementParts['where'][] = '(';
323
-            $this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
324
-            $statementParts['where'][] = ' AND ';
325
-            $this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
326
-            $statementParts['where'][] = ')';
327
-        } elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface) {
328
-            $statementParts['where'][] = '(';
329
-            $this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
330
-            $statementParts['where'][] = ' OR ';
331
-            $this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
332
-            $statementParts['where'][] = ')';
333
-        } elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface) {
334
-            $statementParts['where'][] = 'NOT (';
335
-            $this->parseConstraint($constraint->getConstraint(), $source, $statementParts, $parameters);
336
-            $statementParts['where'][] = ')';
337
-        } elseif ($constraint instanceof ComparisonInterface) {
338
-            $this->parseComparison($constraint, $source, $statementParts, $parameters);
339
-        }
340
-    }
341
-
342
-    /**
343
-     * Parse a Comparison into SQL and parameter arrays.
344
-     *
345
-     * @param ComparisonInterface $comparison The comparison to parse
346
-     * @param SourceInterface $source The source
347
-     * @param array &$statementParts SQL query parts to add to
348
-     * @param array &$parameters Parameters to bind to the SQL
349
-     * @return void
350
-     * @throws Exception\RepositoryException
351
-     */
352
-    protected function parseComparison(ComparisonInterface $comparison, SourceInterface $source, array &$statementParts, array &$parameters)
353
-    {
354
-        $operand1 = $comparison->getOperand1();
355
-        $operator = $comparison->getOperator();
356
-        $operand2 = $comparison->getOperand2();
357
-        if ($operator === QueryInterface::OPERATOR_IN) {
358
-            $items = [];
359
-            $hasValue = false;
360
-            foreach ($operand2 as $value) {
361
-                $value = $this->getPlainValue($value);
362
-                if ($value !== null) {
363
-                    $items[] = $value;
364
-                    $hasValue = true;
365
-                }
366
-            }
367
-            if ($hasValue === false) {
368
-                $statementParts['where'][] = '1<>1';
369
-            } else {
370
-                $this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters, null);
371
-                $parameters[] = $items;
372
-            }
373
-        } elseif ($operator === QueryInterface::OPERATOR_CONTAINS) {
374
-            if ($operand2 === null) {
375
-                $statementParts['where'][] = '1<>1';
376
-            } else {
377
-                throw new \Exception('Not implemented! Contact extension author.', 1412931227);
378
-                # @todo re-implement me if necessary.
379
-                #$tableName = $this->query->getType();
380
-                #$propertyName = $operand1->getPropertyName();
381
-                #while (strpos($propertyName, '.') !== false) {
382
-                #	$this->addUnionStatement($tableName, $propertyName, $statementParts);
383
-                #}
384
-                #$columnName = $propertyName;
385
-                #$columnMap = $propertyName;
386
-                #$typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
387
-                #if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
388
-                #	$relationTableName = $columnMap->getRelationTableName();
389
-                #	$statementParts['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=?)';
390
-                #	$parameters[] = intval($this->getPlainValue($operand2));
391
-                #} elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
392
-                #	$parentKeyFieldName = $columnMap->getParentKeyFieldName();
393
-                #	if (isset($parentKeyFieldName)) {
394
-                #		$childTableName = $columnMap->getChildTableName();
395
-                #		$statementParts['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=?)';
396
-                #		$parameters[] = intval($this->getPlainValue($operand2));
397
-                #	} else {
398
-                #		$statementParts['where'][] = 'FIND_IN_SET(?,' . $tableName . '.' . $columnName . ')';
399
-                #		$parameters[] = intval($this->getPlainValue($operand2));
400
-                #	}
401
-                #} else {
402
-                #	throw new Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
403
-                #}
404
-            }
405
-        } else {
406
-            if ($operand2 === null) {
407
-                if ($operator === QueryInterface::OPERATOR_EQUAL_TO) {
408
-                    $operator = self::OPERATOR_EQUAL_TO_NULL;
409
-                } elseif ($operator === QueryInterface::OPERATOR_NOT_EQUAL_TO) {
410
-                    $operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
411
-                }
412
-            }
413
-            $this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters);
414
-            $parameters[] = $this->getPlainValue($operand2);
415
-        }
416
-    }
417
-
418
-    /**
419
-     * Returns a plain value, i.e. objects are flattened if possible.
420
-     *
421
-     * @param mixed $input
422
-     * @return mixed
423
-     * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException
424
-     */
425
-    protected function getPlainValue($input)
426
-    {
427
-        if (is_array($input)) {
428
-            throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An array could not be converted to a plain value.', 1274799932);
429
-        }
430
-        if ($input instanceof \DateTime) {
431
-            return $input->format('U');
432
-        } elseif (is_object($input)) {
433
-            if ($input instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
434
-                $realInput = $input->_loadRealInstance();
435
-            } else {
436
-                $realInput = $input;
437
-            }
438
-            if ($realInput instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
439
-                return $realInput->getUid();
440
-            } else {
441
-                throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An object of class "' . get_class($realInput) . '" could not be converted to a plain value.', 1274799934);
442
-            }
443
-        } elseif (is_bool($input)) {
444
-            return $input === true ? 1 : 0;
445
-        } else {
446
-            return $input;
447
-        }
448
-    }
449
-
450
-    /**
451
-     * Parse a DynamicOperand into SQL and parameter arrays.
452
-     *
453
-     * @param DynamicOperandInterface $operand
454
-     * @param string $operator One of the JCR_OPERATOR_* constants
455
-     * @param SourceInterface $source The source
456
-     * @param array &$statementParts The query parts
457
-     * @param array &$parameters The parameters that will replace the markers
458
-     * @param string $valueFunction an optional SQL function to apply to the operand value
459
-     * @return void
460
-     */
461
-    protected function parseDynamicOperand(DynamicOperandInterface $operand, $operator, SourceInterface $source, array &$statementParts, array &$parameters, $valueFunction = null)
462
-    {
463
-        if ($operand instanceof LowerCaseInterface) {
464
-            $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'LOWER');
465
-        } elseif ($operand instanceof UpperCaseInterface) {
466
-            $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'UPPER');
467
-        } elseif ($operand instanceof PropertyValueInterface) {
468
-            $propertyName = $operand->getPropertyName();
469
-
470
-            // Reset value.
471
-            $this->currentChildTableNameAlias = '';
472
-
473
-            if ($source instanceof SelectorInterface) {
474
-                $tableName = $this->query->getType();
475
-                while (strpos($propertyName, '.') !== false) {
476
-                    $this->addUnionStatement($tableName, $propertyName, $statementParts);
477
-                }
478
-            } elseif ($source instanceof JoinInterface) {
479
-                $tableName = $source->getJoinCondition()->getSelector1Name();
480
-            }
481
-
482
-            $columnName = $propertyName;
483
-            $resolvedOperator = $this->resolveOperator($operator);
484
-            $constraintSQL = '';
485
-
486
-            $marker = $operator === QueryInterface::OPERATOR_IN
487
-                ? '(?)'
488
-                : '?';
489
-
490
-            if ($valueFunction === null) {
491
-                $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $resolvedOperator . ' ' . $marker;
492
-            } else {
493
-                $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $resolvedOperator . ' ' . $marker;
494
-            }
495
-
496
-            if (isset($tableName) && !empty($this->currentChildTableNameAlias)) {
497
-                $constraintSQL = $this->replaceTableNameByAlias($tableName, $this->currentChildTableNameAlias, $constraintSQL);
498
-            }
499
-            $statementParts['where'][] = $constraintSQL;
500
-        }
501
-    }
502
-
503
-    /**
504
-     * @param string &$tableName
505
-     * @param string &$propertyPath
506
-     * @param array &$statementParts
507
-     */
508
-    protected function addUnionStatement(&$tableName, &$propertyPath, array &$statementParts)
509
-    {
510
-
511
-        $table = Tca::table($tableName);
512
-
513
-        $explodedPropertyPath = explode('.', $propertyPath, 2);
514
-        $fieldName = $explodedPropertyPath[0];
515
-
516
-        // Field of type "group" are special because property path must contain the table name
517
-        // to determine the relation type. Example for sys_category, property path will look like "items.sys_file"
518
-        $parts = explode('.', $propertyPath, 3);
519
-        if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
520
-            $explodedPropertyPath[0] = $parts[0] . '.' . $parts[1];
521
-            $explodedPropertyPath[1] = $parts[2];
522
-            $fieldName = $explodedPropertyPath[0];
523
-        }
524
-
525
-        $parentKeyFieldName = $table->field($fieldName)->getForeignField();
526
-        $childTableName = $table->field($fieldName)->getForeignTable();
527
-
528
-        if ($childTableName === null) {
529
-            throw new Exception\InvalidRelationConfigurationException('The relation information for property "' . $fieldName . '" of class "' . $tableName . '" is missing.', 1353170925);
530
-        }
531
-
532
-        if ($table->field($fieldName)->hasOne()) { // includes relation "one-to-one" and "many-to-one"
533
-            // sometimes the opposite relation is not defined. We don't want to force this config for backward compatibility reasons.
534
-            // $parentKeyFieldName === null does the trick somehow. Before condition was if (isset($parentKeyFieldName))
535
-            if ($table->field($fieldName)->hasRelationManyToOne() || $parentKeyFieldName === null) {
536
-                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $fieldName . '=' . $childTableName . '.uid';
537
-            } else {
538
-                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
539
-            }
540
-        } elseif ($table->field($fieldName)->hasRelationManyToMany()) {
541
-            $relationTableName = $table->field($fieldName)->getManyToManyTable();
542
-
543
-            $parentKeyFieldName = $table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
544
-            $childKeyFieldName = !$table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
545
-
546
-            // MM table e.g sys_category_record_mm
547
-            $relationTableNameAlias = $this->generateAlias($relationTableName);
548
-            $join = sprintf(
549
-                'LEFT JOIN %s AS %s ON %s.uid=%s.%s', $relationTableName,
550
-                $relationTableNameAlias,
551
-                $tableName,
552
-                $relationTableNameAlias,
553
-                $parentKeyFieldName
554
-            );
555
-            $statementParts['unions'][$relationTableNameAlias] = $join;
556
-
557
-            // Foreign table e.g sys_category
558
-            $childTableNameAlias = $this->generateAlias($childTableName);
559
-            $this->currentChildTableNameAlias = $childTableNameAlias;
560
-            $join = sprintf(
561
-                'LEFT JOIN %s AS %s ON %s.%s=%s.uid',
562
-                $childTableName,
563
-                $childTableNameAlias,
564
-                $relationTableNameAlias,
565
-                $childKeyFieldName,
566
-                $childTableNameAlias
567
-            );
568
-            $statementParts['unions'][$childTableNameAlias] = $join;
569
-
570
-            // Find a possible table name for a MM condition.
571
-            $tableNameCondition = $table->field($fieldName)->getAdditionalTableNameCondition();
572
-            if ($tableNameCondition) {
573
-
574
-                // If we can find a source file name,  we can then retrieve more MM conditions from the TCA such as a field name.
575
-                $sourceFileName = $this->query->getSourceFieldName();
576
-                if (empty($sourceFileName)) {
577
-                    $additionalMMConditions = array(
578
-                        'tablenames' => $tableNameCondition,
579
-                    );
580
-                } else {
581
-                    $additionalMMConditions = Tca::table($tableNameCondition)->field($sourceFileName)->getAdditionalMMCondition();
582
-                }
583
-
584
-                foreach ($additionalMMConditions as $additionalFieldName => $additionalMMCondition) {
585
-                    $additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
586
-                    $statementParts['unions'][$relationTableNameAlias] .= $additionalJoin;
587
-
588
-                    $additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
589
-                    $statementParts['unions'][$childTableNameAlias] .= $additionalJoin;
590
-                }
591
-            }
592
-
593
-        } elseif ($table->field($fieldName)->hasMany()) { // includes relations "many-to-one" and "csv" relations
594
-            $childTableNameAlias = $this->generateAlias($childTableName);
595
-            $this->currentChildTableNameAlias = $childTableNameAlias;
596
-
597
-            if (isset($parentKeyFieldName)) {
598
-                $join = sprintf(
599
-                    'LEFT JOIN %s AS %s ON %s.uid=%s.%s',
600
-                    $childTableName,
601
-                    $childTableNameAlias,
602
-                    $tableName,
603
-                    $childTableNameAlias,
604
-                    $parentKeyFieldName
605
-                );
606
-                $statementParts['unions'][$childTableNameAlias] = $join;
607
-            } else {
608
-                $join = sprintf(
609
-                    'LEFT JOIN %s AS %s ON (FIND_IN_SET(%s.uid, %s.%s))',
610
-                    $childTableName,
611
-                    $childTableNameAlias,
612
-                    $childTableNameAlias,
613
-                    $tableName,
614
-                    $fieldName
615
-                );
616
-                $statementParts['unions'][$childTableNameAlias] = $join;
617
-            }
618
-        } else {
619
-            throw new Exception('Could not determine type of relation.', 1252502725);
620
-        }
621
-
622
-        $statementParts['keywords']['distinct'] = 'DISTINCT';
623
-        $propertyPath = $explodedPropertyPath[1];
624
-        $tableName = $childTableName;
625
-    }
626
-
627
-    /**
628
-     * Returns the SQL operator for the given JCR operator type.
629
-     *
630
-     * @param string $operator One of the JCR_OPERATOR_* constants
631
-     * @return string an SQL operator
632
-     * @throws Exception
633
-     */
634
-    protected function resolveOperator($operator)
635
-    {
636
-        switch ($operator) {
637
-            case self::OPERATOR_EQUAL_TO_NULL:
638
-                $operator = 'IS';
639
-                break;
640
-            case self::OPERATOR_NOT_EQUAL_TO_NULL:
641
-                $operator = 'IS NOT';
642
-                break;
643
-            case QueryInterface::OPERATOR_IN:
644
-                $operator = 'IN';
645
-                break;
646
-            case QueryInterface::OPERATOR_EQUAL_TO:
647
-                $operator = '=';
648
-                break;
649
-            case QueryInterface::OPERATOR_NOT_EQUAL_TO:
650
-                $operator = '!=';
651
-                break;
652
-            case QueryInterface::OPERATOR_LESS_THAN:
653
-                $operator = '<';
654
-                break;
655
-            case QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO:
656
-                $operator = '<=';
657
-                break;
658
-            case QueryInterface::OPERATOR_GREATER_THAN:
659
-                $operator = '>';
660
-                break;
661
-            case QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO:
662
-                $operator = '>=';
663
-                break;
664
-            case QueryInterface::OPERATOR_LIKE:
665
-                $operator = 'LIKE';
666
-                break;
667
-            default:
668
-                throw new Exception('Unsupported operator encountered.', 1242816073);
669
-        }
670
-        return $operator;
671
-    }
672
-
673
-    /**
674
-     * Adds additional WHERE statements according to the query settings.
675
-     *
676
-     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
677
-     * @param string $tableNameOrAlias The table name to add the additional where clause for
678
-     * @param array &$statementParts
679
-     * @return void
680
-     */
681
-    protected function addAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableNameOrAlias, &$statementParts)
682
-    {
683
-        $this->addVisibilityConstraintStatement($querySettings, $tableNameOrAlias, $statementParts);
684
-        if ($querySettings->getRespectSysLanguage()) {
685
-            $this->addSysLanguageStatement($tableNameOrAlias, $statementParts, $querySettings);
686
-        }
687
-    }
688
-
689
-    /**
690
-     * Adds enableFields and deletedClause to the query if necessary
691
-     *
692
-     * @param QuerySettingsInterface $querySettings
693
-     * @param string $tableNameOrAlias The database table name
694
-     * @param array &$statementParts The query parts
695
-     * @return void
696
-     */
697
-    protected function addVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableNameOrAlias, array &$statementParts)
698
-    {
699
-        $statement = '';
700
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
701
-        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
702
-            $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
703
-            $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
704
-            $includeDeleted = $querySettings->getIncludeDeleted();
705
-            if ($this->environmentService->isEnvironmentInFrontendMode()) {
706
-                $statement .= $this->getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
707
-            } else {
708
-                // TYPO3_MODE === 'BE'
709
-                $statement .= $this->getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted);
710
-            }
711
-
712
-            // Remove the prefixing "AND" if any.
713
-            if (!empty($statement)) {
714
-                $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
715
-                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = $statement;
716
-            }
717
-        }
718
-    }
719
-
720
-    /**
721
-     * Returns constraint statement for frontend context
722
-     *
723
-     * @param string $tableNameOrAlias
724
-     * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
725
-     * @param array $enableFieldsToBeIgnored If $ignoreEnableFields is true, this array specifies enable fields to be ignored. If it is null or an empty array (default) all enable fields are ignored.
726
-     * @param boolean $includeDeleted A flag indicating whether deleted records should be included
727
-     * @return string
728
-     * @throws Exception\InconsistentQuerySettingsException
729
-     */
730
-    protected function getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted)
731
-    {
732
-        $statement = '';
733
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
734
-        if ($ignoreEnableFields && !$includeDeleted) {
735
-            if (count($enableFieldsToBeIgnored)) {
736
-                // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
737
-                $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
738
-            } else {
739
-                $statement .= $this->getPageRepository()->deleteClause($tableName);
740
-            }
741
-        } elseif (!$ignoreEnableFields && !$includeDeleted) {
742
-            $statement .= $this->getPageRepository()->enableFields($tableName);
743
-        } elseif (!$ignoreEnableFields && $includeDeleted) {
744
-            throw new Exception\InconsistentQuerySettingsException('Query setting "ignoreEnableFields=false" can not be used together with "includeDeleted=true" in frontend context.', 1327678173);
745
-        }
746
-        return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
747
-    }
748
-
749
-    /**
750
-     * Returns constraint statement for backend context
751
-     *
752
-     * @param string $tableNameOrAlias
753
-     * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
754
-     * @param boolean $includeDeleted A flag indicating whether deleted records should be included
755
-     * @return string
756
-     */
757
-    protected function getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted)
758
-    {
759
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
760
-        $statement = '';
761
-        if (!$ignoreEnableFields) {
762
-            $statement .= BackendUtility::BEenableFields($tableName);
763
-        }
764
-
765
-        // If the table is found to have "workspace" support, add the corresponding fields in the statement.
766
-        if (Tca::table($tableName)->hasWorkspaceSupport()) {
767
-            if ($this->getBackendUser()->workspace === 0) {
768
-                $statement .= ' AND ' . $tableName . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
769
-            } else {
770
-                // Show only records of live and of the current workspace
771
-                // In case we are in a Versioning preview
772
-                $statement .= ' AND (' .
773
-                    $tableName . '.t3ver_wsid=0 OR ' .
774
-                    $tableName . '.t3ver_wsid=' . (int)$this->getBackendUser()->workspace .
775
-                    ')';
776
-            }
777
-
778
-            // Check if this segment make sense here or whether it should be in the "if" part when we have workspace = 0
779
-            $statement .= ' AND ' . $tableName . '.pid<>-1';
780
-        }
781
-
782
-        if (!$includeDeleted) {
783
-            $statement .= BackendUtility::deleteClause($tableName);
784
-        }
785
-
786
-        return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
787
-    }
788
-
789
-    /**
790
-     * Builds the language field statement
791
-     *
792
-     * @param string $tableNameOrAlias The database table name
793
-     * @param array &$statementParts The query parts
794
-     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
795
-     * @return void
796
-     * @throws Exception
797
-     */
798
-    protected function addSysLanguageStatement($tableNameOrAlias, array &$statementParts, $querySettings)
799
-    {
800
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
801
-        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
802
-            if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
803
-                // Select all entries for the current language
804
-                $additionalWhereClause = $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getLanguageUid()) . ',-1)';
805
-                // If any language is set -> get those entries which are not translated yet
806
-                // They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
807
-                if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
808
-                    && $querySettings->getLanguageUid() > 0
809
-                ) {
810
-                    $additionalWhereClause .= ' OR (' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
811
-                        ' AND ' . $tableNameOrAlias . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
812
-                        ' FROM ' . $tableName .
813
-                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
814
-                        ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
815
-
816
-                    // Add delete clause to ensure all entries are loaded
817
-                    if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
818
-                        $additionalWhereClause .= ' AND ' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
819
-                    }
820
-                    $additionalWhereClause .= '))';
821
-                }
822
-                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = '(' . $additionalWhereClause . ')';
823
-            }
824
-        }
825
-    }
826
-
827
-    /**
828
-     * Transforms orderings into SQL.
829
-     *
830
-     * @param array $orderings An array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
831
-     * @param SourceInterface $source The source
832
-     * @param array &$statementParts The query parts
833
-     * @return void
834
-     * @throws Exception\UnsupportedOrderException
835
-     */
836
-    protected function parseOrderings(array $orderings, SourceInterface $source, array &$statementParts)
837
-    {
838
-        foreach ($orderings as $fieldNameAndPath => $order) {
839
-            switch ($order) {
840
-                case QueryInterface::ORDER_ASCENDING:
841
-                    $order = 'ASC';
842
-                    break;
843
-                case QueryInterface::ORDER_DESCENDING:
844
-                    $order = 'DESC';
845
-                    break;
846
-                default:
847
-                    throw new Exception\UnsupportedOrderException('Unsupported order encountered.', 1456845126);
848
-            }
849
-
850
-            $tableName = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->query->getType());
851
-            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $tableName);
852
-            $statementParts['orderings'][] = sprintf('%s.%s %s', $tableName, $fieldName, $order);
853
-        }
854
-    }
855
-
856
-    /**
857
-     * Transforms limit and offset into SQL
858
-     *
859
-     * @param int $limit
860
-     * @param int $offset
861
-     * @param array &$statementParts
862
-     * @return void
863
-     */
864
-    protected function parseLimitAndOffset($limit, $offset, array &$statementParts)
865
-    {
866
-        if ($limit !== null && $offset !== null) {
867
-            $statementParts['limit'] = intval($offset) . ', ' . intval($limit);
868
-        } elseif ($limit !== null) {
869
-            $statementParts['limit'] = intval($limit);
870
-        }
871
-    }
872
-
873
-    /**
874
-     * @param array $rows
875
-     * @return array
876
-     */
877
-    protected function getContentObjects(array $rows): array
878
-    {
879
-        $contentObjects = [];
880
-        foreach ($rows as $row) {
881
-
882
-            // Get language uid from querySettings.
883
-            // Ensure the backend handling is not broken (fallback to Get parameter 'L' if needed)
884
-            $overlaidRow = $this->doLanguageAndWorkspaceOverlay(
885
-                $row,
886
-                $this->query->getQuerySettings()
887
-            );
888
-
889
-            $contentObjects[] = GeneralUtility::makeInstance(
890
-                \Fab\Vidi\Domain\Model\Content::class,
891
-                $this->query->getType(),
892
-                $overlaidRow
893
-            );
894
-        }
895
-
896
-        return $contentObjects;
897
-    }
898
-
899
-    /**
900
-     * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
901
-     * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
902
-     *
903
-     * @param array $row
904
-     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
905
-     * @return array
906
-     */
907
-    protected function doLanguageAndWorkspaceOverlay(array $row, $querySettings)
908
-    {
909
-        $tableName = $this->getTableName();
910
-
911
-        $pageRepository = $this->getPageRepository();
912
-        if (is_object($GLOBALS['TSFE'])) {
913
-            $languageMode = $GLOBALS['TSFE']->sys_language_mode;
914
-            if ($this->isBackendUserLogged() && $this->getBackendUser()->workspace !== 0) {
915
-                $pageRepository->versioningWorkspaceId = $this->getBackendUser()->workspace;
916
-            }
917
-        } else {
918
-            $languageMode = '';
919
-            $workspaceUid = $this->getBackendUser()->workspace;
920
-            $pageRepository->versioningWorkspaceId = $workspaceUid;
921
-            if ($this->getBackendUser()->workspace !== 0) {
922
-                $pageRepository->versioningPreview = 1;
923
-            }
924
-        }
925
-
926
-        // If current row is a translation select its parent
927
-        if (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
928
-            && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
929
-        ) {
930
-            if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
931
-                && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
932
-            ) {
933
-                $queryBuilder = $this->getQueryBuilder();
934
-                $row = $queryBuilder
935
-                    ->select($tableName . '.*')
936
-                    ->from($tableName)
937
-                    ->andWhere(
938
-                        $tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
939
-                        $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = 0'
940
-                    )
941
-                    ->execute()
942
-                    ->fetch();
943
-            }
944
-        }
945
-
946
-        // Retrieve the original uid; Used for Workspaces!
947
-        if (TYPO3_MODE !== 'BE') {
948
-            $pageRepository->versionOL($tableName, $row, true, true);
949
-        } else {
950
-            \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($tableName, $row);
951
-        }
952
-        if ($pageRepository->versioningPreview && isset($row['_ORIG_uid'])) {
953
-            $row['uid'] = $row['_ORIG_uid'];
954
-        }
955
-
956
-        // Special case for table "pages"
957
-        if ($tableName == 'pages') {
958
-            $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
959
-        } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
960
-            && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
961
-        ) {
962
-            if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], array(-1, 0))) {
963
-                $overlayMode = $languageMode === 'strict' ? 'hideNonTranslated' : '';
964
-                $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
965
-            }
966
-        }
967
-
968
-        return $row;
969
-    }
970
-
971
-    /**
972
-     * Return a resolved table name given a possible table name alias.
973
-     *
974
-     * @param string $tableNameOrAlias
975
-     * @return string
976
-     */
977
-    protected function resolveTableNameAlias($tableNameOrAlias)
978
-    {
979
-        $resolvedTableName = $tableNameOrAlias;
980
-        if (!empty($this->tableNameAliases['aliases'][$tableNameOrAlias])) {
981
-            $resolvedTableName = $this->tableNameAliases['aliases'][$tableNameOrAlias];
982
-        }
983
-        return $resolvedTableName;
984
-    }
985
-
986
-    /**
987
-     * Generate a unique table name alias for the given table name.
988
-     *
989
-     * @param string $tableName
990
-     * @return string
991
-     */
992
-    protected function generateAlias($tableName)
993
-    {
994
-
995
-        if (!isset($this->tableNameAliases['aliasIncrement'][$tableName])) {
996
-            $this->tableNameAliases['aliasIncrement'][$tableName] = 0;
997
-        }
998
-
999
-        $numberOfAliases = $this->tableNameAliases['aliasIncrement'][$tableName];
1000
-        $tableNameAlias = $tableName . $numberOfAliases;
1001
-
1002
-        $this->tableNameAliases['aliasIncrement'][$tableName]++;
1003
-        $this->tableNameAliases['aliases'][$tableNameAlias] = $tableName;
1004
-
1005
-        return $tableNameAlias;
1006
-    }
1007
-
1008
-    /**
1009
-     * Replace the table names by its table name alias within the given statement.
1010
-     *
1011
-     * @param string $tableName
1012
-     * @param string $tableNameAlias
1013
-     * @param string $statement
1014
-     * @return string
1015
-     */
1016
-    protected function replaceTableNameByAlias($tableName, $tableNameAlias, $statement)
1017
-    {
1018
-        if ($statement && $tableName !== $tableNameAlias) {
1019
-            $statement = str_replace($tableName, $tableNameAlias, $statement);
1020
-        }
1021
-        return $statement;
1022
-    }
1023
-
1024
-    /**
1025
-     * Returns an instance of the current Backend User.
1026
-     *
1027
-     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1028
-     */
1029
-    protected function getBackendUser()
1030
-    {
1031
-        return $GLOBALS['BE_USER'];
1032
-    }
1033
-
1034
-    /**
1035
-     * Tell whether a Backend User is logged in.
1036
-     *
1037
-     * @return bool
1038
-     */
1039
-    protected function isBackendUserLogged()
1040
-    {
1041
-        return is_object($GLOBALS['BE_USER']);
1042
-    }
1043
-
1044
-    /**
1045
-     * @return PageRepository|object
1046
-     */
1047
-    protected function getPageRepository()
1048
-    {
1049
-        if (!$this->pageRepository instanceof PageRepository) {
1050
-            if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
1051
-                $this->pageRepository = $GLOBALS['TSFE']->sys_page;
1052
-            } else {
1053
-                $this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
1054
-            }
1055
-        }
1056
-
1057
-        return $this->pageRepository;
1058
-    }
1059
-
1060
-    /**
1061
-     * @return \Fab\Vidi\Resolver\FieldPathResolver|object
1062
-     */
1063
-    protected function getFieldPathResolver()
1064
-    {
1065
-        return GeneralUtility::makeInstance(\Fab\Vidi\Resolver\FieldPathResolver::class);
1066
-    }
1067
-
1068
-    /**
1069
-     * @return object|Connection
1070
-     */
1071
-    protected function getConnection(): Connection
1072
-    {
1073
-        /** @var ConnectionPool $connectionPool */
1074
-        return GeneralUtility::makeInstance(ConnectionPool::class)
1075
-            ->getConnectionForTable($this->getTableName());
1076
-    }
1077
-
1078
-    /**
1079
-     * @return object|QueryBuilder
1080
-     */
1081
-    protected function getQueryBuilder(): QueryBuilder
1082
-    {
1083
-        /** @var ConnectionPool $connectionPool */
1084
-        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1085
-        return $connectionPool->getQueryBuilderForTable($this->getTableName());
1086
-    }
1087
-
1088
-    /**
1089
-     * @return string
1090
-     */
1091
-    public function getTableName(): string
1092
-    {
1093
-        return $this->query->getSource()->getNodeTypeName(); // getSelectorName()
1094
-    }
42
+	const OPERATOR_EQUAL_TO_NULL = 'operatorEqualToNull';
43
+	const OPERATOR_NOT_EQUAL_TO_NULL = 'operatorNotEqualToNull';
44
+
45
+	/**
46
+	 * The TYPO3 page repository. Used for language and workspace overlay
47
+	 *
48
+	 * @var PageRepository
49
+	 */
50
+	protected $pageRepository;
51
+
52
+	/**
53
+	 * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
54
+	 * @inject
55
+	 */
56
+	protected $environmentService;
57
+
58
+	/**
59
+	 * @var \Fab\Vidi\Persistence\Query
60
+	 */
61
+	protected $query;
62
+
63
+	/**
64
+	 * Store some info related to table name and its aliases.
65
+	 *
66
+	 * @var array
67
+	 */
68
+	protected $tableNameAliases = array(
69
+		'aliases' => [],
70
+		'aliasIncrement' => [],
71
+	);
72
+
73
+	/**
74
+	 * Use to store the current foreign table name alias.
75
+	 *
76
+	 * @var string
77
+	 */
78
+	protected $currentChildTableNameAlias = '';
79
+
80
+	/**
81
+	 * @param Query $query
82
+	 */
83
+	public function __construct(Query $query)
84
+	{
85
+		$this->query = $query;
86
+	}
87
+
88
+	/**
89
+	 * @param $parameters
90
+	 * @return array
91
+	 */
92
+	protected static function getTypes($parameters)
93
+	{
94
+		$types = [];
95
+		foreach ($parameters as $parameter) {
96
+			if (is_array($parameter)) {
97
+				if (MathUtility::canBeInterpretedAsInteger($parameter[0])) {
98
+					$types[] = \Doctrine\DBAL\Connection::PARAM_INT_ARRAY;
99
+				} else {
100
+					$types[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
101
+				}
102
+			} else {
103
+				if (MathUtility::canBeInterpretedAsInteger($parameter)) {
104
+					$types[] = ParameterType::INTEGER;
105
+				} else {
106
+					$types[] = ParameterType::STRING;
107
+				}
108
+			}
109
+		}
110
+		return $types;
111
+	}
112
+
113
+	/**
114
+	 * Returns the result of the query
115
+	 */
116
+	public function fetchResult()
117
+	{
118
+		$parameters = [];
119
+		$statementParts = $this->parseQuery($parameters);
120
+		$statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
121
+		$sql = $this->buildQuery($statementParts);
122
+		//print $sql; exit();
123
+
124
+		$rows = $this->getConnection()
125
+			->executeQuery($sql, $parameters, self::getTypes($parameters))
126
+			->fetchAll();
127
+
128
+		return $this->getContentObjects($rows);
129
+	}
130
+
131
+	/**
132
+	 * Returns the number of tuples matching the query.
133
+	 *
134
+	 * @return int The number of matching tuples
135
+	 */
136
+	public function countResult()
137
+	{
138
+		$parameters = [];
139
+		$statementParts = $this->parseQuery($parameters);
140
+		$statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
141
+		$types = self::getTypes($parameters);
142
+
143
+		// if limit is set, we need to count the rows "manually" as COUNT(*) ignores LIMIT constraints
144
+		if (!empty($statementParts['limit'])) {
145
+			$sql = $this->buildQuery($statementParts);
146
+
147
+			$count = $this
148
+				->getConnection()
149
+				->executeQuery($sql, $parameters, $types)
150
+				->rowCount();
151
+		} else {
152
+			$statementParts['fields'] = array('COUNT(*)');
153
+			// having orderings without grouping is not compatible with non-MySQL DBMS
154
+			$statementParts['orderings'] = [];
155
+			if (isset($statementParts['keywords']['distinct'])) {
156
+				unset($statementParts['keywords']['distinct']);
157
+				$distinctField = $this->query->getDistinct() ? $this->query->getDistinct() : 'uid';
158
+				$statementParts['fields'] = array('COUNT(DISTINCT ' . $statementParts['mainTable'] . '.' . $distinctField . ')');
159
+			}
160
+
161
+			$sql = $this->buildQuery($statementParts);
162
+			$count = $this
163
+				->getConnection()
164
+				->executeQuery($sql, $parameters, $types)
165
+				->fetchColumn(0);
166
+		}
167
+		return (int)$count;
168
+	}
169
+
170
+	/**
171
+	 * Parses the query and returns the SQL statement parts.
172
+	 *
173
+	 * @param array &$parameters
174
+	 * @return array
175
+	 */
176
+	public function parseQuery(array &$parameters)
177
+	{
178
+		$statementParts = [];
179
+		$statementParts['keywords'] = [];
180
+		$statementParts['tables'] = [];
181
+		$statementParts['unions'] = [];
182
+		$statementParts['fields'] = [];
183
+		$statementParts['where'] = [];
184
+		$statementParts['additionalWhereClause'] = [];
185
+		$statementParts['orderings'] = [];
186
+		$statementParts['limit'] = [];
187
+		$query = $this->query;
188
+		$source = $query->getSource();
189
+		$this->parseSource($source, $statementParts);
190
+		$this->parseConstraint($query->getConstraint(), $source, $statementParts, $parameters);
191
+		$this->parseOrderings($query->getOrderings(), $source, $statementParts);
192
+		$this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $statementParts);
193
+		$tableNames = array_unique(array_keys($statementParts['tables'] + $statementParts['unions']));
194
+		foreach ($tableNames as $tableNameOrAlias) {
195
+			if (is_string($tableNameOrAlias) && strlen($tableNameOrAlias) > 0) {
196
+				$this->addAdditionalWhereClause($query->getQuerySettings(), $tableNameOrAlias, $statementParts);
197
+			}
198
+		}
199
+
200
+		return $statementParts;
201
+	}
202
+
203
+	/**
204
+	 * Fiddle with the statement structure to handle recursive MM relations.
205
+	 * For the recursive MM query to work, we must invert some values.
206
+	 * Let see if that is the best way of doing that...
207
+	 *
208
+	 * @param array $statementParts
209
+	 * @return array
210
+	 */
211
+	public function processStatementStructureForRecursiveMMRelation(array $statementParts)
212
+	{
213
+
214
+		if ($this->hasRecursiveMMRelation()) {
215
+			$tableName = $this->query->getType();
216
+
217
+			// In order the MM query to work for a recursive MM query, we must invert some values.
218
+			// tx_domain_model_foo0 (the alias) <--> tx_domain_model_foo (the origin table name)
219
+			$values = [];
220
+			foreach ($statementParts['fields'] as $key => $value) {
221
+				$values[$key] = str_replace($tableName, $tableName . '0', $value);
222
+			}
223
+			$statementParts['fields'] = $values;
224
+
225
+			// Same comment as above.
226
+			$values = [];
227
+			foreach ($statementParts['where'] as $key => $value) {
228
+				$values[$key] = str_replace($tableName . '0', $tableName, $value);
229
+			}
230
+			$statementParts['where'] = $values;
231
+
232
+			// We must be more restrictive by transforming the "left" union by "inner"
233
+			$values = [];
234
+			foreach ($statementParts['unions'] as $key => $value) {
235
+				$values[$key] = str_replace('LEFT JOIN', 'INNER JOIN', $value);
236
+			}
237
+			$statementParts['unions'] = $values;
238
+		}
239
+
240
+		return $statementParts;
241
+	}
242
+
243
+	/**
244
+	 * Tell whether there is a recursive MM relation.
245
+	 *
246
+	 * @return bool
247
+	 */
248
+	public function hasRecursiveMMRelation()
249
+	{
250
+		return isset($this->tableNameAliases['aliasIncrement'][$this->query->getType()]);
251
+
252
+	}
253
+
254
+	/**
255
+	 * Returns the statement, ready to be executed.
256
+	 *
257
+	 * @param array $statementParts The SQL statement parts
258
+	 * @return string The SQL statement
259
+	 */
260
+	public function buildQuery(array $statementParts)
261
+	{
262
+
263
+		// Add more statement to the UNION part.
264
+		if (!empty($statementParts['unions'])) {
265
+			foreach ($statementParts['unions'] as $tableName => $unionPart) {
266
+				if (!empty($statementParts['additionalWhereClause'][$tableName])) {
267
+					$statementParts['unions'][$tableName] .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
268
+				}
269
+			}
270
+		}
271
+
272
+		$statement = 'SELECT ' . implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']) . ' FROM ' . implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']);
273
+		if (!empty($statementParts['where'])) {
274
+			$statement .= ' WHERE ' . implode('', $statementParts['where']);
275
+			if (!empty($statementParts['additionalWhereClause'][$this->query->getType()])) {
276
+				$statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
277
+			}
278
+		} elseif (!empty($statementParts['additionalWhereClause'])) {
279
+			$statement .= ' WHERE ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
280
+		}
281
+		if (!empty($statementParts['orderings'])) {
282
+			$statement .= ' ORDER BY ' . implode(', ', $statementParts['orderings']);
283
+		}
284
+		if (!empty($statementParts['limit'])) {
285
+			$statement .= ' LIMIT ' . $statementParts['limit'];
286
+		}
287
+
288
+		return $statement;
289
+	}
290
+
291
+	/**
292
+	 * Transforms a Query Source into SQL and parameter arrays
293
+	 *
294
+	 * @param SourceInterface $source The source
295
+	 * @param array &$sql
296
+	 * @return void
297
+	 */
298
+	protected function parseSource(SourceInterface $source, array &$sql)
299
+	{
300
+		$tableName = $this->getTableName();
301
+		$sql['fields'][$tableName] = $tableName . '.*';
302
+		if ($this->query->getDistinct()) {
303
+			$sql['fields'][$tableName] = $tableName . '.' . $this->query->getDistinct();
304
+			$sql['keywords']['distinct'] = 'DISTINCT';
305
+		}
306
+		$sql['tables'][$tableName] = $tableName;
307
+		$sql['mainTable'] = $tableName;
308
+	}
309
+
310
+	/**
311
+	 * Transforms a constraint into SQL and parameter arrays
312
+	 *
313
+	 * @param ConstraintInterface $constraint The constraint
314
+	 * @param SourceInterface $source The source
315
+	 * @param array &$statementParts The query parts
316
+	 * @param array &$parameters The parameters that will replace the markers
317
+	 * @return void
318
+	 */
319
+	protected function parseConstraint(ConstraintInterface $constraint = null, SourceInterface $source, array &$statementParts, array &$parameters)
320
+	{
321
+		if ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface) {
322
+			$statementParts['where'][] = '(';
323
+			$this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
324
+			$statementParts['where'][] = ' AND ';
325
+			$this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
326
+			$statementParts['where'][] = ')';
327
+		} elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface) {
328
+			$statementParts['where'][] = '(';
329
+			$this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
330
+			$statementParts['where'][] = ' OR ';
331
+			$this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
332
+			$statementParts['where'][] = ')';
333
+		} elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface) {
334
+			$statementParts['where'][] = 'NOT (';
335
+			$this->parseConstraint($constraint->getConstraint(), $source, $statementParts, $parameters);
336
+			$statementParts['where'][] = ')';
337
+		} elseif ($constraint instanceof ComparisonInterface) {
338
+			$this->parseComparison($constraint, $source, $statementParts, $parameters);
339
+		}
340
+	}
341
+
342
+	/**
343
+	 * Parse a Comparison into SQL and parameter arrays.
344
+	 *
345
+	 * @param ComparisonInterface $comparison The comparison to parse
346
+	 * @param SourceInterface $source The source
347
+	 * @param array &$statementParts SQL query parts to add to
348
+	 * @param array &$parameters Parameters to bind to the SQL
349
+	 * @return void
350
+	 * @throws Exception\RepositoryException
351
+	 */
352
+	protected function parseComparison(ComparisonInterface $comparison, SourceInterface $source, array &$statementParts, array &$parameters)
353
+	{
354
+		$operand1 = $comparison->getOperand1();
355
+		$operator = $comparison->getOperator();
356
+		$operand2 = $comparison->getOperand2();
357
+		if ($operator === QueryInterface::OPERATOR_IN) {
358
+			$items = [];
359
+			$hasValue = false;
360
+			foreach ($operand2 as $value) {
361
+				$value = $this->getPlainValue($value);
362
+				if ($value !== null) {
363
+					$items[] = $value;
364
+					$hasValue = true;
365
+				}
366
+			}
367
+			if ($hasValue === false) {
368
+				$statementParts['where'][] = '1<>1';
369
+			} else {
370
+				$this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters, null);
371
+				$parameters[] = $items;
372
+			}
373
+		} elseif ($operator === QueryInterface::OPERATOR_CONTAINS) {
374
+			if ($operand2 === null) {
375
+				$statementParts['where'][] = '1<>1';
376
+			} else {
377
+				throw new \Exception('Not implemented! Contact extension author.', 1412931227);
378
+				# @todo re-implement me if necessary.
379
+				#$tableName = $this->query->getType();
380
+				#$propertyName = $operand1->getPropertyName();
381
+				#while (strpos($propertyName, '.') !== false) {
382
+				#	$this->addUnionStatement($tableName, $propertyName, $statementParts);
383
+				#}
384
+				#$columnName = $propertyName;
385
+				#$columnMap = $propertyName;
386
+				#$typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
387
+				#if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
388
+				#	$relationTableName = $columnMap->getRelationTableName();
389
+				#	$statementParts['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=?)';
390
+				#	$parameters[] = intval($this->getPlainValue($operand2));
391
+				#} elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
392
+				#	$parentKeyFieldName = $columnMap->getParentKeyFieldName();
393
+				#	if (isset($parentKeyFieldName)) {
394
+				#		$childTableName = $columnMap->getChildTableName();
395
+				#		$statementParts['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=?)';
396
+				#		$parameters[] = intval($this->getPlainValue($operand2));
397
+				#	} else {
398
+				#		$statementParts['where'][] = 'FIND_IN_SET(?,' . $tableName . '.' . $columnName . ')';
399
+				#		$parameters[] = intval($this->getPlainValue($operand2));
400
+				#	}
401
+				#} else {
402
+				#	throw new Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
403
+				#}
404
+			}
405
+		} else {
406
+			if ($operand2 === null) {
407
+				if ($operator === QueryInterface::OPERATOR_EQUAL_TO) {
408
+					$operator = self::OPERATOR_EQUAL_TO_NULL;
409
+				} elseif ($operator === QueryInterface::OPERATOR_NOT_EQUAL_TO) {
410
+					$operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
411
+				}
412
+			}
413
+			$this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters);
414
+			$parameters[] = $this->getPlainValue($operand2);
415
+		}
416
+	}
417
+
418
+	/**
419
+	 * Returns a plain value, i.e. objects are flattened if possible.
420
+	 *
421
+	 * @param mixed $input
422
+	 * @return mixed
423
+	 * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException
424
+	 */
425
+	protected function getPlainValue($input)
426
+	{
427
+		if (is_array($input)) {
428
+			throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An array could not be converted to a plain value.', 1274799932);
429
+		}
430
+		if ($input instanceof \DateTime) {
431
+			return $input->format('U');
432
+		} elseif (is_object($input)) {
433
+			if ($input instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
434
+				$realInput = $input->_loadRealInstance();
435
+			} else {
436
+				$realInput = $input;
437
+			}
438
+			if ($realInput instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
439
+				return $realInput->getUid();
440
+			} else {
441
+				throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An object of class "' . get_class($realInput) . '" could not be converted to a plain value.', 1274799934);
442
+			}
443
+		} elseif (is_bool($input)) {
444
+			return $input === true ? 1 : 0;
445
+		} else {
446
+			return $input;
447
+		}
448
+	}
449
+
450
+	/**
451
+	 * Parse a DynamicOperand into SQL and parameter arrays.
452
+	 *
453
+	 * @param DynamicOperandInterface $operand
454
+	 * @param string $operator One of the JCR_OPERATOR_* constants
455
+	 * @param SourceInterface $source The source
456
+	 * @param array &$statementParts The query parts
457
+	 * @param array &$parameters The parameters that will replace the markers
458
+	 * @param string $valueFunction an optional SQL function to apply to the operand value
459
+	 * @return void
460
+	 */
461
+	protected function parseDynamicOperand(DynamicOperandInterface $operand, $operator, SourceInterface $source, array &$statementParts, array &$parameters, $valueFunction = null)
462
+	{
463
+		if ($operand instanceof LowerCaseInterface) {
464
+			$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'LOWER');
465
+		} elseif ($operand instanceof UpperCaseInterface) {
466
+			$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'UPPER');
467
+		} elseif ($operand instanceof PropertyValueInterface) {
468
+			$propertyName = $operand->getPropertyName();
469
+
470
+			// Reset value.
471
+			$this->currentChildTableNameAlias = '';
472
+
473
+			if ($source instanceof SelectorInterface) {
474
+				$tableName = $this->query->getType();
475
+				while (strpos($propertyName, '.') !== false) {
476
+					$this->addUnionStatement($tableName, $propertyName, $statementParts);
477
+				}
478
+			} elseif ($source instanceof JoinInterface) {
479
+				$tableName = $source->getJoinCondition()->getSelector1Name();
480
+			}
481
+
482
+			$columnName = $propertyName;
483
+			$resolvedOperator = $this->resolveOperator($operator);
484
+			$constraintSQL = '';
485
+
486
+			$marker = $operator === QueryInterface::OPERATOR_IN
487
+				? '(?)'
488
+				: '?';
489
+
490
+			if ($valueFunction === null) {
491
+				$constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $resolvedOperator . ' ' . $marker;
492
+			} else {
493
+				$constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $resolvedOperator . ' ' . $marker;
494
+			}
495
+
496
+			if (isset($tableName) && !empty($this->currentChildTableNameAlias)) {
497
+				$constraintSQL = $this->replaceTableNameByAlias($tableName, $this->currentChildTableNameAlias, $constraintSQL);
498
+			}
499
+			$statementParts['where'][] = $constraintSQL;
500
+		}
501
+	}
502
+
503
+	/**
504
+	 * @param string &$tableName
505
+	 * @param string &$propertyPath
506
+	 * @param array &$statementParts
507
+	 */
508
+	protected function addUnionStatement(&$tableName, &$propertyPath, array &$statementParts)
509
+	{
510
+
511
+		$table = Tca::table($tableName);
512
+
513
+		$explodedPropertyPath = explode('.', $propertyPath, 2);
514
+		$fieldName = $explodedPropertyPath[0];
515
+
516
+		// Field of type "group" are special because property path must contain the table name
517
+		// to determine the relation type. Example for sys_category, property path will look like "items.sys_file"
518
+		$parts = explode('.', $propertyPath, 3);
519
+		if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
520
+			$explodedPropertyPath[0] = $parts[0] . '.' . $parts[1];
521
+			$explodedPropertyPath[1] = $parts[2];
522
+			$fieldName = $explodedPropertyPath[0];
523
+		}
524
+
525
+		$parentKeyFieldName = $table->field($fieldName)->getForeignField();
526
+		$childTableName = $table->field($fieldName)->getForeignTable();
527
+
528
+		if ($childTableName === null) {
529
+			throw new Exception\InvalidRelationConfigurationException('The relation information for property "' . $fieldName . '" of class "' . $tableName . '" is missing.', 1353170925);
530
+		}
531
+
532
+		if ($table->field($fieldName)->hasOne()) { // includes relation "one-to-one" and "many-to-one"
533
+			// sometimes the opposite relation is not defined. We don't want to force this config for backward compatibility reasons.
534
+			// $parentKeyFieldName === null does the trick somehow. Before condition was if (isset($parentKeyFieldName))
535
+			if ($table->field($fieldName)->hasRelationManyToOne() || $parentKeyFieldName === null) {
536
+				$statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $fieldName . '=' . $childTableName . '.uid';
537
+			} else {
538
+				$statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
539
+			}
540
+		} elseif ($table->field($fieldName)->hasRelationManyToMany()) {
541
+			$relationTableName = $table->field($fieldName)->getManyToManyTable();
542
+
543
+			$parentKeyFieldName = $table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
544
+			$childKeyFieldName = !$table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
545
+
546
+			// MM table e.g sys_category_record_mm
547
+			$relationTableNameAlias = $this->generateAlias($relationTableName);
548
+			$join = sprintf(
549
+				'LEFT JOIN %s AS %s ON %s.uid=%s.%s', $relationTableName,
550
+				$relationTableNameAlias,
551
+				$tableName,
552
+				$relationTableNameAlias,
553
+				$parentKeyFieldName
554
+			);
555
+			$statementParts['unions'][$relationTableNameAlias] = $join;
556
+
557
+			// Foreign table e.g sys_category
558
+			$childTableNameAlias = $this->generateAlias($childTableName);
559
+			$this->currentChildTableNameAlias = $childTableNameAlias;
560
+			$join = sprintf(
561
+				'LEFT JOIN %s AS %s ON %s.%s=%s.uid',
562
+				$childTableName,
563
+				$childTableNameAlias,
564
+				$relationTableNameAlias,
565
+				$childKeyFieldName,
566
+				$childTableNameAlias
567
+			);
568
+			$statementParts['unions'][$childTableNameAlias] = $join;
569
+
570
+			// Find a possible table name for a MM condition.
571
+			$tableNameCondition = $table->field($fieldName)->getAdditionalTableNameCondition();
572
+			if ($tableNameCondition) {
573
+
574
+				// If we can find a source file name,  we can then retrieve more MM conditions from the TCA such as a field name.
575
+				$sourceFileName = $this->query->getSourceFieldName();
576
+				if (empty($sourceFileName)) {
577
+					$additionalMMConditions = array(
578
+						'tablenames' => $tableNameCondition,
579
+					);
580
+				} else {
581
+					$additionalMMConditions = Tca::table($tableNameCondition)->field($sourceFileName)->getAdditionalMMCondition();
582
+				}
583
+
584
+				foreach ($additionalMMConditions as $additionalFieldName => $additionalMMCondition) {
585
+					$additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
586
+					$statementParts['unions'][$relationTableNameAlias] .= $additionalJoin;
587
+
588
+					$additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
589
+					$statementParts['unions'][$childTableNameAlias] .= $additionalJoin;
590
+				}
591
+			}
592
+
593
+		} elseif ($table->field($fieldName)->hasMany()) { // includes relations "many-to-one" and "csv" relations
594
+			$childTableNameAlias = $this->generateAlias($childTableName);
595
+			$this->currentChildTableNameAlias = $childTableNameAlias;
596
+
597
+			if (isset($parentKeyFieldName)) {
598
+				$join = sprintf(
599
+					'LEFT JOIN %s AS %s ON %s.uid=%s.%s',
600
+					$childTableName,
601
+					$childTableNameAlias,
602
+					$tableName,
603
+					$childTableNameAlias,
604
+					$parentKeyFieldName
605
+				);
606
+				$statementParts['unions'][$childTableNameAlias] = $join;
607
+			} else {
608
+				$join = sprintf(
609
+					'LEFT JOIN %s AS %s ON (FIND_IN_SET(%s.uid, %s.%s))',
610
+					$childTableName,
611
+					$childTableNameAlias,
612
+					$childTableNameAlias,
613
+					$tableName,
614
+					$fieldName
615
+				);
616
+				$statementParts['unions'][$childTableNameAlias] = $join;
617
+			}
618
+		} else {
619
+			throw new Exception('Could not determine type of relation.', 1252502725);
620
+		}
621
+
622
+		$statementParts['keywords']['distinct'] = 'DISTINCT';
623
+		$propertyPath = $explodedPropertyPath[1];
624
+		$tableName = $childTableName;
625
+	}
626
+
627
+	/**
628
+	 * Returns the SQL operator for the given JCR operator type.
629
+	 *
630
+	 * @param string $operator One of the JCR_OPERATOR_* constants
631
+	 * @return string an SQL operator
632
+	 * @throws Exception
633
+	 */
634
+	protected function resolveOperator($operator)
635
+	{
636
+		switch ($operator) {
637
+			case self::OPERATOR_EQUAL_TO_NULL:
638
+				$operator = 'IS';
639
+				break;
640
+			case self::OPERATOR_NOT_EQUAL_TO_NULL:
641
+				$operator = 'IS NOT';
642
+				break;
643
+			case QueryInterface::OPERATOR_IN:
644
+				$operator = 'IN';
645
+				break;
646
+			case QueryInterface::OPERATOR_EQUAL_TO:
647
+				$operator = '=';
648
+				break;
649
+			case QueryInterface::OPERATOR_NOT_EQUAL_TO:
650
+				$operator = '!=';
651
+				break;
652
+			case QueryInterface::OPERATOR_LESS_THAN:
653
+				$operator = '<';
654
+				break;
655
+			case QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO:
656
+				$operator = '<=';
657
+				break;
658
+			case QueryInterface::OPERATOR_GREATER_THAN:
659
+				$operator = '>';
660
+				break;
661
+			case QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO:
662
+				$operator = '>=';
663
+				break;
664
+			case QueryInterface::OPERATOR_LIKE:
665
+				$operator = 'LIKE';
666
+				break;
667
+			default:
668
+				throw new Exception('Unsupported operator encountered.', 1242816073);
669
+		}
670
+		return $operator;
671
+	}
672
+
673
+	/**
674
+	 * Adds additional WHERE statements according to the query settings.
675
+	 *
676
+	 * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
677
+	 * @param string $tableNameOrAlias The table name to add the additional where clause for
678
+	 * @param array &$statementParts
679
+	 * @return void
680
+	 */
681
+	protected function addAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableNameOrAlias, &$statementParts)
682
+	{
683
+		$this->addVisibilityConstraintStatement($querySettings, $tableNameOrAlias, $statementParts);
684
+		if ($querySettings->getRespectSysLanguage()) {
685
+			$this->addSysLanguageStatement($tableNameOrAlias, $statementParts, $querySettings);
686
+		}
687
+	}
688
+
689
+	/**
690
+	 * Adds enableFields and deletedClause to the query if necessary
691
+	 *
692
+	 * @param QuerySettingsInterface $querySettings
693
+	 * @param string $tableNameOrAlias The database table name
694
+	 * @param array &$statementParts The query parts
695
+	 * @return void
696
+	 */
697
+	protected function addVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableNameOrAlias, array &$statementParts)
698
+	{
699
+		$statement = '';
700
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
701
+		if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
702
+			$ignoreEnableFields = $querySettings->getIgnoreEnableFields();
703
+			$enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
704
+			$includeDeleted = $querySettings->getIncludeDeleted();
705
+			if ($this->environmentService->isEnvironmentInFrontendMode()) {
706
+				$statement .= $this->getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
707
+			} else {
708
+				// TYPO3_MODE === 'BE'
709
+				$statement .= $this->getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted);
710
+			}
711
+
712
+			// Remove the prefixing "AND" if any.
713
+			if (!empty($statement)) {
714
+				$statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
715
+				$statementParts['additionalWhereClause'][$tableNameOrAlias][] = $statement;
716
+			}
717
+		}
718
+	}
719
+
720
+	/**
721
+	 * Returns constraint statement for frontend context
722
+	 *
723
+	 * @param string $tableNameOrAlias
724
+	 * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
725
+	 * @param array $enableFieldsToBeIgnored If $ignoreEnableFields is true, this array specifies enable fields to be ignored. If it is null or an empty array (default) all enable fields are ignored.
726
+	 * @param boolean $includeDeleted A flag indicating whether deleted records should be included
727
+	 * @return string
728
+	 * @throws Exception\InconsistentQuerySettingsException
729
+	 */
730
+	protected function getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted)
731
+	{
732
+		$statement = '';
733
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
734
+		if ($ignoreEnableFields && !$includeDeleted) {
735
+			if (count($enableFieldsToBeIgnored)) {
736
+				// array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
737
+				$statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
738
+			} else {
739
+				$statement .= $this->getPageRepository()->deleteClause($tableName);
740
+			}
741
+		} elseif (!$ignoreEnableFields && !$includeDeleted) {
742
+			$statement .= $this->getPageRepository()->enableFields($tableName);
743
+		} elseif (!$ignoreEnableFields && $includeDeleted) {
744
+			throw new Exception\InconsistentQuerySettingsException('Query setting "ignoreEnableFields=false" can not be used together with "includeDeleted=true" in frontend context.', 1327678173);
745
+		}
746
+		return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
747
+	}
748
+
749
+	/**
750
+	 * Returns constraint statement for backend context
751
+	 *
752
+	 * @param string $tableNameOrAlias
753
+	 * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
754
+	 * @param boolean $includeDeleted A flag indicating whether deleted records should be included
755
+	 * @return string
756
+	 */
757
+	protected function getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted)
758
+	{
759
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
760
+		$statement = '';
761
+		if (!$ignoreEnableFields) {
762
+			$statement .= BackendUtility::BEenableFields($tableName);
763
+		}
764
+
765
+		// If the table is found to have "workspace" support, add the corresponding fields in the statement.
766
+		if (Tca::table($tableName)->hasWorkspaceSupport()) {
767
+			if ($this->getBackendUser()->workspace === 0) {
768
+				$statement .= ' AND ' . $tableName . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
769
+			} else {
770
+				// Show only records of live and of the current workspace
771
+				// In case we are in a Versioning preview
772
+				$statement .= ' AND (' .
773
+					$tableName . '.t3ver_wsid=0 OR ' .
774
+					$tableName . '.t3ver_wsid=' . (int)$this->getBackendUser()->workspace .
775
+					')';
776
+			}
777
+
778
+			// Check if this segment make sense here or whether it should be in the "if" part when we have workspace = 0
779
+			$statement .= ' AND ' . $tableName . '.pid<>-1';
780
+		}
781
+
782
+		if (!$includeDeleted) {
783
+			$statement .= BackendUtility::deleteClause($tableName);
784
+		}
785
+
786
+		return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
787
+	}
788
+
789
+	/**
790
+	 * Builds the language field statement
791
+	 *
792
+	 * @param string $tableNameOrAlias The database table name
793
+	 * @param array &$statementParts The query parts
794
+	 * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
795
+	 * @return void
796
+	 * @throws Exception
797
+	 */
798
+	protected function addSysLanguageStatement($tableNameOrAlias, array &$statementParts, $querySettings)
799
+	{
800
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
801
+		if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
802
+			if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
803
+				// Select all entries for the current language
804
+				$additionalWhereClause = $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getLanguageUid()) . ',-1)';
805
+				// If any language is set -> get those entries which are not translated yet
806
+				// They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
807
+				if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
808
+					&& $querySettings->getLanguageUid() > 0
809
+				) {
810
+					$additionalWhereClause .= ' OR (' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
811
+						' AND ' . $tableNameOrAlias . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
812
+						' FROM ' . $tableName .
813
+						' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
814
+						' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
815
+
816
+					// Add delete clause to ensure all entries are loaded
817
+					if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
818
+						$additionalWhereClause .= ' AND ' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
819
+					}
820
+					$additionalWhereClause .= '))';
821
+				}
822
+				$statementParts['additionalWhereClause'][$tableNameOrAlias][] = '(' . $additionalWhereClause . ')';
823
+			}
824
+		}
825
+	}
826
+
827
+	/**
828
+	 * Transforms orderings into SQL.
829
+	 *
830
+	 * @param array $orderings An array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
831
+	 * @param SourceInterface $source The source
832
+	 * @param array &$statementParts The query parts
833
+	 * @return void
834
+	 * @throws Exception\UnsupportedOrderException
835
+	 */
836
+	protected function parseOrderings(array $orderings, SourceInterface $source, array &$statementParts)
837
+	{
838
+		foreach ($orderings as $fieldNameAndPath => $order) {
839
+			switch ($order) {
840
+				case QueryInterface::ORDER_ASCENDING:
841
+					$order = 'ASC';
842
+					break;
843
+				case QueryInterface::ORDER_DESCENDING:
844
+					$order = 'DESC';
845
+					break;
846
+				default:
847
+					throw new Exception\UnsupportedOrderException('Unsupported order encountered.', 1456845126);
848
+			}
849
+
850
+			$tableName = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->query->getType());
851
+			$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $tableName);
852
+			$statementParts['orderings'][] = sprintf('%s.%s %s', $tableName, $fieldName, $order);
853
+		}
854
+	}
855
+
856
+	/**
857
+	 * Transforms limit and offset into SQL
858
+	 *
859
+	 * @param int $limit
860
+	 * @param int $offset
861
+	 * @param array &$statementParts
862
+	 * @return void
863
+	 */
864
+	protected function parseLimitAndOffset($limit, $offset, array &$statementParts)
865
+	{
866
+		if ($limit !== null && $offset !== null) {
867
+			$statementParts['limit'] = intval($offset) . ', ' . intval($limit);
868
+		} elseif ($limit !== null) {
869
+			$statementParts['limit'] = intval($limit);
870
+		}
871
+	}
872
+
873
+	/**
874
+	 * @param array $rows
875
+	 * @return array
876
+	 */
877
+	protected function getContentObjects(array $rows): array
878
+	{
879
+		$contentObjects = [];
880
+		foreach ($rows as $row) {
881
+
882
+			// Get language uid from querySettings.
883
+			// Ensure the backend handling is not broken (fallback to Get parameter 'L' if needed)
884
+			$overlaidRow = $this->doLanguageAndWorkspaceOverlay(
885
+				$row,
886
+				$this->query->getQuerySettings()
887
+			);
888
+
889
+			$contentObjects[] = GeneralUtility::makeInstance(
890
+				\Fab\Vidi\Domain\Model\Content::class,
891
+				$this->query->getType(),
892
+				$overlaidRow
893
+			);
894
+		}
895
+
896
+		return $contentObjects;
897
+	}
898
+
899
+	/**
900
+	 * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
901
+	 * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
902
+	 *
903
+	 * @param array $row
904
+	 * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
905
+	 * @return array
906
+	 */
907
+	protected function doLanguageAndWorkspaceOverlay(array $row, $querySettings)
908
+	{
909
+		$tableName = $this->getTableName();
910
+
911
+		$pageRepository = $this->getPageRepository();
912
+		if (is_object($GLOBALS['TSFE'])) {
913
+			$languageMode = $GLOBALS['TSFE']->sys_language_mode;
914
+			if ($this->isBackendUserLogged() && $this->getBackendUser()->workspace !== 0) {
915
+				$pageRepository->versioningWorkspaceId = $this->getBackendUser()->workspace;
916
+			}
917
+		} else {
918
+			$languageMode = '';
919
+			$workspaceUid = $this->getBackendUser()->workspace;
920
+			$pageRepository->versioningWorkspaceId = $workspaceUid;
921
+			if ($this->getBackendUser()->workspace !== 0) {
922
+				$pageRepository->versioningPreview = 1;
923
+			}
924
+		}
925
+
926
+		// If current row is a translation select its parent
927
+		if (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
928
+			&& isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
929
+		) {
930
+			if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
931
+				&& $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
932
+			) {
933
+				$queryBuilder = $this->getQueryBuilder();
934
+				$row = $queryBuilder
935
+					->select($tableName . '.*')
936
+					->from($tableName)
937
+					->andWhere(
938
+						$tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
939
+						$tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = 0'
940
+					)
941
+					->execute()
942
+					->fetch();
943
+			}
944
+		}
945
+
946
+		// Retrieve the original uid; Used for Workspaces!
947
+		if (TYPO3_MODE !== 'BE') {
948
+			$pageRepository->versionOL($tableName, $row, true, true);
949
+		} else {
950
+			\TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($tableName, $row);
951
+		}
952
+		if ($pageRepository->versioningPreview && isset($row['_ORIG_uid'])) {
953
+			$row['uid'] = $row['_ORIG_uid'];
954
+		}
955
+
956
+		// Special case for table "pages"
957
+		if ($tableName == 'pages') {
958
+			$row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
959
+		} elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
960
+			&& $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
961
+		) {
962
+			if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], array(-1, 0))) {
963
+				$overlayMode = $languageMode === 'strict' ? 'hideNonTranslated' : '';
964
+				$row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
965
+			}
966
+		}
967
+
968
+		return $row;
969
+	}
970
+
971
+	/**
972
+	 * Return a resolved table name given a possible table name alias.
973
+	 *
974
+	 * @param string $tableNameOrAlias
975
+	 * @return string
976
+	 */
977
+	protected function resolveTableNameAlias($tableNameOrAlias)
978
+	{
979
+		$resolvedTableName = $tableNameOrAlias;
980
+		if (!empty($this->tableNameAliases['aliases'][$tableNameOrAlias])) {
981
+			$resolvedTableName = $this->tableNameAliases['aliases'][$tableNameOrAlias];
982
+		}
983
+		return $resolvedTableName;
984
+	}
985
+
986
+	/**
987
+	 * Generate a unique table name alias for the given table name.
988
+	 *
989
+	 * @param string $tableName
990
+	 * @return string
991
+	 */
992
+	protected function generateAlias($tableName)
993
+	{
994
+
995
+		if (!isset($this->tableNameAliases['aliasIncrement'][$tableName])) {
996
+			$this->tableNameAliases['aliasIncrement'][$tableName] = 0;
997
+		}
998
+
999
+		$numberOfAliases = $this->tableNameAliases['aliasIncrement'][$tableName];
1000
+		$tableNameAlias = $tableName . $numberOfAliases;
1001
+
1002
+		$this->tableNameAliases['aliasIncrement'][$tableName]++;
1003
+		$this->tableNameAliases['aliases'][$tableNameAlias] = $tableName;
1004
+
1005
+		return $tableNameAlias;
1006
+	}
1007
+
1008
+	/**
1009
+	 * Replace the table names by its table name alias within the given statement.
1010
+	 *
1011
+	 * @param string $tableName
1012
+	 * @param string $tableNameAlias
1013
+	 * @param string $statement
1014
+	 * @return string
1015
+	 */
1016
+	protected function replaceTableNameByAlias($tableName, $tableNameAlias, $statement)
1017
+	{
1018
+		if ($statement && $tableName !== $tableNameAlias) {
1019
+			$statement = str_replace($tableName, $tableNameAlias, $statement);
1020
+		}
1021
+		return $statement;
1022
+	}
1023
+
1024
+	/**
1025
+	 * Returns an instance of the current Backend User.
1026
+	 *
1027
+	 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1028
+	 */
1029
+	protected function getBackendUser()
1030
+	{
1031
+		return $GLOBALS['BE_USER'];
1032
+	}
1033
+
1034
+	/**
1035
+	 * Tell whether a Backend User is logged in.
1036
+	 *
1037
+	 * @return bool
1038
+	 */
1039
+	protected function isBackendUserLogged()
1040
+	{
1041
+		return is_object($GLOBALS['BE_USER']);
1042
+	}
1043
+
1044
+	/**
1045
+	 * @return PageRepository|object
1046
+	 */
1047
+	protected function getPageRepository()
1048
+	{
1049
+		if (!$this->pageRepository instanceof PageRepository) {
1050
+			if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
1051
+				$this->pageRepository = $GLOBALS['TSFE']->sys_page;
1052
+			} else {
1053
+				$this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
1054
+			}
1055
+		}
1056
+
1057
+		return $this->pageRepository;
1058
+	}
1059
+
1060
+	/**
1061
+	 * @return \Fab\Vidi\Resolver\FieldPathResolver|object
1062
+	 */
1063
+	protected function getFieldPathResolver()
1064
+	{
1065
+		return GeneralUtility::makeInstance(\Fab\Vidi\Resolver\FieldPathResolver::class);
1066
+	}
1067
+
1068
+	/**
1069
+	 * @return object|Connection
1070
+	 */
1071
+	protected function getConnection(): Connection
1072
+	{
1073
+		/** @var ConnectionPool $connectionPool */
1074
+		return GeneralUtility::makeInstance(ConnectionPool::class)
1075
+			->getConnectionForTable($this->getTableName());
1076
+	}
1077
+
1078
+	/**
1079
+	 * @return object|QueryBuilder
1080
+	 */
1081
+	protected function getQueryBuilder(): QueryBuilder
1082
+	{
1083
+		/** @var ConnectionPool $connectionPool */
1084
+		$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1085
+		return $connectionPool->getQueryBuilderForTable($this->getTableName());
1086
+	}
1087
+
1088
+	/**
1089
+	 * @return string
1090
+	 */
1091
+	public function getTableName(): string
1092
+	{
1093
+		return $this->query->getSource()->getNodeTypeName(); // getSelectorName()
1094
+	}
1095 1095
 
1096 1096
 }
Please login to merge, or discard this patch.
ext_emconf.php 1 patch
Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -1,28 +1,28 @@
 block discarded – undo
1 1
 <?php
2 2
 
3 3
 $EM_CONF[$_EXTKEY] = [
4
-    'title' => 'Versatile and Interactive Display - List Component',
5
-    'description' => 'Generic listing of records with versatile ways of interacting with the data, e.g. advanced filter, inline editing, mass editing, ... Veni, vidi, vici!',
6
-    'category' => 'module',
7
-    'author' => 'Fabien Udriot',
8
-    'author_email' => '[email protected]',
9
-    'module' => '',
10
-    'state' => 'stable',
11
-    'version' => '4.0.1',
12
-    'autoload' => [
13
-        'psr-4' => ['Fab\\Vidi\\' => 'Classes']
14
-    ],
15
-    'constraints' =>
16
-        [
17
-            'depends' =>
18
-                [
19
-                    'typo3' => '9.5.0-9.5.99',
20
-                ],
21
-            'conflicts' =>
22
-                [
23
-                ],
24
-            'suggests' =>
25
-                [
26
-                ],
27
-        ]
4
+	'title' => 'Versatile and Interactive Display - List Component',
5
+	'description' => 'Generic listing of records with versatile ways of interacting with the data, e.g. advanced filter, inline editing, mass editing, ... Veni, vidi, vici!',
6
+	'category' => 'module',
7
+	'author' => 'Fabien Udriot',
8
+	'author_email' => '[email protected]',
9
+	'module' => '',
10
+	'state' => 'stable',
11
+	'version' => '4.0.1',
12
+	'autoload' => [
13
+		'psr-4' => ['Fab\\Vidi\\' => 'Classes']
14
+	],
15
+	'constraints' =>
16
+		[
17
+			'depends' =>
18
+				[
19
+					'typo3' => '9.5.0-9.5.99',
20
+				],
21
+			'conflicts' =>
22
+				[
23
+				],
24
+			'suggests' =>
25
+				[
26
+				],
27
+		]
28 28
 ];
29 29
\ No newline at end of file
Please login to merge, or discard this patch.