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