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