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