|
1
|
|
|
<?php |
|
2
|
|
|
namespace Fab\Vidi\Domain\Repository; |
|
3
|
|
|
|
|
4
|
|
|
/** |
|
5
|
|
|
* This file is part of the TYPO3 CMS project. |
|
6
|
|
|
* |
|
7
|
|
|
* It is free software; you can redistribute it and/or modify it under |
|
8
|
|
|
* the terms of the GNU General Public License, either version 2 |
|
9
|
|
|
* of the License, or any later version. |
|
10
|
|
|
* |
|
11
|
|
|
* For the full copyright and license information, please read the |
|
12
|
|
|
* LICENSE.txt file that was distributed with this source code. |
|
13
|
|
|
* |
|
14
|
|
|
* The TYPO3 project - inspiring people to share! |
|
15
|
|
|
*/ |
|
16
|
|
|
|
|
17
|
|
|
use Fab\Vidi\DataHandler\DataHandlerFactory; |
|
18
|
|
|
use Fab\Vidi\Domain\Validator\ContentValidator; |
|
19
|
|
|
use Fab\Vidi\Domain\Validator\LanguageValidator; |
|
20
|
|
|
use Fab\Vidi\Persistence\QuerySettings; |
|
21
|
|
|
use Fab\Vidi\Resolver\FieldPathResolver; |
|
22
|
|
|
use TYPO3\CMS\Core\DataHandling\DataHandler; |
|
23
|
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility; |
|
24
|
|
|
use TYPO3\CMS\Core\Utility\MathUtility; |
|
25
|
|
|
use TYPO3\CMS\Extbase\Object\ObjectManager; |
|
26
|
|
|
use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedMethodException; |
|
27
|
|
|
use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface; |
|
28
|
|
|
use TYPO3\CMS\Extbase\Persistence\RepositoryInterface; |
|
29
|
|
|
use Fab\Vidi\Converter\Property; |
|
30
|
|
|
use Fab\Vidi\DataHandler\ProcessAction; |
|
31
|
|
|
use Fab\Vidi\Domain\Model\Content; |
|
32
|
|
|
use Fab\Vidi\Persistence\Matcher; |
|
33
|
|
|
use Fab\Vidi\Persistence\Order; |
|
34
|
|
|
use Fab\Vidi\Persistence\Query; |
|
35
|
|
|
use Fab\Vidi\Tca\Tca; |
|
36
|
|
|
use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Repository for accessing Content |
|
40
|
|
|
*/ |
|
41
|
|
|
class ContentRepository implements RepositoryInterface |
|
42
|
|
|
{ |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Tell whether it is a raw result (array) or object being returned. |
|
46
|
|
|
* |
|
47
|
|
|
* @var bool |
|
48
|
|
|
*/ |
|
49
|
|
|
protected $rawResult = false; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* The data type to be returned, e.g fe_users, fe_groups, tt_content, etc... |
|
53
|
|
|
* |
|
54
|
|
|
* @var string |
|
55
|
|
|
*/ |
|
56
|
|
|
protected $dataType; |
|
57
|
|
|
|
|
58
|
|
|
/** |
|
59
|
|
|
* The source field is useful in the context of MM relations to know who is the caller |
|
60
|
|
|
* e.g findByItems which eventually corresponds to a field name. |
|
61
|
|
|
* |
|
62
|
|
|
* @var string |
|
63
|
|
|
*/ |
|
64
|
|
|
protected $sourceFieldName = ''; |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* @var array |
|
68
|
|
|
*/ |
|
69
|
|
|
protected $errorMessages = array(); |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* @var QuerySettingsInterface |
|
73
|
|
|
*/ |
|
74
|
|
|
protected $defaultQuerySettings; |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* Constructor |
|
78
|
|
|
* |
|
79
|
|
|
* @param string $dataType |
|
80
|
|
|
*/ |
|
81
|
|
|
public function __construct($dataType) |
|
82
|
|
|
{ |
|
83
|
|
|
$this->dataType = $dataType; |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
/** |
|
87
|
|
|
* Returns all objects of this repository. |
|
88
|
|
|
* |
|
89
|
|
|
* @return Content[] |
|
90
|
|
|
*/ |
|
91
|
|
|
public function findAll() |
|
92
|
|
|
{ |
|
93
|
|
|
$query = $this->createQuery(); |
|
94
|
|
|
return $query->execute(); |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
/** |
|
98
|
|
|
* Returns all "distinct" values for a given property. |
|
99
|
|
|
* |
|
100
|
|
|
* @param string $propertyName |
|
101
|
|
|
* @param Matcher $matcher |
|
102
|
|
|
* @return Content[] |
|
103
|
|
|
*/ |
|
104
|
|
View Code Duplication |
public function findDistinctValues($propertyName, Matcher $matcher = null) |
|
|
|
|
|
|
105
|
|
|
{ |
|
106
|
|
|
$query = $this->createQuery(); |
|
107
|
|
|
$query->setDistinct($propertyName); |
|
108
|
|
|
|
|
109
|
|
|
// Remove empty values from selection. |
|
110
|
|
|
$constraint = $query->logicalNot($query->equals($propertyName, '')); |
|
111
|
|
|
|
|
112
|
|
|
// Add some additional constraints from the Matcher object. |
|
113
|
|
|
$matcherConstraint = null; |
|
114
|
|
|
if (!is_null($matcher)) { |
|
115
|
|
|
$matcherConstraint = $this->computeConstraints($query, $matcher); |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
// Assemble the final constraints or not. |
|
119
|
|
|
if ($matcherConstraint) { |
|
120
|
|
|
$query->logicalAnd($matcherConstraint, $constraint); |
|
121
|
|
|
$query->matching($query->logicalAnd($matcherConstraint, $constraint)); |
|
122
|
|
|
} else { |
|
123
|
|
|
$query->matching($constraint); |
|
124
|
|
|
} |
|
125
|
|
|
|
|
126
|
|
|
return $query->execute(); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
/** |
|
130
|
|
|
* Returns all "distinct" values for a given property. |
|
131
|
|
|
* |
|
132
|
|
|
* @param string $propertyName |
|
133
|
|
|
* @param Matcher $matcher |
|
134
|
|
|
* @return int |
|
135
|
|
|
*/ |
|
136
|
|
View Code Duplication |
public function countDistinctValues($propertyName, Matcher $matcher = null) |
|
|
|
|
|
|
137
|
|
|
{ |
|
138
|
|
|
$query = $this->createQuery(); |
|
139
|
|
|
$query->setDistinct($propertyName); |
|
140
|
|
|
|
|
141
|
|
|
// Remove empty values from selection. |
|
142
|
|
|
$constraint = $query->logicalNot($query->equals($propertyName, '')); |
|
143
|
|
|
|
|
144
|
|
|
// Add some additional constraints from the Matcher object. |
|
145
|
|
|
$matcherConstraint = null; |
|
146
|
|
|
if (!is_null($matcher)) { |
|
147
|
|
|
$matcherConstraint = $this->computeConstraints($query, $matcher); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
// Assemble the final constraints or not. |
|
151
|
|
|
if ($matcherConstraint) { |
|
152
|
|
|
$query->logicalAnd($matcherConstraint, $constraint); |
|
153
|
|
|
$query->matching($query->logicalAnd($matcherConstraint, $constraint)); |
|
154
|
|
|
} else { |
|
155
|
|
|
$query->matching($constraint); |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
return $query->count(); |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* Finds an object matching the given identifier. |
|
163
|
|
|
* |
|
164
|
|
|
* @param int $uid The identifier of the object to find |
|
165
|
|
|
* @return Content|null |
|
166
|
|
|
* @api |
|
167
|
|
|
*/ |
|
168
|
|
|
public function findByUid($uid) |
|
169
|
|
|
{ |
|
170
|
|
|
return $this->findByIdentifier($uid); |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
/** |
|
174
|
|
|
* Finds all Contents given specified matches. |
|
175
|
|
|
* |
|
176
|
|
|
* @param string $propertyName |
|
177
|
|
|
* @param array $values |
|
178
|
|
|
* @return Content[] |
|
179
|
|
|
*/ |
|
180
|
|
|
public function findIn($propertyName, array $values) |
|
181
|
|
|
{ |
|
182
|
|
|
$query = $this->createQuery(); |
|
183
|
|
|
$query->matching($query->in($propertyName, $values)); |
|
184
|
|
|
return $query->execute(); |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
/** |
|
188
|
|
|
* Finds all Contents given specified matches. |
|
189
|
|
|
* |
|
190
|
|
|
* @param Matcher $matcher |
|
191
|
|
|
* @param Order $order The order |
|
192
|
|
|
* @param int $limit |
|
193
|
|
|
* @param int $offset |
|
194
|
|
|
* @return Content[] |
|
195
|
|
|
*/ |
|
196
|
|
|
public function findBy(Matcher $matcher, Order $order = null, $limit = null, $offset = null) |
|
197
|
|
|
{ |
|
198
|
|
|
|
|
199
|
|
|
$query = $this->createQuery(); |
|
200
|
|
|
|
|
201
|
|
|
$limit = (int)$limit; // make sure to cast |
|
|
|
|
|
|
202
|
|
|
if ($limit > 0) { |
|
203
|
|
|
$query->setLimit($limit); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
if ($order) { |
|
207
|
|
|
$query->setOrderings($order->getOrderings()); |
|
208
|
|
|
|
|
209
|
|
|
// Loops around the orderings adding if necessary a dummy condition |
|
210
|
|
|
// to make sure the relations can be resolved when transforming the query to plain SQL. |
|
211
|
|
|
foreach ($order->getOrderings() as $ordering => $direction) { |
|
212
|
|
|
if ($this->hasForeignRelationIn($ordering)) { |
|
213
|
|
|
$relationalField = $this->getForeignRelationFrom($ordering); |
|
214
|
|
|
$matcher->like($relationalField . '.uid', ''); |
|
215
|
|
|
} |
|
216
|
|
|
} |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
if ($offset) { |
|
|
|
|
|
|
220
|
|
|
$query->setOffset($offset); |
|
221
|
|
|
} |
|
222
|
|
|
|
|
223
|
|
|
$constraints = $this->computeConstraints($query, $matcher); |
|
224
|
|
|
|
|
225
|
|
|
if ($constraints) { |
|
226
|
|
|
$query->matching($constraints); |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
return $query->execute(); |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
/** |
|
233
|
|
|
* Find one Content object given specified matches. |
|
234
|
|
|
* |
|
235
|
|
|
* @param Matcher $matcher |
|
236
|
|
|
* @return Content |
|
237
|
|
|
*/ |
|
238
|
|
|
public function findOneBy(Matcher $matcher) |
|
239
|
|
|
{ |
|
240
|
|
|
|
|
241
|
|
|
$query = $this->createQuery(); |
|
242
|
|
|
|
|
243
|
|
|
$constraints = $this->computeConstraints($query, $matcher); |
|
244
|
|
|
|
|
245
|
|
|
if ($constraints) { |
|
246
|
|
|
$query->matching($constraints); |
|
247
|
|
|
} |
|
248
|
|
|
|
|
249
|
|
|
$query->setLimit(1); // only take one! |
|
250
|
|
|
|
|
251
|
|
|
$resultSet = $query->execute(); |
|
252
|
|
|
if ($resultSet) { |
|
|
|
|
|
|
253
|
|
|
$resultSet = current($resultSet); |
|
254
|
|
|
} |
|
255
|
|
|
return $resultSet; |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
/** |
|
259
|
|
|
* Count all Contents given specified matches. |
|
260
|
|
|
* |
|
261
|
|
|
* @param Matcher $matcher |
|
262
|
|
|
* @return int |
|
263
|
|
|
*/ |
|
264
|
|
|
public function countBy(Matcher $matcher) |
|
265
|
|
|
{ |
|
266
|
|
|
|
|
267
|
|
|
$query = $this->createQuery(); |
|
268
|
|
|
|
|
269
|
|
|
$constraints = $this->computeConstraints($query, $matcher); |
|
270
|
|
|
|
|
271
|
|
|
if ($constraints) { |
|
272
|
|
|
$query->matching($constraints); |
|
273
|
|
|
} |
|
274
|
|
|
|
|
275
|
|
|
return $query->count(); |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
/** |
|
279
|
|
|
* Update a content with new information. |
|
280
|
|
|
* |
|
281
|
|
|
* @param Content $content |
|
282
|
|
|
* @param $language |
|
283
|
|
|
* @return bool |
|
284
|
|
|
*/ |
|
285
|
|
View Code Duplication |
public function localize($content, $language) |
|
|
|
|
|
|
286
|
|
|
{ |
|
287
|
|
|
|
|
288
|
|
|
// Security check |
|
289
|
|
|
$this->getContentValidator()->validate($content); |
|
290
|
|
|
$this->getLanguageValidator()->validate($language); |
|
291
|
|
|
|
|
292
|
|
|
$dataType = $content->getDataType(); |
|
293
|
|
|
$handler = $this->getDataHandlerFactory()->action(ProcessAction::LOCALIZE)->forType($dataType)->getDataHandler(); |
|
294
|
|
|
|
|
295
|
|
|
$handlerResult = $handler->processLocalize($content, $language); |
|
296
|
|
|
$this->errorMessages = $handler->getErrorMessages(); |
|
297
|
|
|
return $handlerResult; |
|
298
|
|
|
} |
|
299
|
|
|
|
|
300
|
|
|
/** |
|
301
|
|
|
* Update a content with new information. |
|
302
|
|
|
* |
|
303
|
|
|
* @param Content $content |
|
304
|
|
|
* @return bool |
|
305
|
|
|
*/ |
|
306
|
|
|
public function update($content) |
|
307
|
|
|
{ |
|
308
|
|
|
|
|
309
|
|
|
// Security check. |
|
310
|
|
|
$this->getContentValidator()->validate($content); |
|
311
|
|
|
|
|
312
|
|
|
$dataType = $content->getDataType(); |
|
313
|
|
|
$handler = $this->getDataHandlerFactory()->action(ProcessAction::UPDATE)->forType($dataType)->getDataHandler(); |
|
314
|
|
|
|
|
315
|
|
|
$handlerResult = $handler->processUpdate($content); |
|
316
|
|
|
$this->errorMessages = $handler->getErrorMessages(); |
|
317
|
|
|
return $handlerResult; |
|
318
|
|
|
} |
|
319
|
|
|
|
|
320
|
|
|
/** |
|
321
|
|
|
* Removes an object from this repository. |
|
322
|
|
|
* |
|
323
|
|
|
* @param Content $content |
|
324
|
|
|
* @return boolean |
|
325
|
|
|
*/ |
|
326
|
|
|
public function remove($content) |
|
327
|
|
|
{ |
|
328
|
|
|
$dataType = $content->getDataType(); |
|
329
|
|
|
$handler = $this->getDataHandlerFactory()->action(ProcessAction::REMOVE)->forType($dataType)->getDataHandler(); |
|
330
|
|
|
|
|
331
|
|
|
$handlerResult = $handler->processRemove($content); |
|
332
|
|
|
$this->errorMessages = $handler->getErrorMessages(); |
|
333
|
|
|
return $handlerResult; |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
/** |
|
337
|
|
|
* Move a content within this repository. |
|
338
|
|
|
* The $target corresponds to the pid to move the records to. |
|
339
|
|
|
* It can also be a negative value in case of sorting. The negative value would be the uid of its predecessor. |
|
340
|
|
|
* |
|
341
|
|
|
* @param Content $content |
|
342
|
|
|
* @param string $target |
|
343
|
|
|
* @return bool |
|
344
|
|
|
*/ |
|
345
|
|
View Code Duplication |
public function move($content, $target) |
|
|
|
|
|
|
346
|
|
|
{ |
|
347
|
|
|
|
|
348
|
|
|
// Security check. |
|
349
|
|
|
$this->getContentValidator()->validate($content); |
|
350
|
|
|
|
|
351
|
|
|
$dataType = $content->getDataType(); |
|
352
|
|
|
$handler = $this->getDataHandlerFactory()->action(ProcessAction::MOVE)->forType($dataType)->getDataHandler(); |
|
353
|
|
|
|
|
354
|
|
|
$handlerResult = $handler->processMove($content, $target); |
|
355
|
|
|
$this->errorMessages = $handler->getErrorMessages(); |
|
356
|
|
|
return $handlerResult; |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
/** |
|
360
|
|
|
* Copy a content within this repository. |
|
361
|
|
|
* |
|
362
|
|
|
* @param Content $content |
|
363
|
|
|
* @return bool |
|
364
|
|
|
*/ |
|
365
|
|
View Code Duplication |
public function copy($content, $target) |
|
|
|
|
|
|
366
|
|
|
{ |
|
367
|
|
|
|
|
368
|
|
|
// Security check. |
|
369
|
|
|
$this->getContentValidator()->validate($content); |
|
370
|
|
|
|
|
371
|
|
|
$dataType = $content->getDataType(); |
|
372
|
|
|
$handler = $this->getDataHandlerFactory()->action(ProcessAction::COPY)->forType($dataType)->getDataHandler(); |
|
373
|
|
|
|
|
374
|
|
|
$handlerResult = $handler->processCopy($content, $target); |
|
375
|
|
|
$this->errorMessages = $handler->getErrorMessages(); |
|
376
|
|
|
return $handlerResult; |
|
377
|
|
|
} |
|
378
|
|
|
|
|
379
|
|
|
/** |
|
380
|
|
|
* Adds an object to this repository. |
|
381
|
|
|
* |
|
382
|
|
|
* @param object $object The object to add |
|
383
|
|
|
* @throws \BadMethodCallException |
|
384
|
|
|
* @return void |
|
385
|
|
|
* @api |
|
386
|
|
|
*/ |
|
387
|
|
|
public function add($object) |
|
388
|
|
|
{ |
|
389
|
|
|
throw new \BadMethodCallException('Repository does not support the add() method.', 1375805599); |
|
390
|
|
|
} |
|
391
|
|
|
|
|
392
|
|
|
/** |
|
393
|
|
|
* Returns the total number objects of this repository. |
|
394
|
|
|
* |
|
395
|
|
|
* @return integer The object count |
|
396
|
|
|
* @api |
|
397
|
|
|
*/ |
|
398
|
|
|
public function countAll() |
|
399
|
|
|
{ |
|
400
|
|
|
$query = $this->createQuery(); |
|
401
|
|
|
return $query->count(); |
|
402
|
|
|
} |
|
403
|
|
|
|
|
404
|
|
|
/** |
|
405
|
|
|
* Removes all objects of this repository as if remove() was called for |
|
406
|
|
|
* all of them. |
|
407
|
|
|
* |
|
408
|
|
|
* @return void |
|
409
|
|
|
* @api |
|
410
|
|
|
*/ |
|
411
|
|
|
public function removeAll() |
|
412
|
|
|
{ |
|
413
|
|
|
// TODO: Implement removeAll() method. |
|
414
|
|
|
} |
|
415
|
|
|
|
|
416
|
|
|
/** |
|
417
|
|
|
* Finds an object matching the given identifier. |
|
418
|
|
|
* |
|
419
|
|
|
* @param mixed $identifier The identifier of the object to find |
|
420
|
|
|
* @return Content|null |
|
421
|
|
|
* @api |
|
422
|
|
|
*/ |
|
423
|
|
|
public function findByIdentifier($identifier) |
|
424
|
|
|
{ |
|
425
|
|
|
$query = $this->createQuery(); |
|
426
|
|
|
|
|
427
|
|
|
$result = $query->matching($query->equals('uid', $identifier)) |
|
428
|
|
|
->execute(); |
|
429
|
|
|
|
|
430
|
|
|
if (is_array($result)) { |
|
431
|
|
|
$result = current($result); |
|
432
|
|
|
} |
|
433
|
|
|
|
|
434
|
|
|
return $result; |
|
435
|
|
|
} |
|
436
|
|
|
|
|
437
|
|
|
/** |
|
438
|
|
|
* Dispatches magic methods (findBy[Property]()) |
|
439
|
|
|
* |
|
440
|
|
|
* @param string $methodName The name of the magic method |
|
441
|
|
|
* @param string $arguments The arguments of the magic method |
|
442
|
|
|
* @throws UnsupportedMethodException |
|
443
|
|
|
* @return mixed |
|
444
|
|
|
* @api |
|
445
|
|
|
*/ |
|
446
|
|
|
public function __call($methodName, $arguments) |
|
447
|
|
|
{ |
|
448
|
|
|
if (substr($methodName, 0, 6) === 'findBy' && strlen($methodName) > 7) { |
|
449
|
|
|
$propertyName = strtolower(substr(substr($methodName, 6), 0, 1)) . substr(substr($methodName, 6), 1); |
|
450
|
|
|
$result = $this->processMagicCall($propertyName, $arguments[0]); |
|
451
|
|
View Code Duplication |
} elseif (substr($methodName, 0, 9) === 'findOneBy' && strlen($methodName) > 10) { |
|
|
|
|
|
|
452
|
|
|
$propertyName = strtolower(substr(substr($methodName, 9), 0, 1)) . substr(substr($methodName, 9), 1); |
|
453
|
|
|
$result = $this->processMagicCall($propertyName, $arguments[0], 'one'); |
|
454
|
|
|
} elseif (substr($methodName, 0, 7) === 'countBy' && strlen($methodName) > 8) { |
|
455
|
|
|
$propertyName = strtolower(substr(substr($methodName, 7), 0, 1)) . substr(substr($methodName, 7), 1); |
|
456
|
|
|
$result = $this->processMagicCall($propertyName, $arguments[0], 'count'); |
|
457
|
|
|
} else { |
|
458
|
|
|
throw new UnsupportedMethodException('The method "' . $methodName . '" is not supported by the repository.', 1360838010); |
|
459
|
|
|
} |
|
460
|
|
|
return $result; |
|
461
|
|
|
} |
|
462
|
|
|
|
|
463
|
|
|
/** |
|
464
|
|
|
* Returns a query for objects of this repository |
|
465
|
|
|
* |
|
466
|
|
|
* @return Query |
|
467
|
|
|
* @api |
|
468
|
|
|
*/ |
|
469
|
|
|
public function createQuery() |
|
470
|
|
|
{ |
|
471
|
|
|
/** @var Query $query */ |
|
472
|
|
|
$query = $this->getObjectManager()->get(Query::class, $this->dataType); |
|
473
|
|
|
$query->setSourceFieldName($this->sourceFieldName); |
|
474
|
|
|
|
|
475
|
|
|
if ($this->defaultQuerySettings) { |
|
476
|
|
|
$query->setQuerySettings($this->defaultQuerySettings); |
|
477
|
|
|
} else { |
|
478
|
|
|
|
|
479
|
|
|
// Initialize and pass the query settings at this level. |
|
480
|
|
|
/** @var QuerySettings $querySettings */ |
|
481
|
|
|
$querySettings = $this->getObjectManager()->get(QuerySettings::class); |
|
482
|
|
|
|
|
483
|
|
|
// Default choice for the BE. |
|
484
|
|
|
if ($this->isBackendMode()) { |
|
485
|
|
|
$querySettings->setIgnoreEnableFields(true); |
|
486
|
|
|
} |
|
487
|
|
|
|
|
488
|
|
|
$query->setQuerySettings($querySettings); |
|
489
|
|
|
} |
|
490
|
|
|
|
|
491
|
|
|
return $query; |
|
492
|
|
|
} |
|
493
|
|
|
|
|
494
|
|
|
/** |
|
495
|
|
|
* Sets the property names to order the result by per default. |
|
496
|
|
|
* Expected like this: |
|
497
|
|
|
* array( |
|
498
|
|
|
* 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING, |
|
499
|
|
|
* 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING |
|
500
|
|
|
* ) |
|
501
|
|
|
* |
|
502
|
|
|
* @param array $defaultOrderings The property names to order by |
|
503
|
|
|
* @throws \BadMethodCallException |
|
504
|
|
|
* @return void |
|
505
|
|
|
* @api |
|
506
|
|
|
*/ |
|
507
|
|
|
public function setDefaultOrderings(array $defaultOrderings) |
|
508
|
|
|
{ |
|
509
|
|
|
throw new \BadMethodCallException('Repository does not support the setDefaultOrderings() method.', 1375805598); |
|
510
|
|
|
} |
|
511
|
|
|
|
|
512
|
|
|
/** |
|
513
|
|
|
* Sets the default query settings to be used in this repository |
|
514
|
|
|
* |
|
515
|
|
|
* @param QuerySettingsInterface $defaultQuerySettings The query settings to be used by default |
|
516
|
|
|
* @throws \BadMethodCallException |
|
517
|
|
|
* @return void |
|
518
|
|
|
* @api |
|
519
|
|
|
*/ |
|
520
|
|
|
public function setDefaultQuerySettings(QuerySettingsInterface $defaultQuerySettings) |
|
521
|
|
|
{ |
|
522
|
|
|
$this->defaultQuerySettings = $defaultQuerySettings; |
|
523
|
|
|
} |
|
524
|
|
|
|
|
525
|
|
|
/** |
|
526
|
|
|
* @return void |
|
527
|
|
|
*/ |
|
528
|
|
|
public function resetDefaultQuerySettings() |
|
529
|
|
|
{ |
|
530
|
|
|
$this->defaultQuerySettings = null; |
|
531
|
|
|
} |
|
532
|
|
|
|
|
533
|
|
|
|
|
534
|
|
|
/** |
|
535
|
|
|
* @return array |
|
536
|
|
|
*/ |
|
537
|
|
|
public function getErrorMessages() |
|
538
|
|
|
{ |
|
539
|
|
|
return $this->errorMessages; |
|
540
|
|
|
} |
|
541
|
|
|
|
|
542
|
|
|
/** |
|
543
|
|
|
* @param string $sourceFieldName |
|
544
|
|
|
* @return $this |
|
545
|
|
|
*/ |
|
546
|
|
|
public function setSourceFieldName($sourceFieldName) |
|
547
|
|
|
{ |
|
548
|
|
|
$this->sourceFieldName = $sourceFieldName; |
|
549
|
|
|
return $this; |
|
550
|
|
|
} |
|
551
|
|
|
|
|
552
|
|
|
/** |
|
553
|
|
|
* @return string |
|
554
|
|
|
*/ |
|
555
|
|
|
public function getDataType() |
|
556
|
|
|
{ |
|
557
|
|
|
return $this->dataType; |
|
558
|
|
|
} |
|
559
|
|
|
|
|
560
|
|
|
/** |
|
561
|
|
|
* Tell whether the order has a foreign table in its expression, e.g. "metadata.title". |
|
562
|
|
|
* |
|
563
|
|
|
* @param string $ordering |
|
564
|
|
|
* @return bool |
|
565
|
|
|
*/ |
|
566
|
|
|
protected function hasForeignRelationIn($ordering) |
|
567
|
|
|
{ |
|
568
|
|
|
return strpos($ordering, '.') !== false; |
|
569
|
|
|
} |
|
570
|
|
|
|
|
571
|
|
|
/** |
|
572
|
|
|
* Extract the foreign relation of the ordering "metadata.title" -> "metadata" |
|
573
|
|
|
* |
|
574
|
|
|
* @param string $ordering |
|
575
|
|
|
* @return string |
|
576
|
|
|
*/ |
|
577
|
|
|
protected function getForeignRelationFrom($ordering) |
|
578
|
|
|
{ |
|
579
|
|
|
$parts = explode('.', $ordering); |
|
580
|
|
|
return $parts[0]; |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
|
|
/** |
|
584
|
|
|
* Get the constraints |
|
585
|
|
|
* |
|
586
|
|
|
* @param Query $query |
|
587
|
|
|
* @param Matcher $matcher |
|
588
|
|
|
* @return ConstraintInterface|null |
|
589
|
|
|
*/ |
|
590
|
|
|
protected function computeConstraints(Query $query, Matcher $matcher) |
|
591
|
|
|
{ |
|
592
|
|
|
|
|
593
|
|
|
$constraints = null; |
|
594
|
|
|
|
|
595
|
|
|
$collectedConstraints = array(); |
|
596
|
|
|
|
|
597
|
|
|
// Search term |
|
598
|
|
|
$constraint = $this->computeSearchTermConstraint($query, $matcher); |
|
599
|
|
|
if ($constraint) { |
|
600
|
|
|
$collectedConstraints[] = $constraint; |
|
601
|
|
|
} |
|
602
|
|
|
|
|
603
|
|
|
foreach ($matcher->getSupportedOperators() as $operator) { |
|
604
|
|
|
$constraint = $this->computeConstraint($query, $matcher, $operator); |
|
|
|
|
|
|
605
|
|
|
if ($constraint) { |
|
606
|
|
|
$collectedConstraints[] = $constraint; |
|
607
|
|
|
} |
|
608
|
|
|
} |
|
609
|
|
|
|
|
610
|
|
|
if (count($collectedConstraints) > 1) { |
|
611
|
|
|
$logical = $matcher->getDefaultLogicalSeparator(); |
|
612
|
|
|
$constraints = $query->$logical($collectedConstraints); |
|
613
|
|
|
} elseif (!empty($collectedConstraints)) { |
|
614
|
|
|
|
|
615
|
|
|
// true means there is one constraint only and should become the result |
|
616
|
|
|
$constraints = current($collectedConstraints); |
|
617
|
|
|
} |
|
618
|
|
|
|
|
619
|
|
|
// Trigger signal for post processing the computed constraints object. |
|
620
|
|
|
$constraints = $this->emitPostProcessConstraintsSignal($query, $constraints); |
|
621
|
|
|
|
|
622
|
|
|
return $constraints; |
|
623
|
|
|
} |
|
624
|
|
|
|
|
625
|
|
|
/** |
|
626
|
|
|
* Computes the search constraint and returns it. |
|
627
|
|
|
* |
|
628
|
|
|
* @param Query $query |
|
629
|
|
|
* @param Matcher $matcher |
|
630
|
|
|
* @return ConstraintInterface|null |
|
631
|
|
|
*/ |
|
632
|
|
|
protected function computeSearchTermConstraint(Query $query, Matcher $matcher) |
|
633
|
|
|
{ |
|
634
|
|
|
|
|
635
|
|
|
$result = null; |
|
636
|
|
|
|
|
637
|
|
|
// Search term case |
|
638
|
|
|
if ($matcher->getSearchTerm()) { |
|
639
|
|
|
|
|
640
|
|
|
$fields = GeneralUtility::trimExplode(',', Tca::table($this->dataType)->getSearchFields(), true); |
|
641
|
|
|
|
|
642
|
|
|
$constraints = array(); |
|
643
|
|
|
$likeClause = sprintf('%%%s%%', $matcher->getSearchTerm()); |
|
644
|
|
|
foreach ($fields as $fieldNameAndPath) { |
|
645
|
|
|
if ($this->isSuitableForLike($fieldNameAndPath, $matcher->getSearchTerm())) { |
|
646
|
|
|
|
|
647
|
|
|
$dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType); |
|
648
|
|
|
$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType); |
|
649
|
|
|
|
|
650
|
|
|
if (Tca::table($dataType)->hasField($fieldName) && Tca::table($dataType)->field($fieldName)->hasRelation()) { |
|
651
|
|
|
$foreignTable = Tca::table($dataType)->field($fieldName)->getForeignTable(); |
|
652
|
|
|
$fieldNameAndPath = $fieldNameAndPath . '.' . Tca::table($foreignTable)->getLabelField(); |
|
653
|
|
|
} |
|
654
|
|
|
$constraints[] = $query->like($fieldNameAndPath, $likeClause); |
|
655
|
|
|
} |
|
656
|
|
|
} |
|
657
|
|
|
$logical = $matcher->getLogicalSeparatorForSearchTerm(); |
|
658
|
|
|
$result = $query->$logical($constraints); |
|
659
|
|
|
} |
|
660
|
|
|
|
|
661
|
|
|
return $result; |
|
662
|
|
|
} |
|
663
|
|
|
|
|
664
|
|
|
/** |
|
665
|
|
|
* It does not make sense to have a "like" in presence of numerical field, e.g "uid". |
|
666
|
|
|
* Tell whether the given value makes sense for a "like" clause. |
|
667
|
|
|
* |
|
668
|
|
|
* @param string $fieldNameAndPath |
|
669
|
|
|
* @param string $value |
|
670
|
|
|
* @return bool |
|
671
|
|
|
*/ |
|
672
|
|
|
protected function isSuitableForLike($fieldNameAndPath, $value) |
|
673
|
|
|
{ |
|
674
|
|
|
$isSuitable = true; |
|
675
|
|
|
|
|
676
|
|
|
// true means it is a string |
|
677
|
|
|
if (!MathUtility::canBeInterpretedAsInteger($value)) { |
|
678
|
|
|
|
|
679
|
|
|
$dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType); |
|
680
|
|
|
$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType); |
|
681
|
|
|
|
|
682
|
|
|
if (Tca::table($dataType)->field($fieldName)->isNumerical() |
|
683
|
|
|
&& !Tca::table($dataType)->field($fieldName)->hasRelation() |
|
684
|
|
|
) { |
|
685
|
|
|
$isSuitable = false; |
|
686
|
|
|
} |
|
687
|
|
|
} |
|
688
|
|
|
|
|
689
|
|
|
return $isSuitable; |
|
690
|
|
|
} |
|
691
|
|
|
|
|
692
|
|
|
/** |
|
693
|
|
|
* Computes the constraint for matches and returns it. |
|
694
|
|
|
* |
|
695
|
|
|
* @param Query $query |
|
696
|
|
|
* @param Matcher $matcher |
|
697
|
|
|
* @param string $operator |
|
698
|
|
|
* @return ConstraintInterface|null |
|
699
|
|
|
*/ |
|
700
|
|
|
protected function computeConstraint(Query $query, Matcher $matcher, $operator) |
|
701
|
|
|
{ |
|
702
|
|
|
$result = null; |
|
703
|
|
|
|
|
704
|
|
|
$operatorName = ucfirst($operator); |
|
705
|
|
|
$getCriteria = sprintf('get%s', $operatorName); |
|
706
|
|
|
$criteria = $matcher->$getCriteria(); |
|
707
|
|
|
|
|
708
|
|
|
if (!empty($criteria)) { |
|
709
|
|
|
$constraints = array(); |
|
710
|
|
|
|
|
711
|
|
|
foreach ($criteria as $criterion) { |
|
712
|
|
|
|
|
713
|
|
|
$fieldNameAndPath = $criterion['fieldNameAndPath']; |
|
714
|
|
|
$operand = $criterion['operand']; |
|
715
|
|
|
|
|
716
|
|
|
// Compute a few variables... |
|
717
|
|
|
// $dataType is generally equals to $this->dataType but not always... if fieldName is a path. |
|
718
|
|
|
$dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType); |
|
719
|
|
|
$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType); |
|
720
|
|
|
$fieldPath = $this->getFieldPathResolver()->stripFieldName($fieldNameAndPath, $this->dataType); |
|
721
|
|
|
|
|
722
|
|
|
if (Tca::table($dataType)->field($fieldName)->hasRelation()) { |
|
723
|
|
|
if (MathUtility::canBeInterpretedAsInteger($operand)) { |
|
724
|
|
|
$fieldNameAndPath = $fieldName . '.uid'; |
|
725
|
|
|
} else { |
|
726
|
|
|
$foreignTableName = Tca::table($dataType)->field($fieldName)->getForeignTable(); |
|
727
|
|
|
$foreignTable = Tca::table($foreignTableName); |
|
728
|
|
|
$fieldNameAndPath = $fieldName . '.' . $foreignTable->getLabelField(); |
|
729
|
|
|
} |
|
730
|
|
|
|
|
731
|
|
|
// If different means we should restore the prepended path segment for proper SQL parser. |
|
732
|
|
|
// This is true for a composite field, e.g items.sys_file_metadata for categories. |
|
733
|
|
|
if ($fieldName !== $fieldPath) { |
|
734
|
|
|
$fieldNameAndPath = $fieldPath . '.' . $fieldNameAndPath; |
|
735
|
|
|
} |
|
736
|
|
|
} |
|
737
|
|
|
|
|
738
|
|
|
$constraints[] = $query->$operator($fieldNameAndPath, $criterion['operand']); |
|
739
|
|
|
} |
|
740
|
|
|
|
|
741
|
|
|
$getLogicalSeparator = sprintf('getLogicalSeparatorFor%s', $operatorName); |
|
742
|
|
|
$logical = $matcher->$getLogicalSeparator(); |
|
743
|
|
|
$result = $query->$logical($constraints); |
|
744
|
|
|
} |
|
745
|
|
|
|
|
746
|
|
|
return $result; |
|
747
|
|
|
} |
|
748
|
|
|
|
|
749
|
|
|
/** |
|
750
|
|
|
* @return DataHandler |
|
751
|
|
|
*/ |
|
752
|
|
|
protected function getDataHandler() |
|
753
|
|
|
{ |
|
754
|
|
|
if (!$this->dataHandler) { |
|
755
|
|
|
$this->dataHandler = GeneralUtility::makeInstance(DataHandler::class); |
|
756
|
|
|
} |
|
757
|
|
|
return $this->dataHandler; |
|
758
|
|
|
} |
|
759
|
|
|
|
|
760
|
|
|
/** |
|
761
|
|
|
* Handle the magic call by properly creating a Query object and returning its result. |
|
762
|
|
|
* |
|
763
|
|
|
* @param string $propertyName |
|
764
|
|
|
* @param string $value |
|
765
|
|
|
* @param string $flag |
|
766
|
|
|
* @return array |
|
767
|
|
|
*/ |
|
768
|
|
|
protected function processMagicCall($propertyName, $value, $flag = '') |
|
769
|
|
|
{ |
|
770
|
|
|
|
|
771
|
|
|
$fieldName = Property::name($propertyName)->of($this->dataType)->toFieldName(); |
|
772
|
|
|
|
|
773
|
|
|
/** @var $matcher Matcher */ |
|
774
|
|
|
$matcher = GeneralUtility::makeInstance(Matcher::class, array(), $this->getDataType()); |
|
775
|
|
|
|
|
776
|
|
|
$table = Tca::table($this->dataType); |
|
777
|
|
|
if ($table->field($fieldName)->isGroup()) { |
|
778
|
|
|
|
|
779
|
|
|
$valueParts = explode('.', $value, 2); |
|
780
|
|
|
$fieldName = $fieldName . '.' . $valueParts[0]; |
|
781
|
|
|
$value = $valueParts[1]; |
|
|
|
|
|
|
782
|
|
|
} |
|
783
|
|
|
|
|
784
|
|
|
$matcher->equals($fieldName, $value); |
|
785
|
|
|
|
|
786
|
|
|
if ($flag == 'count') { |
|
787
|
|
|
$result = $this->countBy($matcher); |
|
788
|
|
|
} else { |
|
789
|
|
|
$result = $this->findBy($matcher); |
|
790
|
|
|
} |
|
791
|
|
|
return $flag == 'one' && !empty($result) ? reset($result) : $result; |
|
792
|
|
|
} |
|
793
|
|
|
|
|
794
|
|
|
/** |
|
795
|
|
|
* @return DataHandlerFactory |
|
796
|
|
|
*/ |
|
797
|
|
|
protected function getDataHandlerFactory() |
|
798
|
|
|
{ |
|
799
|
|
|
return GeneralUtility::makeInstance(DataHandlerFactory::class); |
|
800
|
|
|
} |
|
801
|
|
|
|
|
802
|
|
|
/** |
|
803
|
|
|
* Returns whether the current mode is Backend |
|
804
|
|
|
* |
|
805
|
|
|
* @return bool |
|
806
|
|
|
*/ |
|
807
|
|
|
protected function isBackendMode() |
|
808
|
|
|
{ |
|
809
|
|
|
return TYPO3_MODE == 'BE'; |
|
810
|
|
|
} |
|
811
|
|
|
|
|
812
|
|
|
/** |
|
813
|
|
|
* @return FieldPathResolver |
|
814
|
|
|
*/ |
|
815
|
|
|
protected function getFieldPathResolver() |
|
816
|
|
|
{ |
|
817
|
|
|
return GeneralUtility::makeInstance(FieldPathResolver::class); |
|
818
|
|
|
} |
|
819
|
|
|
|
|
820
|
|
|
/** |
|
821
|
|
|
* @return ObjectManager |
|
822
|
|
|
*/ |
|
823
|
|
|
protected function getObjectManager() |
|
824
|
|
|
{ |
|
825
|
|
|
return GeneralUtility::makeInstance(ObjectManager::class); |
|
826
|
|
|
} |
|
827
|
|
|
|
|
828
|
|
|
/** |
|
829
|
|
|
* @return ContentValidator |
|
830
|
|
|
*/ |
|
831
|
|
|
protected function getContentValidator() |
|
832
|
|
|
{ |
|
833
|
|
|
return GeneralUtility::makeInstance(ContentValidator::class); |
|
834
|
|
|
} |
|
835
|
|
|
|
|
836
|
|
|
/** |
|
837
|
|
|
* @return LanguageValidator |
|
838
|
|
|
*/ |
|
839
|
|
|
protected function getLanguageValidator() |
|
840
|
|
|
{ |
|
841
|
|
|
return GeneralUtility::makeInstance(LanguageValidator::class); |
|
842
|
|
|
} |
|
843
|
|
|
|
|
844
|
|
|
/** |
|
845
|
|
|
* Signal that is called for post-processing the computed constraints object. |
|
846
|
|
|
* |
|
847
|
|
|
* @param Query $query |
|
848
|
|
|
* @param ConstraintInterface|null $constraints |
|
849
|
|
|
* @return ConstraintInterface|null $constraints |
|
850
|
|
|
* @signal |
|
851
|
|
|
*/ |
|
852
|
|
|
protected function emitPostProcessConstraintsSignal(Query $query, $constraints) |
|
853
|
|
|
{ |
|
854
|
|
|
$result = $this->getSignalSlotDispatcher()->dispatch( |
|
855
|
|
|
self::class, |
|
856
|
|
|
'postProcessConstraintsObject', |
|
857
|
|
|
array( |
|
858
|
|
|
$query, |
|
859
|
|
|
$constraints |
|
860
|
|
|
) |
|
861
|
|
|
); |
|
862
|
|
|
|
|
863
|
|
|
return $result[1]; |
|
864
|
|
|
} |
|
865
|
|
|
|
|
866
|
|
|
/** |
|
867
|
|
|
* @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher |
|
868
|
|
|
*/ |
|
869
|
|
|
protected function getSignalSlotDispatcher() |
|
870
|
|
|
{ |
|
871
|
|
|
return $this->getObjectManager()->get(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class); |
|
872
|
|
|
} |
|
873
|
|
|
|
|
874
|
|
|
} |
|
875
|
|
|
|
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.