Completed
Push — master ( b8d895...def68e )
by Fabien
02:42
created
Classes/Persistence/Storage/VidiDbBackend.php 2 patches
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
-        $parts = explode('.', $propertyPath, 3);
592
-        if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
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
+		$parts = explode('.', $propertyPath, 3);
592
+		if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
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.
Spacing   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -127,7 +127,7 @@  discard block
 block discarded – undo
127 127
         $fieldNames = array_keys($identifier);
128 128
         $suffixedFieldNames = [];
129 129
         foreach ($fieldNames as $fieldName) {
130
-            $suffixedFieldNames[] = $fieldName . '=?';
130
+            $suffixedFieldNames[] = $fieldName.'=?';
131 131
         }
132 132
         return implode(' AND ', $suffixedFieldNames);
133 133
     }
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
             if (isset($statementParts['keywords']['distinct'])) {
189 189
                 unset($statementParts['keywords']['distinct']);
190 190
                 $distinctField = $this->query->getDistinct() ? $this->query->getDistinct() : 'uid';
191
-                $statementParts['fields'] = array('COUNT(DISTINCT ' . reset($statementParts['tables']) . '.' . $distinctField . ')');
191
+                $statementParts['fields'] = array('COUNT(DISTINCT '.reset($statementParts['tables']).'.'.$distinctField.')');
192 192
             }
193 193
 
194 194
             $statement = $this->buildQuery($statementParts);
@@ -258,14 +258,14 @@  discard block
 block discarded – undo
258 258
             // tx_domain_model_foo0 (the alias) <--> tx_domain_model_foo (the origin table name)
259 259
             $values = [];
260 260
             foreach ($statementParts['fields'] as $key => $value) {
261
-                $values[$key] = str_replace($tableName, $tableName . '0', $value);
261
+                $values[$key] = str_replace($tableName, $tableName.'0', $value);
262 262
             }
263 263
             $statementParts['fields'] = $values;
264 264
 
265 265
             // Same comment as above.
266 266
             $values = [];
267 267
             foreach ($statementParts['where'] as $key => $value) {
268
-                $values[$key] = str_replace($tableName . '0', $tableName, $value);
268
+                $values[$key] = str_replace($tableName.'0', $tableName, $value);
269 269
             }
270 270
             $statementParts['where'] = $values;
271 271
 
@@ -304,25 +304,25 @@  discard block
 block discarded – undo
304 304
         if (!empty($statementParts['unions'])) {
305 305
             foreach ($statementParts['unions'] as $tableName => $unionPart) {
306 306
                 if (!empty($statementParts['additionalWhereClause'][$tableName])) {
307
-                    $statementParts['unions'][$tableName] .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
307
+                    $statementParts['unions'][$tableName] .= ' AND '.implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
308 308
                 }
309 309
             }
310 310
         }
311 311
 
312
-        $statement = 'SELECT ' . implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']) . ' FROM ' . implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']);
312
+        $statement = 'SELECT '.implode(' ', $statementParts['keywords']).' '.implode(',', $statementParts['fields']).' FROM '.implode(' ', $statementParts['tables']).' '.implode(' ', $statementParts['unions']);
313 313
         if (!empty($statementParts['where'])) {
314
-            $statement .= ' WHERE ' . implode('', $statementParts['where']);
314
+            $statement .= ' WHERE '.implode('', $statementParts['where']);
315 315
             if (!empty($statementParts['additionalWhereClause'][$this->query->getType()])) {
316
-                $statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
316
+                $statement .= ' AND '.implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
317 317
             }
318 318
         } elseif (!empty($statementParts['additionalWhereClause'])) {
319
-            $statement .= ' WHERE ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
319
+            $statement .= ' WHERE '.implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
320 320
         }
321 321
         if (!empty($statementParts['orderings'])) {
322
-            $statement .= ' ORDER BY ' . implode(', ', $statementParts['orderings']);
322
+            $statement .= ' ORDER BY '.implode(', ', $statementParts['orderings']);
323 323
         }
324 324
         if (!empty($statementParts['limit'])) {
325
-            $statement .= ' LIMIT ' . $statementParts['limit'];
325
+            $statement .= ' LIMIT '.$statementParts['limit'];
326 326
         }
327 327
 
328 328
         return $statement;
@@ -339,10 +339,10 @@  discard block
 block discarded – undo
339 339
     {
340 340
         if ($source instanceof SelectorInterface) {
341 341
             $tableName = $source->getNodeTypeName();
342
-            $sql['fields'][$tableName] = $tableName . '.*';
342
+            $sql['fields'][$tableName] = $tableName.'.*';
343 343
             $sql['tables'][$tableName] = $tableName;
344 344
             if ($this->query->getDistinct()) {
345
-                $sql['fields'][$tableName] = $tableName . '.' . $this->query->getDistinct();
345
+                $sql['fields'][$tableName] = $tableName.'.'.$this->query->getDistinct();
346 346
                 $sql['keywords']['distinct'] = 'DISTINCT';
347 347
             }
348 348
         } elseif ($source instanceof JoinInterface) {
@@ -367,15 +367,15 @@  discard block
 block discarded – undo
367 367
             $rightTableName = $rightSource->getLeft()->getSelectorName();
368 368
         } else {
369 369
             $rightTableName = $rightSource->getSelectorName();
370
-            $sql['fields'][$leftTableName] = $rightTableName . '.*';
370
+            $sql['fields'][$leftTableName] = $rightTableName.'.*';
371 371
         }
372 372
         $sql['tables'][$leftTableName] = $leftTableName;
373
-        $sql['unions'][$rightTableName] = 'LEFT JOIN ' . $rightTableName;
373
+        $sql['unions'][$rightTableName] = 'LEFT JOIN '.$rightTableName;
374 374
         $joinCondition = $join->getJoinCondition();
375 375
         if ($joinCondition instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\EquiJoinCondition) {
376 376
             $column1Name = $joinCondition->getProperty1Name();
377 377
             $column2Name = $joinCondition->getProperty2Name();
378
-            $sql['unions'][$rightTableName] .= ' ON ' . $joinCondition->getSelector1Name() . '.' . $column1Name . ' = ' . $joinCondition->getSelector2Name() . '.' . $column2Name;
378
+            $sql['unions'][$rightTableName] .= ' ON '.$joinCondition->getSelector1Name().'.'.$column1Name.' = '.$joinCondition->getSelector2Name().'.'.$column2Name;
379 379
         }
380 380
         if ($rightSource instanceof JoinInterface) {
381 381
             $this->parseJoin($rightSource, $sql);
@@ -513,7 +513,7 @@  discard block
 block discarded – undo
513 513
             if ($realInput instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
514 514
                 return $realInput->getUid();
515 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);
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 517
             }
518 518
         } elseif (is_bool($input)) {
519 519
             return $input === true ? 1 : 0;
@@ -558,9 +558,9 @@  discard block
 block discarded – undo
558 558
             $operator = $this->resolveOperator($operator);
559 559
             $constraintSQL = '';
560 560
             if ($valueFunction === null) {
561
-                $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $operator . ' ?';
561
+                $constraintSQL .= (!empty($tableName) ? $tableName.'.' : '').$columnName.' '.$operator.' ?';
562 562
             } else {
563
-                $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $operator . ' ?';
563
+                $constraintSQL .= $valueFunction.'('.(!empty($tableName) ? $tableName.'.' : '').$columnName.') '.$operator.' ?';
564 564
             }
565 565
 
566 566
             if (isset($tableName) && !empty($this->currentChildTableNameAlias)) {
@@ -590,7 +590,7 @@  discard block
 block discarded – undo
590 590
         // to determine the relation type. Example for sys_category, property path will look like "items.sys_file"
591 591
         $parts = explode('.', $propertyPath, 3);
592 592
         if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
593
-            $explodedPropertyPath[0] = $parts[0] . '.' . $parts[1];
593
+            $explodedPropertyPath[0] = $parts[0].'.'.$parts[1];
594 594
             $explodedPropertyPath[1] = $parts[2];
595 595
             $fieldName = $explodedPropertyPath[0];
596 596
         }
@@ -599,16 +599,16 @@  discard block
 block discarded – undo
599 599
         $childTableName = $table->field($fieldName)->getForeignTable();
600 600
 
601 601
         if ($childTableName === null) {
602
-            throw new Exception\InvalidRelationConfigurationException('The relation information for property "' . $fieldName . '" of class "' . $tableName . '" is missing.', 1353170925);
602
+            throw new Exception\InvalidRelationConfigurationException('The relation information for property "'.$fieldName.'" of class "'.$tableName.'" is missing.', 1353170925);
603 603
         }
604 604
 
605 605
         if ($table->field($fieldName)->hasOne()) { // includes relation "one-to-one" and "many-to-one"
606 606
             // sometimes the opposite relation is not defined. We don't want to force this config for backward compatibility reasons.
607 607
             // $parentKeyFieldName === null does the trick somehow. Before condition was if (isset($parentKeyFieldName))
608 608
             if ($table->field($fieldName)->hasRelationManyToOne() || $parentKeyFieldName === null) {
609
-                $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $fieldName . '=' . $childTableName . '.uid';
609
+                $sql['unions'][$childTableName] = 'LEFT JOIN '.$childTableName.' ON '.$tableName.'.'.$fieldName.'='.$childTableName.'.uid';
610 610
             } else {
611
-                $sql['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
611
+                $sql['unions'][$childTableName] = 'LEFT JOIN '.$childTableName.' ON '.$tableName.'.uid='.$childTableName.'.'.$parentKeyFieldName;
612 612
             }
613 613
         } elseif ($table->field($fieldName)->hasRelationManyToMany()) {
614 614
             $relationTableName = $table->field($fieldName)->getManyToManyTable();
@@ -773,11 +773,11 @@  discard block
 block discarded – undo
773 773
                     foreach ($parameter as $item) {
774 774
                         $items[] = $this->databaseHandle->fullQuoteStr($item, $tableName);
775 775
                     }
776
-                    $parameter = '(' . implode(',', $items) . ')';
776
+                    $parameter = '('.implode(',', $items).')';
777 777
                 } else {
778 778
                     $parameter = $this->databaseHandle->fullQuoteStr($parameter, $tableName);
779 779
                 }
780
-                $sqlString = substr($sqlString, 0, $markPosition) . $parameter . substr($sqlString, ($markPosition + 1));
780
+                $sqlString = substr($sqlString, 0, $markPosition).$parameter.substr($sqlString, ($markPosition + 1));
781 781
             }
782 782
             $offset = $markPosition + strlen($parameter);
783 783
         }
@@ -878,18 +878,18 @@  discard block
 block discarded – undo
878 878
         // If the table is found to have "workspace" support, add the corresponding fields in the statement.
879 879
         if (Tca::table($tableName)->hasWorkspaceSupport()) {
880 880
             if ($this->getBackendUser()->workspace === 0) {
881
-                $statement .= ' AND ' . $tableName . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
881
+                $statement .= ' AND '.$tableName.'.t3ver_state<='.new VersionState(VersionState::DEFAULT_STATE);
882 882
             } else {
883 883
                 // Show only records of live and of the current workspace
884 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 .
885
+                $statement .= ' AND ('.
886
+                    $tableName.'.t3ver_wsid=0 OR '.
887
+                    $tableName.'.t3ver_wsid='.(int)$this->getBackendUser()->workspace.
888 888
                     ')';
889 889
             }
890 890
 
891 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';
892
+            $statement .= ' AND '.$tableName.'.pid<>-1';
893 893
         }
894 894
 
895 895
         if (!$includeDeleted) {
@@ -915,25 +915,25 @@  discard block
 block discarded – undo
915 915
         if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
916 916
             if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
917 917
                 // Select all entries for the current language
918
-                $additionalWhereClause = $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getLanguageUid()) . ',-1)';
918
+                $additionalWhereClause = $tableNameOrAlias.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['languageField'].' IN ('.intval($querySettings->getLanguageUid()).',-1)';
919 919
                 // If any language is set -> get those entries which are not translated yet
920 920
                 // They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
921 921
                 if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
922 922
                     && $querySettings->getLanguageUid() > 0
923 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';
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 929
 
930 930
                     // Add delete clause to ensure all entries are loaded
931 931
                     if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
932
-                        $additionalWhereClause .= ' AND ' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
932
+                        $additionalWhereClause .= ' AND '.$tableNameOrAlias.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['delete'].'=0';
933 933
                     }
934 934
                     $additionalWhereClause .= '))';
935 935
                 }
936
-                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = '(' . $additionalWhereClause . ')';
936
+                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = '('.$additionalWhereClause.')';
937 937
             }
938 938
         }
939 939
     }
@@ -978,7 +978,7 @@  discard block
 block discarded – undo
978 978
     protected function parseLimitAndOffset($limit, $offset, array &$sql)
979 979
     {
980 980
         if ($limit !== null && $offset !== null) {
981
-            $sql['limit'] = intval($offset) . ', ' . intval($limit);
981
+            $sql['limit'] = intval($offset).', '.intval($limit);
982 982
         } elseif ($limit !== null) {
983 983
             $sql['limit'] = intval($limit);
984 984
         }
@@ -1045,10 +1045,10 @@  discard block
 block discarded – undo
1045 1045
                 && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
1046 1046
             ) {
1047 1047
                 $row = $this->databaseHandle->exec_SELECTgetSingleRow(
1048
-                    $tableName . '.*',
1048
+                    $tableName.'.*',
1049 1049
                     $tableName,
1050
-                    $tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] .
1051
-                    ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0'
1050
+                    $tableName.'.uid='.(int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']].
1051
+                    ' AND '.$tableName.'.'.$GLOBALS['TCA'][$tableName]['ctrl']['languageField'].'=0'
1052 1052
                 );
1053 1053
             }
1054 1054
         }
@@ -1107,7 +1107,7 @@  discard block
 block discarded – undo
1107 1107
         }
1108 1108
 
1109 1109
         $numberOfAliases = $this->tableNameAliases['aliasIncrement'][$tableName];
1110
-        $tableNameAlias = $tableName . $numberOfAliases;
1110
+        $tableNameAlias = $tableName.$numberOfAliases;
1111 1111
 
1112 1112
         $this->tableNameAliases['aliasIncrement'][$tableName]++;
1113 1113
         $this->tableNameAliases['aliases'][$tableNameAlias] = $tableName;
@@ -1186,7 +1186,7 @@  discard block
 block discarded – undo
1186 1186
     {
1187 1187
         $error = $this->databaseHandle->sql_error();
1188 1188
         if ($error !== '') {
1189
-            $error .= $sql ? ': ' . $sql : '';
1189
+            $error .= $sql ? ': '.$sql : '';
1190 1190
             throw new \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\SqlErrorException($error, 1247602160);
1191 1191
         }
1192 1192
     }
Please login to merge, or discard this patch.
Classes/Tca/TableService.php 1 patch
Indentation   +442 added lines, -442 removed lines patch added patch discarded remove patch
@@ -19,447 +19,447 @@
 block discarded – undo
19 19
 class TableService extends AbstractTca
20 20
 {
21 21
 
22
-    /**
23
-     * @var array
24
-     */
25
-    protected $tca;
26
-
27
-    /**
28
-     * @var array
29
-     */
30
-    protected $columnTca;
31
-
32
-    /**
33
-     * @var string
34
-     */
35
-    protected $tableName;
36
-
37
-    /**
38
-     * @var array
39
-     */
40
-    protected $instances;
41
-
42
-    /**
43
-     * @throws InvalidKeyInArrayException
44
-     * @param string $tableName
45
-     * @return \Fab\Vidi\Tca\TableService
46
-     */
47
-    public function __construct($tableName)
48
-    {
49
-        $this->tableName = $tableName;
50
-        if (empty($GLOBALS['TCA'][$this->tableName])) {
51
-            throw new InvalidKeyInArrayException(sprintf('No TCA existence for table "%s"', $this->tableName), 1356945106);
52
-        }
53
-        $this->tca = $GLOBALS['TCA'][$this->tableName]['ctrl'];
54
-        $this->columnTca = $GLOBALS['TCA'][$this->tableName]['columns'];
55
-    }
56
-
57
-    /**
58
-     * Tell whether the table has a label field.
59
-     *
60
-     * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
61
-     * @return string
62
-     */
63
-    public function hasLabelField()
64
-    {
65
-        return $this->has('label');
66
-    }
67
-
68
-    /**
69
-     * Get the label name of table name.
70
-     *
71
-     * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
72
-     * @return string
73
-     */
74
-    public function getLabelField()
75
-    {
76
-        $labelField = $this->get('label');
77
-        if (empty($labelField)) {
78
-            throw new InvalidKeyInArrayException(sprintf('No label configured for table "%s"', $this->tableName), 1385586726);
79
-        }
80
-        return $labelField;
81
-    }
82
-
83
-    /**
84
-     * Returns the translated label of the table name.
85
-     *
86
-     * @return string
87
-     */
88
-    public function getLabel()
89
-    {
90
-        $label = '';
91
-        try {
92
-            $label = LocalizationUtility::translate($this->getLabelField(), '');
93
-        } catch (\InvalidArgumentException $e) {
94
-        }
95
-        if (empty($label)) {
96
-            $label = $this->getLabelField();
97
-        }
98
-        return $label;
99
-    }
100
-
101
-    /**
102
-     * Returns the title of the table.
103
-     *
104
-     * @return string
105
-     */
106
-    public function getTitle()
107
-    {
108
-        $title = '';
109
-        try {
110
-            $title = LocalizationUtility::translate($this->get('title'), '');
111
-        } catch (\InvalidArgumentException $e) {
112
-        }
113
-        if (empty($title)) {
114
-            $title = $this->get('title');
115
-        }
116
-        return $title;
117
-    }
118
-
119
-    /**
120
-     * Return the "disabled" field.
121
-     *
122
-     * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
123
-     * @return string|null
124
-     */
125
-    public function getHiddenField()
126
-    {
127
-        $hiddenField = null;
128
-        $enableColumns = $this->get('enablecolumns');
129
-        if (is_array($enableColumns) && !empty($enableColumns['disabled'])) {
130
-            $hiddenField = $enableColumns['disabled'];
131
-        }
132
-        return $hiddenField;
133
-    }
134
-
135
-    /**
136
-     * Return the "starttime" field.
137
-     *
138
-     * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
139
-     * @return string|null
140
-     */
141
-    public function getStartTimeField()
142
-    {
143
-        $startTimeField = null;
144
-        $enableColumns = $this->get('enablecolumns');
145
-        if (is_array($enableColumns) && !empty($enableColumns['starttime'])) {
146
-            $startTimeField = $enableColumns['starttime'];
147
-        }
148
-        return $startTimeField;
149
-    }
150
-
151
-    /**
152
-     * Return the "endtime" field.
153
-     *
154
-     * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
155
-     * @return string|null
156
-     */
157
-    public function getEndTimeField()
158
-    {
159
-        $endTimeField = null;
160
-        $enableColumns = $this->get('enablecolumns');
161
-        if (is_array($enableColumns) && !empty($enableColumns['endtime'])) {
162
-            $endTimeField = $enableColumns['endtime'];
163
-        }
164
-        return $endTimeField;
165
-    }
166
-
167
-    /**
168
-     * Tells whether the table is hidden.
169
-     *
170
-     * @return bool
171
-     */
172
-    public function isHidden()
173
-    {
174
-        return isset($this->tca['hideTable']) ? $this->tca['hideTable'] : false;
175
-    }
176
-
177
-    /**
178
-     * Tells whether the table is not hidden.
179
-     *
180
-     * @return bool
181
-     */
182
-    public function isNotHidden()
183
-    {
184
-        return !$this->isHidden();
185
-    }
186
-
187
-    /**
188
-     * Get the "deleted" field for the table.
189
-     *
190
-     * @return string|null
191
-     */
192
-    public function getDeletedField()
193
-    {
194
-        return $this->get('delete');
195
-    }
196
-
197
-    /**
198
-     * Get the modification time stamp field.
199
-     *
200
-     * @return string|null
201
-     */
202
-    public function getTimeModificationField()
203
-    {
204
-        return $this->get('tstamp');
205
-    }
206
-
207
-    /**
208
-     * Get the creation time stamp field.
209
-     *
210
-     * @return string|null
211
-     */
212
-    public function getTimeCreationField()
213
-    {
214
-        return $this->get('crdate');
215
-    }
216
-
217
-    /**
218
-     * Get the language field for the table.
219
-     *
220
-     * @return string|null
221
-     */
222
-    public function getLanguageField()
223
-    {
224
-        return $this->get('languageField');
225
-    }
226
-
227
-    /**
228
-     * Get the field which points to the parent.
229
-     *
230
-     * @return string|null
231
-     */
232
-    public function getLanguageParentField()
233
-    {
234
-        return $this->get('transOrigPointerField');
235
-    }
236
-
237
-    /**
238
-     * Returns the default order in the form of a SQL segment.
239
-     *
240
-     * @return string|null
241
-     */
242
-    public function getDefaultOrderSql()
243
-    {
244
-        // "sortby" typically has "sorting" as value.
245
-        $order = $this->get('sortby') ? $this->get('sortby') . ' ASC' : $this->get('default_sortby');
246
-        return $order;
247
-    }
248
-
249
-    /**
250
-     * Returns the parsed default orderings.
251
-     * Returns array looks like array('title' => 'ASC');
252
-     *
253
-     * @return array
254
-     */
255
-    public function getDefaultOrderings()
256
-    {
257
-
258
-        // first clean up the sql segment
259
-        $defaultOrder = str_replace('ORDER BY', '', $this->getDefaultOrderSql());
260
-        $defaultOrderParts = GeneralUtility::trimExplode(',', $defaultOrder, true);
261
-
262
-        $orderings = [];
263
-        foreach ($defaultOrderParts as $defaultOrderPart) {
264
-            $parts = GeneralUtility::trimExplode(' ', $defaultOrderPart);
265
-            if (empty($parts[1])) {
266
-                $parts[1] = QueryInterface::ORDER_DESCENDING;
267
-            }
268
-            $orderings[$parts[0]] = $parts[1];
269
-        }
270
-
271
-        return $orderings;
272
-    }
273
-
274
-    /**
275
-     * Returns the searchable fields.
276
-     *
277
-     * @return string|null
278
-     */
279
-    public function getSearchFields()
280
-    {
281
-        return $this->get('searchFields');
282
-    }
283
-
284
-    /**
285
-     * Returns an array containing the field names.
286
-     *
287
-     * @return array
288
-     */
289
-    public function getFields()
290
-    {
291
-        return array_keys($this->columnTca);
292
-    }
293
-
294
-    /**
295
-     * Returns an array containing the fields and their configuration.
296
-     *
297
-     * @return array
298
-     */
299
-    public function getFieldsAndConfiguration()
300
-    {
301
-        return $this->columnTca;
302
-    }
303
-
304
-    /**
305
-     * Tell whether we have a field "sorting".
306
-     *
307
-     * @return bool
308
-     */
309
-    public function hasSortableField()
310
-    {
311
-        return $this->has('sortby');
312
-    }
313
-
314
-    /**
315
-     * Tell whether the field exists or not.
316
-     *
317
-     * @param string $fieldName
318
-     * @return bool
319
-     */
320
-    public function hasField($fieldName)
321
-    {
322
-        if ($this->isComposite($fieldName)) {
323
-            $parts = explode('.', $fieldName);
324
-            $strippedFieldName = $parts[0];
325
-            $tableName = $parts[1];
326
-
327
-            $hasField = $this->columnTca[$strippedFieldName] && isset($GLOBALS['TCA'][$tableName]);
328
-
329
-            // Continue checking that the $strippedFieldName is of type "group"
330
-            if (isset($GLOBALS['TCA'][$this->tableName]['columns'][$strippedFieldName]) && count($parts) > 2) {
331
-                $hasField = Tca::table($this->tableName)->field($strippedFieldName)->isGroup(); // Group
332
-            }
333
-        } else {
334
-            $hasField = isset($this->columnTca[$fieldName]) || in_array($fieldName, Tca::getSystemFields());
335
-        }
336
-        return $hasField;
337
-    }
338
-
339
-    /**
340
-     * Tell whether the field name contains a path, e.g. metadata.title
341
-     *
342
-     * @param string $fieldName
343
-     * @return boolean
344
-     */
345
-    public function isComposite($fieldName)
346
-    {
347
-        return strpos($fieldName, '.') > 0;
348
-    }
349
-
350
-    /**
351
-     * Tells whether the $key exists.
352
-     *
353
-     * @param string $key
354
-     * @return string
355
-     */
356
-    public function has($key)
357
-    {
358
-        return isset($this->tca[$key]);
359
-    }
360
-
361
-    /**
362
-     * Tells whether the table name has "workspace" support.
363
-     *
364
-     * @return string
365
-     */
366
-    public function hasWorkspaceSupport()
367
-    {
368
-        return isset($this->tca['versioningWS']);
369
-    }
370
-
371
-    /**
372
-     * Tells whether the table name has "language" support.
373
-     *
374
-     * @return string
375
-     */
376
-    public function hasLanguageSupport()
377
-    {
378
-        return isset($this->tca['languageField']);
379
-    }
380
-
381
-    /**
382
-     * Return configuration value given a key.
383
-     *
384
-     * @param string $key
385
-     * @return string|null
386
-     */
387
-    public function get($key)
388
-    {
389
-        return $this->has($key) ? $this->tca[$key] : null;
390
-    }
391
-
392
-    /**
393
-     * @return array
394
-     */
395
-    public function getTca()
396
-    {
397
-        return $this->tca;
398
-    }
399
-
400
-    /**
401
-     * Tell whether the current BE User has access to this field.
402
-     *
403
-     * @return bool
404
-     */
405
-    public function hasAccess()
406
-    {
407
-        $hasAccess = true;
408
-        if ($this->isBackendMode()) {
409
-            $hasAccess = $this->getBackendUser()->check('tables_modify', $this->tableName);
410
-        }
411
-        return $hasAccess;
412
-    }
413
-
414
-    /**
415
-     * @param string $fieldName
416
-     * @throws \Exception
417
-     * @return \Fab\Vidi\Tca\FieldService
418
-     */
419
-    public function field($fieldName)
420
-    {
421
-
422
-        // In case field contains items.tx_table for field type "group"
423
-        $compositeField = '';
424
-        if (strpos($fieldName, '.') !== false) {
425
-            $compositeField = $fieldName;
426
-            $fieldParts = explode('.', $compositeField, 2);
427
-            $fieldName = $fieldParts[0];
428
-
429
-            // Special when field has been instantiated without the field name and path.
430
-            if (!empty($this->instances[$fieldName])) {
431
-                /** @var FieldService $field */
432
-                $field = $this->instances[$fieldName];
433
-                $field->setCompositeField($compositeField);
434
-            }
435
-        }
436
-
437
-        // True for system fields such as uid, pid that don't necessarily have a TCA.
438
-        if (empty($this->columnTca[$fieldName]) && in_array($fieldName, Tca::getSystemFields())) {
439
-            $this->columnTca[$fieldName] = [];
440
-        } elseif (empty($this->columnTca[$fieldName])) {
441
-            $message = sprintf(
442
-                'Does the field really exist? No TCA entry found for field "%s" for table "%s"',
443
-                $fieldName,
444
-                $this->tableName
445
-            );
446
-            throw new \Exception($message, 1385554481);
447
-        }
448
-
449
-
450
-        if (empty($this->instances[$fieldName])) {
451
-
452
-            $instance = GeneralUtility::makeInstance(
453
-                'Fab\Vidi\Tca\FieldService',
454
-                $fieldName,
455
-                $this->columnTca[$fieldName],
456
-                $this->tableName,
457
-                $compositeField
458
-            );
459
-
460
-            $this->instances[$fieldName] = $instance;
461
-        }
462
-        return $this->instances[$fieldName];
463
-    }
22
+	/**
23
+	 * @var array
24
+	 */
25
+	protected $tca;
26
+
27
+	/**
28
+	 * @var array
29
+	 */
30
+	protected $columnTca;
31
+
32
+	/**
33
+	 * @var string
34
+	 */
35
+	protected $tableName;
36
+
37
+	/**
38
+	 * @var array
39
+	 */
40
+	protected $instances;
41
+
42
+	/**
43
+	 * @throws InvalidKeyInArrayException
44
+	 * @param string $tableName
45
+	 * @return \Fab\Vidi\Tca\TableService
46
+	 */
47
+	public function __construct($tableName)
48
+	{
49
+		$this->tableName = $tableName;
50
+		if (empty($GLOBALS['TCA'][$this->tableName])) {
51
+			throw new InvalidKeyInArrayException(sprintf('No TCA existence for table "%s"', $this->tableName), 1356945106);
52
+		}
53
+		$this->tca = $GLOBALS['TCA'][$this->tableName]['ctrl'];
54
+		$this->columnTca = $GLOBALS['TCA'][$this->tableName]['columns'];
55
+	}
56
+
57
+	/**
58
+	 * Tell whether the table has a label field.
59
+	 *
60
+	 * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
61
+	 * @return string
62
+	 */
63
+	public function hasLabelField()
64
+	{
65
+		return $this->has('label');
66
+	}
67
+
68
+	/**
69
+	 * Get the label name of table name.
70
+	 *
71
+	 * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
72
+	 * @return string
73
+	 */
74
+	public function getLabelField()
75
+	{
76
+		$labelField = $this->get('label');
77
+		if (empty($labelField)) {
78
+			throw new InvalidKeyInArrayException(sprintf('No label configured for table "%s"', $this->tableName), 1385586726);
79
+		}
80
+		return $labelField;
81
+	}
82
+
83
+	/**
84
+	 * Returns the translated label of the table name.
85
+	 *
86
+	 * @return string
87
+	 */
88
+	public function getLabel()
89
+	{
90
+		$label = '';
91
+		try {
92
+			$label = LocalizationUtility::translate($this->getLabelField(), '');
93
+		} catch (\InvalidArgumentException $e) {
94
+		}
95
+		if (empty($label)) {
96
+			$label = $this->getLabelField();
97
+		}
98
+		return $label;
99
+	}
100
+
101
+	/**
102
+	 * Returns the title of the table.
103
+	 *
104
+	 * @return string
105
+	 */
106
+	public function getTitle()
107
+	{
108
+		$title = '';
109
+		try {
110
+			$title = LocalizationUtility::translate($this->get('title'), '');
111
+		} catch (\InvalidArgumentException $e) {
112
+		}
113
+		if (empty($title)) {
114
+			$title = $this->get('title');
115
+		}
116
+		return $title;
117
+	}
118
+
119
+	/**
120
+	 * Return the "disabled" field.
121
+	 *
122
+	 * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
123
+	 * @return string|null
124
+	 */
125
+	public function getHiddenField()
126
+	{
127
+		$hiddenField = null;
128
+		$enableColumns = $this->get('enablecolumns');
129
+		if (is_array($enableColumns) && !empty($enableColumns['disabled'])) {
130
+			$hiddenField = $enableColumns['disabled'];
131
+		}
132
+		return $hiddenField;
133
+	}
134
+
135
+	/**
136
+	 * Return the "starttime" field.
137
+	 *
138
+	 * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
139
+	 * @return string|null
140
+	 */
141
+	public function getStartTimeField()
142
+	{
143
+		$startTimeField = null;
144
+		$enableColumns = $this->get('enablecolumns');
145
+		if (is_array($enableColumns) && !empty($enableColumns['starttime'])) {
146
+			$startTimeField = $enableColumns['starttime'];
147
+		}
148
+		return $startTimeField;
149
+	}
150
+
151
+	/**
152
+	 * Return the "endtime" field.
153
+	 *
154
+	 * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
155
+	 * @return string|null
156
+	 */
157
+	public function getEndTimeField()
158
+	{
159
+		$endTimeField = null;
160
+		$enableColumns = $this->get('enablecolumns');
161
+		if (is_array($enableColumns) && !empty($enableColumns['endtime'])) {
162
+			$endTimeField = $enableColumns['endtime'];
163
+		}
164
+		return $endTimeField;
165
+	}
166
+
167
+	/**
168
+	 * Tells whether the table is hidden.
169
+	 *
170
+	 * @return bool
171
+	 */
172
+	public function isHidden()
173
+	{
174
+		return isset($this->tca['hideTable']) ? $this->tca['hideTable'] : false;
175
+	}
176
+
177
+	/**
178
+	 * Tells whether the table is not hidden.
179
+	 *
180
+	 * @return bool
181
+	 */
182
+	public function isNotHidden()
183
+	{
184
+		return !$this->isHidden();
185
+	}
186
+
187
+	/**
188
+	 * Get the "deleted" field for the table.
189
+	 *
190
+	 * @return string|null
191
+	 */
192
+	public function getDeletedField()
193
+	{
194
+		return $this->get('delete');
195
+	}
196
+
197
+	/**
198
+	 * Get the modification time stamp field.
199
+	 *
200
+	 * @return string|null
201
+	 */
202
+	public function getTimeModificationField()
203
+	{
204
+		return $this->get('tstamp');
205
+	}
206
+
207
+	/**
208
+	 * Get the creation time stamp field.
209
+	 *
210
+	 * @return string|null
211
+	 */
212
+	public function getTimeCreationField()
213
+	{
214
+		return $this->get('crdate');
215
+	}
216
+
217
+	/**
218
+	 * Get the language field for the table.
219
+	 *
220
+	 * @return string|null
221
+	 */
222
+	public function getLanguageField()
223
+	{
224
+		return $this->get('languageField');
225
+	}
226
+
227
+	/**
228
+	 * Get the field which points to the parent.
229
+	 *
230
+	 * @return string|null
231
+	 */
232
+	public function getLanguageParentField()
233
+	{
234
+		return $this->get('transOrigPointerField');
235
+	}
236
+
237
+	/**
238
+	 * Returns the default order in the form of a SQL segment.
239
+	 *
240
+	 * @return string|null
241
+	 */
242
+	public function getDefaultOrderSql()
243
+	{
244
+		// "sortby" typically has "sorting" as value.
245
+		$order = $this->get('sortby') ? $this->get('sortby') . ' ASC' : $this->get('default_sortby');
246
+		return $order;
247
+	}
248
+
249
+	/**
250
+	 * Returns the parsed default orderings.
251
+	 * Returns array looks like array('title' => 'ASC');
252
+	 *
253
+	 * @return array
254
+	 */
255
+	public function getDefaultOrderings()
256
+	{
257
+
258
+		// first clean up the sql segment
259
+		$defaultOrder = str_replace('ORDER BY', '', $this->getDefaultOrderSql());
260
+		$defaultOrderParts = GeneralUtility::trimExplode(',', $defaultOrder, true);
261
+
262
+		$orderings = [];
263
+		foreach ($defaultOrderParts as $defaultOrderPart) {
264
+			$parts = GeneralUtility::trimExplode(' ', $defaultOrderPart);
265
+			if (empty($parts[1])) {
266
+				$parts[1] = QueryInterface::ORDER_DESCENDING;
267
+			}
268
+			$orderings[$parts[0]] = $parts[1];
269
+		}
270
+
271
+		return $orderings;
272
+	}
273
+
274
+	/**
275
+	 * Returns the searchable fields.
276
+	 *
277
+	 * @return string|null
278
+	 */
279
+	public function getSearchFields()
280
+	{
281
+		return $this->get('searchFields');
282
+	}
283
+
284
+	/**
285
+	 * Returns an array containing the field names.
286
+	 *
287
+	 * @return array
288
+	 */
289
+	public function getFields()
290
+	{
291
+		return array_keys($this->columnTca);
292
+	}
293
+
294
+	/**
295
+	 * Returns an array containing the fields and their configuration.
296
+	 *
297
+	 * @return array
298
+	 */
299
+	public function getFieldsAndConfiguration()
300
+	{
301
+		return $this->columnTca;
302
+	}
303
+
304
+	/**
305
+	 * Tell whether we have a field "sorting".
306
+	 *
307
+	 * @return bool
308
+	 */
309
+	public function hasSortableField()
310
+	{
311
+		return $this->has('sortby');
312
+	}
313
+
314
+	/**
315
+	 * Tell whether the field exists or not.
316
+	 *
317
+	 * @param string $fieldName
318
+	 * @return bool
319
+	 */
320
+	public function hasField($fieldName)
321
+	{
322
+		if ($this->isComposite($fieldName)) {
323
+			$parts = explode('.', $fieldName);
324
+			$strippedFieldName = $parts[0];
325
+			$tableName = $parts[1];
326
+
327
+			$hasField = $this->columnTca[$strippedFieldName] && isset($GLOBALS['TCA'][$tableName]);
328
+
329
+			// Continue checking that the $strippedFieldName is of type "group"
330
+			if (isset($GLOBALS['TCA'][$this->tableName]['columns'][$strippedFieldName]) && count($parts) > 2) {
331
+				$hasField = Tca::table($this->tableName)->field($strippedFieldName)->isGroup(); // Group
332
+			}
333
+		} else {
334
+			$hasField = isset($this->columnTca[$fieldName]) || in_array($fieldName, Tca::getSystemFields());
335
+		}
336
+		return $hasField;
337
+	}
338
+
339
+	/**
340
+	 * Tell whether the field name contains a path, e.g. metadata.title
341
+	 *
342
+	 * @param string $fieldName
343
+	 * @return boolean
344
+	 */
345
+	public function isComposite($fieldName)
346
+	{
347
+		return strpos($fieldName, '.') > 0;
348
+	}
349
+
350
+	/**
351
+	 * Tells whether the $key exists.
352
+	 *
353
+	 * @param string $key
354
+	 * @return string
355
+	 */
356
+	public function has($key)
357
+	{
358
+		return isset($this->tca[$key]);
359
+	}
360
+
361
+	/**
362
+	 * Tells whether the table name has "workspace" support.
363
+	 *
364
+	 * @return string
365
+	 */
366
+	public function hasWorkspaceSupport()
367
+	{
368
+		return isset($this->tca['versioningWS']);
369
+	}
370
+
371
+	/**
372
+	 * Tells whether the table name has "language" support.
373
+	 *
374
+	 * @return string
375
+	 */
376
+	public function hasLanguageSupport()
377
+	{
378
+		return isset($this->tca['languageField']);
379
+	}
380
+
381
+	/**
382
+	 * Return configuration value given a key.
383
+	 *
384
+	 * @param string $key
385
+	 * @return string|null
386
+	 */
387
+	public function get($key)
388
+	{
389
+		return $this->has($key) ? $this->tca[$key] : null;
390
+	}
391
+
392
+	/**
393
+	 * @return array
394
+	 */
395
+	public function getTca()
396
+	{
397
+		return $this->tca;
398
+	}
399
+
400
+	/**
401
+	 * Tell whether the current BE User has access to this field.
402
+	 *
403
+	 * @return bool
404
+	 */
405
+	public function hasAccess()
406
+	{
407
+		$hasAccess = true;
408
+		if ($this->isBackendMode()) {
409
+			$hasAccess = $this->getBackendUser()->check('tables_modify', $this->tableName);
410
+		}
411
+		return $hasAccess;
412
+	}
413
+
414
+	/**
415
+	 * @param string $fieldName
416
+	 * @throws \Exception
417
+	 * @return \Fab\Vidi\Tca\FieldService
418
+	 */
419
+	public function field($fieldName)
420
+	{
421
+
422
+		// In case field contains items.tx_table for field type "group"
423
+		$compositeField = '';
424
+		if (strpos($fieldName, '.') !== false) {
425
+			$compositeField = $fieldName;
426
+			$fieldParts = explode('.', $compositeField, 2);
427
+			$fieldName = $fieldParts[0];
428
+
429
+			// Special when field has been instantiated without the field name and path.
430
+			if (!empty($this->instances[$fieldName])) {
431
+				/** @var FieldService $field */
432
+				$field = $this->instances[$fieldName];
433
+				$field->setCompositeField($compositeField);
434
+			}
435
+		}
436
+
437
+		// True for system fields such as uid, pid that don't necessarily have a TCA.
438
+		if (empty($this->columnTca[$fieldName]) && in_array($fieldName, Tca::getSystemFields())) {
439
+			$this->columnTca[$fieldName] = [];
440
+		} elseif (empty($this->columnTca[$fieldName])) {
441
+			$message = sprintf(
442
+				'Does the field really exist? No TCA entry found for field "%s" for table "%s"',
443
+				$fieldName,
444
+				$this->tableName
445
+			);
446
+			throw new \Exception($message, 1385554481);
447
+		}
448
+
449
+
450
+		if (empty($this->instances[$fieldName])) {
451
+
452
+			$instance = GeneralUtility::makeInstance(
453
+				'Fab\Vidi\Tca\FieldService',
454
+				$fieldName,
455
+				$this->columnTca[$fieldName],
456
+				$this->tableName,
457
+				$compositeField
458
+			);
459
+
460
+			$this->instances[$fieldName] = $instance;
461
+		}
462
+		return $this->instances[$fieldName];
463
+	}
464 464
 
465 465
 }
Please login to merge, or discard this patch.