|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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()])) { |
|
|
|
|
|
|
248
|
|
|
$statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]); |
|
249
|
|
|
} |
|
250
|
|
View Code Duplication |
} elseif (!empty($statementParts['additionalWhereClause'])) { |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
306
|
|
|
$statementParts['where'][] = 'NOT ('; |
|
307
|
|
|
$this->parseConstraint($constraint->getConstraint(), $source, $statementParts, $parameters); |
|
308
|
|
|
$statementParts['where'][] = ')'; |
|
309
|
|
|
} elseif ($constraint instanceof ComparisonInterface) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
406
|
|
|
$realInput = $input->_loadRealInstance(); |
|
407
|
|
|
} else { |
|
408
|
|
|
$realInput = $input; |
|
409
|
|
|
} |
|
410
|
|
|
if ($realInput instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
436
|
|
|
$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'LOWER'); |
|
437
|
|
|
} elseif ($operand instanceof UpperCaseInterface) { |
|
|
|
|
|
|
438
|
|
|
$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'UPPER'); |
|
439
|
|
|
} elseif ($operand instanceof PropertyValueInterface) { |
|
|
|
|
|
|
440
|
|
|
$propertyName = $operand->getPropertyName(); |
|
441
|
|
|
|
|
442
|
|
|
// Reset value. |
|
443
|
|
|
$this->currentChildTableNameAlias = ''; |
|
444
|
|
|
|
|
445
|
|
|
if ($source instanceof SelectorInterface) { |
|
|
|
|
|
|
446
|
|
|
$tableName = $this->query->getType(); |
|
447
|
|
|
while (strpos($propertyName, '.') !== false) { |
|
448
|
|
|
$this->addUnionStatement($tableName, $propertyName, $statementParts); |
|
449
|
|
|
} |
|
450
|
|
|
} elseif ($source instanceof JoinInterface) { |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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) |
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
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
|
|
|
|
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.