Completed
Push — master ( dfc64f...43826b )
by Fabien
51:13
created

VidiDbBackend::addAdditionalWhereClause()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
3
namespace Fab\Vidi\Persistence\Storage;
4
5
/*
6
 * This file is part of the Fab/Vidi project under GPLv2 or later.
7
 *
8
 * For the full copyright and license information, please read the
9
 * LICENSE.md file that was distributed with this source code.
10
 */
11
12
use Fab\Vidi\Persistence\Query;
13
use Fab\Vidi\Utility\BackendUtility;
14
use TYPO3\CMS\Core\Database\Connection;
15
use TYPO3\CMS\Core\Database\ConnectionPool;
16
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
17
use TYPO3\CMS\Core\Utility\GeneralUtility;
18
use TYPO3\CMS\Core\Versioning\VersionState;
19
use TYPO3\CMS\Extbase\Persistence\Generic\Exception;
20
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface;
21
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface;
22
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\DynamicOperandInterface;
23
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface;
24
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\LowerCaseInterface;
25
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\PropertyValueInterface;
26
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface;
27
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface;
28
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\UpperCaseInterface;
29
use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;
30
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
31
use TYPO3\CMS\Frontend\Page\PageRepository;
32
use Fab\Vidi\Tca\Tca;
33
34
/**
35
 * A Storage backend
36
 */
37
class VidiDbBackend
38
{
39
40
    const OPERATOR_EQUAL_TO_NULL = 'operatorEqualToNull';
41
    const OPERATOR_NOT_EQUAL_TO_NULL = 'operatorNotEqualToNull';
42
43
    /**
44
     * The TYPO3 page repository. Used for language and workspace overlay
45
     *
46
     * @var PageRepository
47
     */
48
    protected $pageRepository;
49
50
    /**
51
     * @var \TYPO3\CMS\Extbase\Service\EnvironmentService
52
     * @inject
53
     */
54
    protected $environmentService;
55
56
    /**
57
     * @var \Fab\Vidi\Persistence\Query
58
     */
59
    protected $query;
60
61
    /**
62
     * Store some info related to table name and its aliases.
63
     *
64
     * @var array
65
     */
66
    protected $tableNameAliases = array(
67
        'aliases' => [],
68
        'aliasIncrement' => [],
69
    );
70
71
    /**
72
     * Use to store the current foreign table name alias.
73
     *
74
     * @var string
75
     */
76
    protected $currentChildTableNameAlias = '';
77
78
    /**
79
     * @param Query $query
80
     */
81
    public function __construct(Query $query)
82
    {
83
        $this->query = $query;
84
    }
85
86
    /**
87
     * Returns the result of the query
88
     */
89
    public function fetchResult()
90
    {
91
        $parameters = [];
92
        $statementParts = $this->parseQuery($parameters);
93
        $statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
94
        $sql = $this->buildQuery($statementParts);
95
        //print $sql; exit();
96
97
        $rows = $this->getConnection()
98
            ->executeQuery($sql, $parameters)
99
            ->fetchAll();
100
101
        return $this->getContentObjects($rows);
102
    }
103
104
    /**
105
     * Returns the number of tuples matching the query.
106
     *
107
     * @return int The number of matching tuples
108
     */
109
    public function countResult()
110
    {
111
        $parameters = [];
112
        $statementParts = $this->parseQuery($parameters);
113
        $statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
114
115
        // if limit is set, we need to count the rows "manually" as COUNT(*) ignores LIMIT constraints
116
        if (!empty($statementParts['limit'])) {
117
            $sql = $this->buildQuery($statementParts);
118
119
            $count = $this
120
                ->getConnection()
121
                ->executeQuery($sql, $parameters)
122
                ->rowCount();
123
        } else {
124
            $statementParts['fields'] = array('COUNT(*)');
125
            // having orderings without grouping is not compatible with non-MySQL DBMS
126
            $statementParts['orderings'] = [];
127
            if (isset($statementParts['keywords']['distinct'])) {
128
                unset($statementParts['keywords']['distinct']);
129
                $distinctField = $this->query->getDistinct() ? $this->query->getDistinct() : 'uid';
130
                $statementParts['fields'] = array('COUNT(DISTINCT ' . $statementParts['mainTable'] . '.' . $distinctField . ')');
131
            }
132
133
            $sql = $this->buildQuery($statementParts);
134
            $count = $this
135
                ->getConnection()
136
                ->executeQuery($sql, $parameters)
137
                ->fetchColumn(0);
138
        }
139
        return (int)$count;
140
    }
141
142
    /**
143
     * Parses the query and returns the SQL statement parts.
144
     *
145
     * @param array &$parameters
146
     * @return array
147
     */
148
    public function parseQuery(array &$parameters)
149
    {
150
        $statementParts = [];
151
        $statementParts['keywords'] = [];
152
        $statementParts['tables'] = [];
153
        $statementParts['unions'] = [];
154
        $statementParts['fields'] = [];
155
        $statementParts['where'] = [];
156
        $statementParts['additionalWhereClause'] = [];
157
        $statementParts['orderings'] = [];
158
        $statementParts['limit'] = [];
159
        $query = $this->query;
160
        $source = $query->getSource();
161
        $this->parseSource($source, $statementParts);
162
        $this->parseConstraint($query->getConstraint(), $source, $statementParts, $parameters);
163
        $this->parseOrderings($query->getOrderings(), $source, $statementParts);
164
        $this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $statementParts);
165
        $tableNames = array_unique(array_keys($statementParts['tables'] + $statementParts['unions']));
166
        foreach ($tableNames as $tableNameOrAlias) {
167
            if (is_string($tableNameOrAlias) && strlen($tableNameOrAlias) > 0) {
168
                $this->addAdditionalWhereClause($query->getQuerySettings(), $tableNameOrAlias, $statementParts);
169
            }
170
        }
171
172
        return $statementParts;
173
    }
174
175
    /**
176
     * Fiddle with the statement structure to handle recursive MM relations.
177
     * For the recursive MM query to work, we must invert some values.
178
     * Let see if that is the best way of doing that...
179
     *
180
     * @param array $statementParts
181
     * @return array
182
     */
183
    public function processStatementStructureForRecursiveMMRelation(array $statementParts)
184
    {
185
186
        if ($this->hasRecursiveMMRelation()) {
187
            $tableName = $this->query->getType();
188
189
            // In order the MM query to work for a recursive MM query, we must invert some values.
190
            // tx_domain_model_foo0 (the alias) <--> tx_domain_model_foo (the origin table name)
191
            $values = [];
192 View Code Duplication
            foreach ($statementParts['fields'] as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
                $values[$key] = str_replace($tableName, $tableName . '0', $value);
194
            }
195
            $statementParts['fields'] = $values;
196
197
            // Same comment as above.
198
            $values = [];
199 View Code Duplication
            foreach ($statementParts['where'] as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
                $values[$key] = str_replace($tableName . '0', $tableName, $value);
201
            }
202
            $statementParts['where'] = $values;
203
204
            // We must be more restrictive by transforming the "left" union by "inner"
205
            $values = [];
206
            foreach ($statementParts['unions'] as $key => $value) {
207
                $values[$key] = str_replace('LEFT JOIN', 'INNER JOIN', $value);
208
            }
209
            $statementParts['unions'] = $values;
210
        }
211
212
        return $statementParts;
213
    }
214
215
    /**
216
     * Tell whether there is a recursive MM relation.
217
     *
218
     * @return bool
219
     */
220
    public function hasRecursiveMMRelation()
221
    {
222
        return isset($this->tableNameAliases['aliasIncrement'][$this->query->getType()]);
223
224
    }
225
226
    /**
227
     * Returns the statement, ready to be executed.
228
     *
229
     * @param array $statementParts The SQL statement parts
230
     * @return string The SQL statement
231
     */
232
    public function buildQuery(array $statementParts)
233
    {
234
235
        // Add more statement to the UNION part.
236
        if (!empty($statementParts['unions'])) {
237
            foreach ($statementParts['unions'] as $tableName => $unionPart) {
238
                if (!empty($statementParts['additionalWhereClause'][$tableName])) {
239
                    $statementParts['unions'][$tableName] .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
240
                }
241
            }
242
        }
243
244
        $statement = 'SELECT ' . implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']) . ' FROM ' . implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']);
245
        if (!empty($statementParts['where'])) {
246
            $statement .= ' WHERE ' . implode('', $statementParts['where']);
247 View Code Duplication
            if (!empty($statementParts['additionalWhereClause'][$this->query->getType()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
                $statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
249
            }
250 View Code Duplication
        } elseif (!empty($statementParts['additionalWhereClause'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
            $statement .= ' WHERE ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
252
        }
253
        if (!empty($statementParts['orderings'])) {
254
            $statement .= ' ORDER BY ' . implode(', ', $statementParts['orderings']);
255
        }
256
        if (!empty($statementParts['limit'])) {
257
            $statement .= ' LIMIT ' . $statementParts['limit'];
258
        }
259
260
        return $statement;
261
    }
262
263
    /**
264
     * Transforms a Query Source into SQL and parameter arrays
265
     *
266
     * @param SourceInterface $source The source
267
     * @param array &$sql
268
     * @return void
269
     */
270
    protected function parseSource(SourceInterface $source, array &$sql)
0 ignored issues
show
Unused Code introduced by
The parameter $source is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
271
    {
272
        $tableName = $this->getTableName();
273
        $sql['fields'][$tableName] = $tableName . '.*';
274
        if ($this->query->getDistinct()) {
275
            $sql['fields'][$tableName] = $tableName . '.' . $this->query->getDistinct();
276
            $sql['keywords']['distinct'] = 'DISTINCT';
277
        }
278
        $sql['tables'][$tableName] = $tableName;
279
        $sql['mainTable'] = $tableName;
280
    }
281
282
    /**
283
     * Transforms a constraint into SQL and parameter arrays
284
     *
285
     * @param ConstraintInterface $constraint The constraint
286
     * @param SourceInterface $source The source
287
     * @param array &$statementParts The query parts
288
     * @param array &$parameters The parameters that will replace the markers
289
     * @return void
290
     */
291
    protected function parseConstraint(ConstraintInterface $constraint = null, SourceInterface $source, array &$statementParts, array &$parameters)
292
    {
293
        if ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...eneric\Qom\AndInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
294
            $statementParts['where'][] = '(';
295
            $this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
296
            $statementParts['where'][] = ' AND ';
297
            $this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
298
            $statementParts['where'][] = ')';
299
        } elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...Generic\Qom\OrInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
300
            $statementParts['where'][] = '(';
301
            $this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
302
            $statementParts['where'][] = ' OR ';
303
            $this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
304
            $statementParts['where'][] = ')';
305
        } elseif ($constraint instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...eneric\Qom\NotInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
306
            $statementParts['where'][] = 'NOT (';
307
            $this->parseConstraint($constraint->getConstraint(), $source, $statementParts, $parameters);
308
            $statementParts['where'][] = ')';
309
        } elseif ($constraint instanceof ComparisonInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...Qom\ComparisonInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
310
            $this->parseComparison($constraint, $source, $statementParts, $parameters);
311
        }
312
    }
313
314
    /**
315
     * Parse a Comparison into SQL and parameter arrays.
316
     *
317
     * @param ComparisonInterface $comparison The comparison to parse
318
     * @param SourceInterface $source The source
319
     * @param array &$statementParts SQL query parts to add to
320
     * @param array &$parameters Parameters to bind to the SQL
321
     * @return void
322
     * @throws Exception\RepositoryException
323
     */
324
    protected function parseComparison(ComparisonInterface $comparison, SourceInterface $source, array &$statementParts, array &$parameters)
325
    {
326
        $operand1 = $comparison->getOperand1();
327
        $operator = $comparison->getOperator();
328
        $operand2 = $comparison->getOperand2();
329
        if ($operator === QueryInterface::OPERATOR_IN) {
330
            $items = [];
331
            $hasValue = false;
332
            foreach ($operand2 as $value) {
333
                $value = $this->getPlainValue($value);
334
                if ($value !== null) {
335
                    $items[] = $value;
336
                    $hasValue = true;
337
                }
338
            }
339
            if ($hasValue === false) {
340
                $statementParts['where'][] = '1<>1';
341
            } else {
342
                $this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters, null);
343
                $parameters[] = $items;
344
            }
345
        } elseif ($operator === QueryInterface::OPERATOR_CONTAINS) {
346
            if ($operand2 === null) {
347
                $statementParts['where'][] = '1<>1';
348
            } else {
349
                throw new \Exception('Not implemented! Contact extension author.', 1412931227);
350
                # @todo re-implement me if necessary.
351
                #$tableName = $this->query->getType();
352
                #$propertyName = $operand1->getPropertyName();
353
                #while (strpos($propertyName, '.') !== false) {
354
                #	$this->addUnionStatement($tableName, $propertyName, $statementParts);
355
                #}
356
                #$columnName = $propertyName;
357
                #$columnMap = $propertyName;
358
                #$typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
359
                #if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
360
                #	$relationTableName = $columnMap->getRelationTableName();
361
                #	$statementParts['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=?)';
362
                #	$parameters[] = intval($this->getPlainValue($operand2));
363
                #} elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
364
                #	$parentKeyFieldName = $columnMap->getParentKeyFieldName();
365
                #	if (isset($parentKeyFieldName)) {
366
                #		$childTableName = $columnMap->getChildTableName();
367
                #		$statementParts['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=?)';
368
                #		$parameters[] = intval($this->getPlainValue($operand2));
369
                #	} else {
370
                #		$statementParts['where'][] = 'FIND_IN_SET(?,' . $tableName . '.' . $columnName . ')';
371
                #		$parameters[] = intval($this->getPlainValue($operand2));
372
                #	}
373
                #} else {
374
                #	throw new Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
375
                #}
376
            }
377
        } else {
378
            if ($operand2 === null) {
379
                if ($operator === QueryInterface::OPERATOR_EQUAL_TO) {
380
                    $operator = self::OPERATOR_EQUAL_TO_NULL;
381
                } elseif ($operator === QueryInterface::OPERATOR_NOT_EQUAL_TO) {
382
                    $operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
383
                }
384
            }
385
            $this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters);
386
            $parameters[] = $this->getPlainValue($operand2);
387
        }
388
    }
389
390
    /**
391
     * Returns a plain value, i.e. objects are flattened if possible.
392
     *
393
     * @param mixed $input
394
     * @return mixed
395
     * @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException
396
     */
397
    protected function getPlainValue($input)
398
    {
399
        if (is_array($input)) {
400
            throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An array could not be converted to a plain value.', 1274799932);
401
        }
402
        if ($input instanceof \DateTime) {
403
            return $input->format('U');
404
        } elseif (is_object($input)) {
405
            if ($input instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...eneric\LazyLoadingProxy does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
406
                $realInput = $input->_loadRealInstance();
407
            } else {
408
                $realInput = $input;
409
            }
410
            if ($realInput instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Domain...t\DomainObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
411
                return $realInput->getUid();
412
            } else {
413
                throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnexpectedTypeException('An object of class "' . get_class($realInput) . '" could not be converted to a plain value.', 1274799934);
414
            }
415
        } elseif (is_bool($input)) {
416
            return $input === true ? 1 : 0;
417
        } else {
418
            return $input;
419
        }
420
    }
421
422
    /**
423
     * Parse a DynamicOperand into SQL and parameter arrays.
424
     *
425
     * @param DynamicOperandInterface $operand
426
     * @param string $operator One of the JCR_OPERATOR_* constants
427
     * @param SourceInterface $source The source
428
     * @param array &$statementParts The query parts
429
     * @param array &$parameters The parameters that will replace the markers
430
     * @param string $valueFunction an optional SQL function to apply to the operand value
431
     * @return void
432
     */
433
    protected function parseDynamicOperand(DynamicOperandInterface $operand, $operator, SourceInterface $source, array &$statementParts, array &$parameters, $valueFunction = null)
434
    {
435
        if ($operand instanceof LowerCaseInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...\Qom\LowerCaseInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
436
            $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'LOWER');
437
        } elseif ($operand instanceof UpperCaseInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...\Qom\UpperCaseInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
438
            $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'UPPER');
439
        } elseif ($operand instanceof PropertyValueInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...\PropertyValueInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
440
            $propertyName = $operand->getPropertyName();
441
442
            // Reset value.
443
            $this->currentChildTableNameAlias = '';
444
445
            if ($source instanceof SelectorInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...c\Qom\SelectorInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
446
                $tableName = $this->query->getType();
447
                while (strpos($propertyName, '.') !== false) {
448
                    $this->addUnionStatement($tableName, $propertyName, $statementParts);
449
                }
450
            } elseif ($source instanceof JoinInterface) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Extbase\Persis...neric\Qom\JoinInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
451
                $tableName = $source->getJoinCondition()->getSelector1Name();
452
            }
453
454
            $columnName = $propertyName;
455
            $resolvedOperator = $this->resolveOperator($operator);
456
            $constraintSQL = '';
457
458
            $marker = $operator === QueryInterface::OPERATOR_IN
459
                ? '(?)'
460
                : '?';
461
462
            if ($valueFunction === null) {
463
                $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $resolvedOperator . ' ' . $marker;
464
            } else {
465
                $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $resolvedOperator . ' ' . $marker;
466
            }
467
468
            if (isset($tableName) && !empty($this->currentChildTableNameAlias)) {
469
                $constraintSQL = $this->replaceTableNameByAlias($tableName, $this->currentChildTableNameAlias, $constraintSQL);
470
            }
471
            $statementParts['where'][] = $constraintSQL;
472
        }
473
    }
474
475
    /**
476
     * @param string &$tableName
477
     * @param string &$propertyPath
478
     * @param array &$statementParts
479
     */
480
    protected function addUnionStatement(&$tableName, &$propertyPath, array &$statementParts)
481
    {
482
483
        $table = Tca::table($tableName);
484
485
        $explodedPropertyPath = explode('.', $propertyPath, 2);
486
        $fieldName = $explodedPropertyPath[0];
487
488
        // Field of type "group" are special because property path must contain the table name
489
        // to determine the relation type. Example for sys_category, property path will look like "items.sys_file"
490
        $parts = explode('.', $propertyPath, 3);
491
        if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
492
            $explodedPropertyPath[0] = $parts[0] . '.' . $parts[1];
493
            $explodedPropertyPath[1] = $parts[2];
494
            $fieldName = $explodedPropertyPath[0];
495
        }
496
497
        $parentKeyFieldName = $table->field($fieldName)->getForeignField();
498
        $childTableName = $table->field($fieldName)->getForeignTable();
499
500
        if ($childTableName === null) {
501
            throw new Exception\InvalidRelationConfigurationException('The relation information for property "' . $fieldName . '" of class "' . $tableName . '" is missing.', 1353170925);
502
        }
503
504
        if ($table->field($fieldName)->hasOne()) { // includes relation "one-to-one" and "many-to-one"
505
            // sometimes the opposite relation is not defined. We don't want to force this config for backward compatibility reasons.
506
            // $parentKeyFieldName === null does the trick somehow. Before condition was if (isset($parentKeyFieldName))
507
            if ($table->field($fieldName)->hasRelationManyToOne() || $parentKeyFieldName === null) {
508
                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $fieldName . '=' . $childTableName . '.uid';
509
            } else {
510
                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
511
            }
512
        } elseif ($table->field($fieldName)->hasRelationManyToMany()) {
513
            $relationTableName = $table->field($fieldName)->getManyToManyTable();
514
515
            $parentKeyFieldName = $table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
516
            $childKeyFieldName = !$table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
517
518
            // MM table e.g sys_category_record_mm
519
            $relationTableNameAlias = $this->generateAlias($relationTableName);
520
            $join = sprintf(
521
                'LEFT JOIN %s AS %s ON %s.uid=%s.%s', $relationTableName,
522
                $relationTableNameAlias,
523
                $tableName,
524
                $relationTableNameAlias,
525
                $parentKeyFieldName
526
            );
527
            $statementParts['unions'][$relationTableNameAlias] = $join;
528
529
            // Foreign table e.g sys_category
530
            $childTableNameAlias = $this->generateAlias($childTableName);
531
            $this->currentChildTableNameAlias = $childTableNameAlias;
532
            $join = sprintf(
533
                'LEFT JOIN %s AS %s ON %s.%s=%s.uid',
534
                $childTableName,
535
                $childTableNameAlias,
536
                $relationTableNameAlias,
537
                $childKeyFieldName,
538
                $childTableNameAlias
539
            );
540
            $statementParts['unions'][$childTableNameAlias] = $join;
541
542
            // Find a possible table name for a MM condition.
543
            $tableNameCondition = $table->field($fieldName)->getAdditionalTableNameCondition();
544
            if ($tableNameCondition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tableNameCondition of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
545
546
                // If we can find a source file name,  we can then retrieve more MM conditions from the TCA such as a field name.
547
                $sourceFileName = $this->query->getSourceFieldName();
548
                if (empty($sourceFileName)) {
549
                    $additionalMMConditions = array(
550
                        'tablenames' => $tableNameCondition,
551
                    );
552
                } else {
553
                    $additionalMMConditions = Tca::table($tableNameCondition)->field($sourceFileName)->getAdditionalMMCondition();
554
                }
555
556
                foreach ($additionalMMConditions as $additionalFieldName => $additionalMMCondition) {
557
                    $additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
558
                    $statementParts['unions'][$relationTableNameAlias] .= $additionalJoin;
559
560
                    $additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
561
                    $statementParts['unions'][$childTableNameAlias] .= $additionalJoin;
562
                }
563
            }
564
565
        } elseif ($table->field($fieldName)->hasMany()) { // includes relations "many-to-one" and "csv" relations
566
            $childTableNameAlias = $this->generateAlias($childTableName);
567
            $this->currentChildTableNameAlias = $childTableNameAlias;
568
569
            if (isset($parentKeyFieldName)) {
570
                $join = sprintf(
571
                    'LEFT JOIN %s AS %s ON %s.uid=%s.%s',
572
                    $childTableName,
573
                    $childTableNameAlias,
574
                    $tableName,
575
                    $childTableNameAlias,
576
                    $parentKeyFieldName
577
                );
578
                $statementParts['unions'][$childTableNameAlias] = $join;
579
            } else {
580
                $join = sprintf(
581
                    'LEFT JOIN %s AS %s ON (FIND_IN_SET(%s.uid, %s.%s))',
582
                    $childTableName,
583
                    $childTableNameAlias,
584
                    $childTableNameAlias,
585
                    $tableName,
586
                    $fieldName
587
                );
588
                $statementParts['unions'][$childTableNameAlias] = $join;
589
            }
590
        } else {
591
            throw new Exception('Could not determine type of relation.', 1252502725);
592
        }
593
594
        $statementParts['keywords']['distinct'] = 'DISTINCT';
595
        $propertyPath = $explodedPropertyPath[1];
596
        $tableName = $childTableName;
597
    }
598
599
    /**
600
     * Returns the SQL operator for the given JCR operator type.
601
     *
602
     * @param string $operator One of the JCR_OPERATOR_* constants
603
     * @return string an SQL operator
604
     * @throws Exception
605
     */
606
    protected function resolveOperator($operator)
607
    {
608
        switch ($operator) {
609
            case self::OPERATOR_EQUAL_TO_NULL:
610
                $operator = 'IS';
611
                break;
612
            case self::OPERATOR_NOT_EQUAL_TO_NULL:
613
                $operator = 'IS NOT';
614
                break;
615
            case QueryInterface::OPERATOR_IN:
616
                $operator = 'IN';
617
                break;
618
            case QueryInterface::OPERATOR_EQUAL_TO:
619
                $operator = '=';
620
                break;
621
            case QueryInterface::OPERATOR_NOT_EQUAL_TO:
622
                $operator = '!=';
623
                break;
624
            case QueryInterface::OPERATOR_LESS_THAN:
625
                $operator = '<';
626
                break;
627
            case QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO:
628
                $operator = '<=';
629
                break;
630
            case QueryInterface::OPERATOR_GREATER_THAN:
631
                $operator = '>';
632
                break;
633
            case QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO:
634
                $operator = '>=';
635
                break;
636
            case QueryInterface::OPERATOR_LIKE:
637
                $operator = 'LIKE';
638
                break;
639
            default:
640
                throw new Exception('Unsupported operator encountered.', 1242816073);
641
        }
642
        return $operator;
643
    }
644
645
    /**
646
     * Adds additional WHERE statements according to the query settings.
647
     *
648
     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
649
     * @param string $tableNameOrAlias The table name to add the additional where clause for
650
     * @param array &$statementParts
651
     * @return void
652
     */
653
    protected function addAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableNameOrAlias, &$statementParts)
654
    {
655
        $this->addVisibilityConstraintStatement($querySettings, $tableNameOrAlias, $statementParts);
656
        if ($querySettings->getRespectSysLanguage()) {
657
            $this->addSysLanguageStatement($tableNameOrAlias, $statementParts, $querySettings);
658
        }
659
    }
660
661
    /**
662
     * Adds enableFields and deletedClause to the query if necessary
663
     *
664
     * @param QuerySettingsInterface $querySettings
665
     * @param string $tableNameOrAlias The database table name
666
     * @param array &$statementParts The query parts
667
     * @return void
668
     */
669
    protected function addVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableNameOrAlias, array &$statementParts)
670
    {
671
        $statement = '';
672
        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
673
        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
674
            $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
675
            $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
676
            $includeDeleted = $querySettings->getIncludeDeleted();
677
            if ($this->environmentService->isEnvironmentInFrontendMode()) {
678
                $statement .= $this->getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
679
            } else {
680
                // TYPO3_MODE === 'BE'
681
                $statement .= $this->getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted);
682
            }
683
684
            // Remove the prefixing "AND" if any.
685
            if (!empty($statement)) {
686
                $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
687
                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = $statement;
688
            }
689
        }
690
    }
691
692
    /**
693
     * Returns constraint statement for frontend context
694
     *
695
     * @param string $tableNameOrAlias
696
     * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
697
     * @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.
698
     * @param boolean $includeDeleted A flag indicating whether deleted records should be included
699
     * @return string
700
     * @throws Exception\InconsistentQuerySettingsException
701
     */
702
    protected function getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted)
703
    {
704
        $statement = '';
705
        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
706
        if ($ignoreEnableFields && !$includeDeleted) {
707
            if (count($enableFieldsToBeIgnored)) {
708
                // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
709
                $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
710
            } else {
711
                $statement .= $this->getPageRepository()->deleteClause($tableName);
712
            }
713
        } elseif (!$ignoreEnableFields && !$includeDeleted) {
714
            $statement .= $this->getPageRepository()->enableFields($tableName);
715
        } elseif (!$ignoreEnableFields && $includeDeleted) {
716
            throw new Exception\InconsistentQuerySettingsException('Query setting "ignoreEnableFields=false" can not be used together with "includeDeleted=true" in frontend context.', 1327678173);
717
        }
718
        return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
719
    }
720
721
    /**
722
     * Returns constraint statement for backend context
723
     *
724
     * @param string $tableNameOrAlias
725
     * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
726
     * @param boolean $includeDeleted A flag indicating whether deleted records should be included
727
     * @return string
728
     */
729
    protected function getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted)
730
    {
731
        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
732
        $statement = '';
733
        if (!$ignoreEnableFields) {
734
            $statement .= BackendUtility::BEenableFields($tableName);
735
        }
736
737
        // If the table is found to have "workspace" support, add the corresponding fields in the statement.
738
        if (Tca::table($tableName)->hasWorkspaceSupport()) {
739
            if ($this->getBackendUser()->workspace === 0) {
740
                $statement .= ' AND ' . $tableName . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
741
            } else {
742
                // Show only records of live and of the current workspace
743
                // In case we are in a Versioning preview
744
                $statement .= ' AND (' .
745
                    $tableName . '.t3ver_wsid=0 OR ' .
746
                    $tableName . '.t3ver_wsid=' . (int)$this->getBackendUser()->workspace .
747
                    ')';
748
            }
749
750
            // Check if this segment make sense here or whether it should be in the "if" part when we have workspace = 0
751
            $statement .= ' AND ' . $tableName . '.pid<>-1';
752
        }
753
754
        if (!$includeDeleted) {
755
            $statement .= BackendUtility::deleteClause($tableName);
756
        }
757
758
        return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
759
    }
760
761
    /**
762
     * Builds the language field statement
763
     *
764
     * @param string $tableNameOrAlias The database table name
765
     * @param array &$statementParts The query parts
766
     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
767
     * @return void
768
     * @throws Exception
769
     */
770
    protected function addSysLanguageStatement($tableNameOrAlias, array &$statementParts, $querySettings)
771
    {
772
        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
773
        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
774
            if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
775
                // Select all entries for the current language
776
                $additionalWhereClause = $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getLanguageUid()) . ',-1)';
777
                // If any language is set -> get those entries which are not translated yet
778
                // They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
779
                if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
780
                    && $querySettings->getLanguageUid() > 0
781
                ) {
782
                    $additionalWhereClause .= ' OR (' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
783
                        ' AND ' . $tableNameOrAlias . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
784
                        ' FROM ' . $tableName .
785
                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
786
                        ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
787
788
                    // Add delete clause to ensure all entries are loaded
789
                    if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
790
                        $additionalWhereClause .= ' AND ' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
791
                    }
792
                    $additionalWhereClause .= '))';
793
                }
794
                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = '(' . $additionalWhereClause . ')';
795
            }
796
        }
797
    }
798
799
    /**
800
     * Transforms orderings into SQL.
801
     *
802
     * @param array $orderings An array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
803
     * @param SourceInterface $source The source
804
     * @param array &$statementParts The query parts
805
     * @return void
806
     * @throws Exception\UnsupportedOrderException
807
     */
808
    protected function parseOrderings(array $orderings, SourceInterface $source, array &$statementParts)
0 ignored issues
show
Unused Code introduced by
The parameter $source is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
809
    {
810
        foreach ($orderings as $fieldNameAndPath => $order) {
811
            switch ($order) {
812
                case QueryInterface::ORDER_ASCENDING:
813
                    $order = 'ASC';
814
                    break;
815
                case QueryInterface::ORDER_DESCENDING:
816
                    $order = 'DESC';
817
                    break;
818
                default:
819
                    throw new Exception\UnsupportedOrderException('Unsupported order encountered.', 1456845126);
820
            }
821
822
            $tableName = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->query->getType());
823
            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $tableName);
824
            $statementParts['orderings'][] = sprintf('%s.%s %s', $tableName, $fieldName, $order);
825
        }
826
    }
827
828
    /**
829
     * Transforms limit and offset into SQL
830
     *
831
     * @param int $limit
832
     * @param int $offset
833
     * @param array &$statementParts
834
     * @return void
835
     */
836
    protected function parseLimitAndOffset($limit, $offset, array &$statementParts)
837
    {
838
        if ($limit !== null && $offset !== null) {
839
            $statementParts['limit'] = intval($offset) . ', ' . intval($limit);
840
        } elseif ($limit !== null) {
841
            $statementParts['limit'] = intval($limit);
842
        }
843
    }
844
845
    /**
846
     * @param array $rows
847
     * @return array
848
     */
849
    protected function getContentObjects(array $rows): array
850
    {
851
        $contentObjects = [];
852
        foreach ($rows as $row) {
853
854
            // Get language uid from querySettings.
855
            // Ensure the backend handling is not broken (fallback to Get parameter 'L' if needed)
856
            $overlaidRow = $this->doLanguageAndWorkspaceOverlay(
857
                $row,
858
                $this->query->getQuerySettings()
859
            );
860
861
            $contentObjects[] = GeneralUtility::makeInstance(
862
                \Fab\Vidi\Domain\Model\Content::class,
863
                $this->query->getType(),
864
                $overlaidRow
865
            );
866
        }
867
868
        return $contentObjects;
869
    }
870
871
    /**
872
     * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
873
     * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
874
     *
875
     * @param array $row
876
     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
877
     * @return array
878
     */
879
    protected function doLanguageAndWorkspaceOverlay(array $row, $querySettings)
880
    {
881
        $tableName = $this->getTableName();
882
883
        $pageRepository = $this->getPageRepository();
884
        if (is_object($GLOBALS['TSFE'])) {
885
            $languageMode = $GLOBALS['TSFE']->sys_language_mode;
886
            if ($this->isBackendUserLogged() && $this->getBackendUser()->workspace !== 0) {
887
                $pageRepository->versioningWorkspaceId = $this->getBackendUser()->workspace;
888
            }
889
        } else {
890
            $languageMode = '';
891
            $workspaceUid = $this->getBackendUser()->workspace;
892
            $pageRepository->versioningWorkspaceId = $workspaceUid;
893
            if ($this->getBackendUser()->workspace !== 0) {
894
                $pageRepository->versioningPreview = 1;
895
            }
896
        }
897
898
        // If current row is a translation select its parent
899
        if (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
900
            && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
901
        ) {
902
            if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
903
                && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
904
            ) {
905
                $queryBuilder = $this->getQueryBuilder();
906
                $row = $queryBuilder
907
                    ->select($tableName . '.*')
908
                    ->from($tableName)
909
                    ->andWhere(
910
                        $tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
911
                        $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = 0'
912
                    )
913
                    ->execute()
914
                    ->fetch();
915
            }
916
        }
917
918
        // Retrieve the original uid; Used for Workspaces!
919
        if (TYPO3_MODE !== 'BE') {
920
            $pageRepository->versionOL($tableName, $row, true, true);
921
        } else {
922
            \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($tableName, $row);
923
        }
924
        if ($pageRepository->versioningPreview && isset($row['_ORIG_uid'])) {
925
            $row['uid'] = $row['_ORIG_uid'];
926
        }
927
928
        // Special case for table "pages"
929
        if ($tableName == 'pages') {
930
            $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
931
        } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
932
            && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
933
        ) {
934
            if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], array(-1, 0))) {
935
                $overlayMode = $languageMode === 'strict' ? 'hideNonTranslated' : '';
936
                $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
937
            }
938
        }
939
940
        return $row;
941
    }
942
943
    /**
944
     * Return a resolved table name given a possible table name alias.
945
     *
946
     * @param string $tableNameOrAlias
947
     * @return string
948
     */
949
    protected function resolveTableNameAlias($tableNameOrAlias)
950
    {
951
        $resolvedTableName = $tableNameOrAlias;
952
        if (!empty($this->tableNameAliases['aliases'][$tableNameOrAlias])) {
953
            $resolvedTableName = $this->tableNameAliases['aliases'][$tableNameOrAlias];
954
        }
955
        return $resolvedTableName;
956
    }
957
958
    /**
959
     * Generate a unique table name alias for the given table name.
960
     *
961
     * @param string $tableName
962
     * @return string
963
     */
964
    protected function generateAlias($tableName)
965
    {
966
967
        if (!isset($this->tableNameAliases['aliasIncrement'][$tableName])) {
968
            $this->tableNameAliases['aliasIncrement'][$tableName] = 0;
969
        }
970
971
        $numberOfAliases = $this->tableNameAliases['aliasIncrement'][$tableName];
972
        $tableNameAlias = $tableName . $numberOfAliases;
973
974
        $this->tableNameAliases['aliasIncrement'][$tableName]++;
975
        $this->tableNameAliases['aliases'][$tableNameAlias] = $tableName;
976
977
        return $tableNameAlias;
978
    }
979
980
    /**
981
     * Replace the table names by its table name alias within the given statement.
982
     *
983
     * @param string $tableName
984
     * @param string $tableNameAlias
985
     * @param string $statement
986
     * @return string
987
     */
988
    protected function replaceTableNameByAlias($tableName, $tableNameAlias, $statement)
989
    {
990
        if ($statement && $tableName !== $tableNameAlias) {
991
            $statement = str_replace($tableName, $tableNameAlias, $statement);
992
        }
993
        return $statement;
994
    }
995
996
    /**
997
     * Returns an instance of the current Backend User.
998
     *
999
     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1000
     */
1001
    protected function getBackendUser()
1002
    {
1003
        return $GLOBALS['BE_USER'];
1004
    }
1005
1006
    /**
1007
     * Tell whether a Backend User is logged in.
1008
     *
1009
     * @return bool
1010
     */
1011
    protected function isBackendUserLogged()
1012
    {
1013
        return is_object($GLOBALS['BE_USER']);
1014
    }
1015
1016
    /**
1017
     * @return PageRepository|object
1018
     */
1019
    protected function getPageRepository()
1020
    {
1021
        if (!$this->pageRepository instanceof PageRepository) {
0 ignored issues
show
Bug introduced by
The class TYPO3\CMS\Frontend\Page\PageRepository does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1022
            if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
1023
                $this->pageRepository = $GLOBALS['TSFE']->sys_page;
1024
            } else {
1025
                $this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
1026
            }
1027
        }
1028
1029
        return $this->pageRepository;
1030
    }
1031
1032
    /**
1033
     * @return \Fab\Vidi\Resolver\FieldPathResolver|object
1034
     */
1035
    protected function getFieldPathResolver()
1036
    {
1037
        return GeneralUtility::makeInstance(\Fab\Vidi\Resolver\FieldPathResolver::class);
1038
    }
1039
1040
    /**
1041
     * @return object|Connection
1042
     */
1043
    protected function getConnection(): Connection
1044
    {
1045
        /** @var ConnectionPool $connectionPool */
1046
        return GeneralUtility::makeInstance(ConnectionPool::class)
1047
            ->getConnectionForTable($this->getTableName());
1048
    }
1049
1050
    /**
1051
     * @return object|QueryBuilder
1052
     */
1053
    protected function getQueryBuilder(): QueryBuilder
1054
    {
1055
        /** @var ConnectionPool $connectionPool */
1056
        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1057
        return $connectionPool->getQueryBuilderForTable($this->getTableName());
1058
    }
1059
1060
    /**
1061
     * @return string
1062
     */
1063
    public function getTableName(): string
1064
    {
1065
        return $this->query->getSource()->getNodeTypeName(); // getSelectorName()
1066
    }
1067
1068
}
1069