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