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