Completed
Push — master ( 892a3f...068784 )
by Fabien
50:28
created
Classes/Domain/Repository/ContentRepository.php 1 patch
Indentation   +844 added lines, -844 removed lines patch added patch discarded remove patch
@@ -37,849 +37,849 @@
 block discarded – undo
37 37
 class ContentRepository implements RepositoryInterface
38 38
 {
39 39
 
40
-    /**
41
-     * Tell whether it is a raw result (array) or object being returned.
42
-     *
43
-     * @var bool
44
-     */
45
-    protected $rawResult = false;
46
-
47
-    /**
48
-     * The data type to be returned, e.g fe_users, fe_groups, tt_content, etc...
49
-     *
50
-     * @var string
51
-     */
52
-    protected $dataType;
53
-
54
-    /**
55
-     * The source field is useful in the context of MM relations to know who is the caller
56
-     * e.g findByItems which eventually corresponds to a field name.
57
-     *
58
-     * @var string
59
-     */
60
-    protected $sourceFieldName = '';
61
-
62
-    /**
63
-     * @var array
64
-     */
65
-    protected $errorMessages = [];
66
-
67
-    /**
68
-     * @var QuerySettingsInterface
69
-     */
70
-    protected $defaultQuerySettings;
71
-
72
-    /**
73
-     * @var DataHandler
74
-     */
75
-    protected $dataHandler;
76
-
77
-    /**
78
-     * Constructor
79
-     *
80
-     * @param string $dataType
81
-     */
82
-    public function __construct($dataType)
83
-    {
84
-        $this->dataType = $dataType;
85
-    }
86
-
87
-    /**
88
-     * Returns all objects of this repository.
89
-     *
90
-     * @return Content[]
91
-     */
92
-    public function findAll()
93
-    {
94
-        $query = $this->createQuery();
95
-        return $query->execute();
96
-    }
97
-
98
-    /**
99
-     * Returns all "distinct" values for a given property.
100
-     *
101
-     * @param string $propertyName
102
-     * @param Matcher $matcher
103
-     * @param Order|null $order
104
-     * @return Content[]
105
-     */
106
-    public function findDistinctValues($propertyName, Matcher $matcher = null, Order $order = null): array
107
-    {
108
-        $query = $this->createQuery();
109
-        $query->setDistinct($propertyName);
110
-
111
-        // Remove empty values from selection.
112
-        $constraint = $query->logicalNot($query->equals($propertyName, ''));
113
-
114
-        // Add some additional constraints from the Matcher object.
115
-        $matcherConstraint = null;
116
-        if ($matcher !== null) {
117
-            $matcherConstraint = $this->computeConstraints($query, $matcher);
118
-        }
119
-
120
-        // Assemble the final constraints or not.
121
-        if ($matcherConstraint) {
122
-            $query->logicalAnd([$matcherConstraint, $constraint]);
123
-            $query->matching($query->logicalAnd([$matcherConstraint, $constraint]));
124
-        } else {
125
-            $query->matching($constraint);
126
-        }
127
-
128
-        if ($order) {
129
-            $query->setOrderings($order->getOrderings());
130
-        }
131
-
132
-        return $query->execute();
133
-    }
134
-
135
-    /**
136
-     * Returns all "distinct" values for a given property.
137
-     *
138
-     * @param string $propertyName
139
-     * @param Matcher $matcher
140
-     * @return int
141
-     */
142
-    public function countDistinctValues($propertyName, Matcher $matcher = null): int
143
-    {
144
-        $query = $this->createQuery();
145
-        $query->setDistinct($propertyName);
146
-
147
-        // Remove empty values from selection.
148
-        $constraint = $query->logicalNot($query->equals($propertyName, ''));
149
-
150
-        // Add some additional constraints from the Matcher object.
151
-        $matcherConstraint = null;
152
-        if (!is_null($matcher)) {
153
-            $matcherConstraint = $this->computeConstraints($query, $matcher);
154
-        }
155
-
156
-        // Assemble the final constraints or not.
157
-        if ($matcherConstraint) {
158
-            $query->logicalAnd([$matcherConstraint, $constraint]);
159
-            $query->matching($query->logicalAnd([$matcherConstraint, $constraint]));
160
-        } else {
161
-            $query->matching($constraint);
162
-        }
163
-
164
-        return $query->count();
165
-    }
166
-
167
-    /**
168
-     * Finds an object matching the given identifier.
169
-     *
170
-     * @param int $uid The identifier of the object to find
171
-     * @return Content|null
172
-     * @api
173
-     */
174
-    public function findByUid($uid)
175
-    {
176
-        return $this->findByIdentifier($uid);
177
-    }
178
-
179
-    /**
180
-     * Finds all Contents given specified matches.
181
-     *
182
-     * @param string $propertyName
183
-     * @param array $values
184
-     * @return Content[]
185
-     */
186
-    public function findIn($propertyName, array $values): array
187
-    {
188
-        $query = $this->createQuery();
189
-        $query->matching($query->in($propertyName, $values));
190
-        return $query->execute();
191
-    }
192
-
193
-    /**
194
-     * Finds all Contents given specified matches.
195
-     *
196
-     * @param Matcher $matcher
197
-     * @param Order $order The order
198
-     * @param int $limit
199
-     * @param int $offset
200
-     * @return Content[]
201
-     */
202
-    public function findBy(Matcher $matcher, Order $order = null, $limit = null, $offset = null): array
203
-    {
204
-
205
-        $query = $this->createQuery();
206
-
207
-        $limit = (int)$limit; // make sure to cast
208
-        if ($limit > 0) {
209
-            $query->setLimit($limit);
210
-        }
211
-
212
-        if ($order) {
213
-            $query->setOrderings($order->getOrderings());
214
-
215
-            // Loops around the orderings adding if necessary a dummy condition
216
-            // to make sure the relations can be resolved when transforming the query to plain SQL.
217
-            foreach ($order->getOrderings() as $ordering => $direction) {
218
-                if ($this->hasForeignRelationIn($ordering)) {
219
-                    $relationalField = $this->getForeignRelationFrom($ordering);
220
-                    $matcher->like($relationalField . '.uid', '');
221
-                }
222
-            }
223
-        }
224
-
225
-        if ($offset) {
226
-            $query->setOffset($offset);
227
-        }
228
-
229
-        $constraints = $this->computeConstraints($query, $matcher);
230
-
231
-        if ($constraints) {
232
-            $query->matching($constraints);
233
-        }
234
-
235
-        return $query->execute();
236
-    }
237
-
238
-    /**
239
-     * Find one Content object given specified matches.
240
-     *
241
-     * @param Matcher $matcher
242
-     * @return Content
243
-     */
244
-    public function findOneBy(Matcher $matcher): Content
245
-    {
246
-
247
-        $query = $this->createQuery();
248
-
249
-        $constraints = $this->computeConstraints($query, $matcher);
250
-
251
-        if ($constraints) {
252
-            $query->matching($constraints);
253
-        }
254
-
255
-        $query->setLimit(1); // only take one!
256
-
257
-        $resultSet = $query->execute();
258
-        if ($resultSet) {
259
-            $resultSet = current($resultSet);
260
-        }
261
-        return $resultSet;
262
-    }
263
-
264
-    /**
265
-     * Count all Contents given specified matches.
266
-     *
267
-     * @param Matcher $matcher
268
-     * @return int
269
-     */
270
-    public function countBy(Matcher $matcher): int
271
-    {
272
-
273
-        $query = $this->createQuery();
274
-
275
-        $constraints = $this->computeConstraints($query, $matcher);
276
-
277
-        if ($constraints) {
278
-            $query->matching($constraints);
279
-        }
280
-
281
-        return $query->count();
282
-    }
283
-
284
-    /**
285
-     * Update a content with new information.
286
-     *
287
-     * @param Content $content
288
-     * @param $language
289
-     * @return bool
290
-     */
291
-    public function localize($content, $language): bool
292
-    {
293
-
294
-        // Security check
295
-        $this->getContentValidator()->validate($content);
296
-        $this->getLanguageValidator()->validate($language);
297
-
298
-        $dataType = $content->getDataType();
299
-        $handler = $this->getDataHandlerFactory()->action(ProcessAction::LOCALIZE)->forType($dataType)->getDataHandler();
300
-
301
-        $handlerResult = $handler->processLocalize($content, $language);
302
-        $this->errorMessages = $handler->getErrorMessages();
303
-        return $handlerResult;
304
-    }
305
-
306
-    /**
307
-     * Update a content with new information.
308
-     *
309
-     * @param Content $content
310
-     * @return bool
311
-     */
312
-    public function update($content)
313
-    {
314
-
315
-        // Security check.
316
-        $this->getContentValidator()->validate($content);
317
-
318
-        $dataType = $content->getDataType();
319
-        $handler = $this->getDataHandlerFactory()->action(ProcessAction::UPDATE)->forType($dataType)->getDataHandler();
320
-
321
-        $handlerResult = $handler->processUpdate($content);
322
-        $this->errorMessages = $handler->getErrorMessages();
323
-        return $handlerResult;
324
-    }
325
-
326
-    /**
327
-     * Removes an object from this repository.
328
-     *
329
-     * @param Content $content
330
-     * @return boolean
331
-     */
332
-    public function remove($content)
333
-    {
334
-        $dataType = $content->getDataType();
335
-        $handler = $this->getDataHandlerFactory()->action(ProcessAction::REMOVE)->forType($dataType)->getDataHandler();
336
-
337
-        $handlerResult = $handler->processRemove($content);
338
-        $this->errorMessages = $handler->getErrorMessages();
339
-        return $handlerResult;
340
-    }
341
-
342
-    /**
343
-     * Move a content within this repository.
344
-     * The $target corresponds to the pid to move the records to.
345
-     * It can also be a negative value in case of sorting. The negative value would be the uid of its predecessor.
346
-     *
347
-     * @param Content $content
348
-     * @param string $target
349
-     * @return bool
350
-     */
351
-    public function move($content, $target): bool
352
-    {
353
-
354
-        // Security check.
355
-        $this->getContentValidator()->validate($content);
356
-
357
-        $dataType = $content->getDataType();
358
-        $handler = $this->getDataHandlerFactory()->action(ProcessAction::MOVE)->forType($dataType)->getDataHandler();
359
-
360
-        $handlerResult = $handler->processMove($content, $target);
361
-        $this->errorMessages = $handler->getErrorMessages();
362
-        return $handlerResult;
363
-    }
364
-
365
-    /**
366
-     * Copy a content within this repository.
367
-     *
368
-     * @param Content $content
369
-     * @return bool
370
-     */
371
-    public function copy($content, $target): bool
372
-    {
373
-
374
-        // Security check.
375
-        $this->getContentValidator()->validate($content);
376
-
377
-        $dataType = $content->getDataType();
378
-        $handler = $this->getDataHandlerFactory()->action(ProcessAction::COPY)->forType($dataType)->getDataHandler();
379
-
380
-        $handlerResult = $handler->processCopy($content, $target);
381
-        $this->errorMessages = $handler->getErrorMessages();
382
-        return $handlerResult;
383
-    }
384
-
385
-    /**
386
-     * Adds an object to this repository.
387
-     *
388
-     * @param object $object The object to add
389
-     * @return void
390
-     * @api
391
-     */
392
-    public function add($object)
393
-    {
394
-        throw new \BadMethodCallException('Repository does not support the add() method.', 1375805599);
395
-    }
396
-
397
-    /**
398
-     * Returns the total number objects of this repository.
399
-     *
400
-     * @return integer The object count
401
-     * @api
402
-     */
403
-    public function countAll()
404
-    {
405
-        $query = $this->createQuery();
406
-        return $query->count();
407
-    }
408
-
409
-    /**
410
-     * Removes all objects of this repository as if remove() was called for
411
-     * all of them.
412
-     *
413
-     * @return void
414
-     * @api
415
-     */
416
-    public function removeAll()
417
-    {
418
-        // TODO: Implement removeAll() method.
419
-    }
420
-
421
-    /**
422
-     * Finds an object matching the given identifier.
423
-     *
424
-     * @param mixed $identifier The identifier of the object to find
425
-     * @return Content|null
426
-     * @api
427
-     */
428
-    public function findByIdentifier($identifier)
429
-    {
430
-        $query = $this->createQuery();
431
-
432
-        $result = $query->matching(
433
-            $query->equals('uid', $identifier)
434
-        )
435
-            ->execute();
436
-
437
-        if (is_array($result)) {
438
-            $result = current($result);
439
-        }
440
-
441
-        return $result;
442
-    }
443
-
444
-    /**
445
-     * Dispatches magic methods (findBy[Property]())
446
-     *
447
-     * @param string $methodName The name of the magic method
448
-     * @param string $arguments The arguments of the magic method
449
-     * @return mixed
450
-     * @api
451
-     */
452
-    public function __call($methodName, $arguments)
453
-    {
454
-        if (substr($methodName, 0, 6) === 'findBy' && strlen($methodName) > 7) {
455
-            $propertyName = strtolower(substr(substr($methodName, 6), 0, 1)) . substr(substr($methodName, 6), 1);
456
-            $result = $this->processMagicCall($propertyName, $arguments[0]);
457
-        } elseif (substr($methodName, 0, 9) === 'findOneBy' && strlen($methodName) > 10) {
458
-            $propertyName = strtolower(substr(substr($methodName, 9), 0, 1)) . substr(substr($methodName, 9), 1);
459
-            $result = $this->processMagicCall($propertyName, $arguments[0], 'one');
460
-        } elseif (substr($methodName, 0, 7) === 'countBy' && strlen($methodName) > 8) {
461
-            $propertyName = strtolower(substr(substr($methodName, 7), 0, 1)) . substr(substr($methodName, 7), 1);
462
-            $result = $this->processMagicCall($propertyName, $arguments[0], 'count');
463
-        } else {
464
-            throw new UnsupportedMethodException('The method "' . $methodName . '" is not supported by the repository.', 1360838010);
465
-        }
466
-        return $result;
467
-    }
468
-
469
-    /**
470
-     * Returns a query for objects of this repository
471
-     *
472
-     * @return Query
473
-     * @api
474
-     */
475
-    public function createQuery()
476
-    {
477
-        /** @var Query $query */
478
-        $query = GeneralUtility::makeInstance(Query::class, $this->dataType);
479
-        $query->setSourceFieldName($this->sourceFieldName);
480
-
481
-        if ($this->defaultQuerySettings) {
482
-            $query->setTypo3QuerySettings($this->defaultQuerySettings);
483
-        } else {
484
-
485
-            // Initialize and pass the query settings at this level.
486
-            $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class);
487
-
488
-            // Default choice for the BE.
489
-            if ($this->isBackendMode()) {
490
-                $querySettings->setIgnoreEnableFields(true);
491
-            }
492
-
493
-            $query->setTypo3QuerySettings($querySettings);
494
-        }
495
-
496
-        return $query;
497
-    }
498
-
499
-    /**
500
-     * Sets the property names to order the result by per default.
501
-     * Expected like this:
502
-     * array(
503
-     * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
504
-     * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
505
-     * )
506
-     *
507
-     * @param array $defaultOrderings The property names to order by
508
-     * @return void
509
-     * @api
510
-     */
511
-    public function setDefaultOrderings(array $defaultOrderings)
512
-    {
513
-        throw new \BadMethodCallException('Repository does not support the setDefaultOrderings() method.', 1375805598);
514
-    }
515
-
516
-    /**
517
-     * Sets the default query settings to be used in this repository
518
-     *
519
-     * @param QuerySettingsInterface $defaultQuerySettings The query settings to be used by default
520
-     * @return void
521
-     * @api
522
-     */
523
-    public function setDefaultQuerySettings(QuerySettingsInterface $defaultQuerySettings)
524
-    {
525
-        $this->defaultQuerySettings = $defaultQuerySettings;
526
-    }
527
-
528
-    /**
529
-     * @return void
530
-     */
531
-    public function resetDefaultQuerySettings(): void
532
-    {
533
-        $this->defaultQuerySettings = null;
534
-    }
535
-
536
-
537
-    /**
538
-     * @return array
539
-     */
540
-    public function getErrorMessages(): array
541
-    {
542
-        return $this->errorMessages;
543
-    }
544
-
545
-    /**
546
-     * @param string $sourceFieldName
547
-     * @return $this
548
-     */
549
-    public function setSourceFieldName($sourceFieldName): self
550
-    {
551
-        $this->sourceFieldName = $sourceFieldName;
552
-        return $this;
553
-    }
554
-
555
-    /**
556
-     * @return string
557
-     */
558
-    public function getDataType(): string
559
-    {
560
-        return $this->dataType;
561
-    }
562
-
563
-    /**
564
-     * Tell whether the order has a foreign table in its expression, e.g. "metadata.title".
565
-     *
566
-     * @param string $ordering
567
-     * @return bool
568
-     */
569
-    protected function hasForeignRelationIn($ordering): bool
570
-    {
571
-        return strpos($ordering, '.') !== false;
572
-    }
573
-
574
-    /**
575
-     * Extract the foreign relation of the ordering "metadata.title" -> "metadata"
576
-     *
577
-     * @param string $ordering
578
-     * @return string
579
-     */
580
-    protected function getForeignRelationFrom($ordering): string
581
-    {
582
-        $parts = explode('.', $ordering);
583
-        return $parts[0];
584
-    }
585
-
586
-    /**
587
-     * Get the constraints
588
-     *
589
-     * @param Query $query
590
-     * @param Matcher $matcher
591
-     * @return ConstraintInterface|null
592
-     */
593
-    protected function computeConstraints(Query $query, Matcher $matcher): ?ConstraintInterface
594
-    {
595
-        $constraints = null;
596
-
597
-        $collectedConstraints = [];
598
-
599
-        // Search term
600
-        $constraint = $this->computeSearchTermConstraint($query, $matcher);
601
-        if ($constraint) {
602
-            $collectedConstraints[] = $constraint;
603
-        }
604
-
605
-        foreach ($matcher->getSupportedOperators() as $operator) {
606
-            $constraint = $this->computeConstraint($query, $matcher, $operator);
607
-            if ($constraint) {
608
-                $collectedConstraints[] = $constraint;
609
-            }
610
-        }
611
-
612
-        if (count($collectedConstraints) > 1) {
613
-            $logical = $matcher->getDefaultLogicalSeparator();
614
-            $constraints = $query->$logical($collectedConstraints);
615
-        } elseif (!empty($collectedConstraints)) {
616
-
617
-            // true means there is one constraint only and should become the result
618
-            $constraints = current($collectedConstraints);
619
-        }
620
-
621
-        // Trigger signal for post processing the computed constraints object.
622
-        $constraints = $this->emitPostProcessConstraintsSignal($query, $constraints);
623
-
624
-        return $constraints;
625
-    }
626
-
627
-    /**
628
-     * Computes the search constraint and returns it.
629
-     *
630
-     * @param Query $query
631
-     * @param Matcher $matcher
632
-     * @return ConstraintInterface|null
633
-     */
634
-    protected function computeSearchTermConstraint(Query $query, Matcher $matcher): ?ConstraintInterface
635
-    {
636
-
637
-        $result = null;
638
-
639
-        // Search term case
640
-        if ($matcher->getSearchTerm()) {
641
-
642
-            $fields = GeneralUtility::trimExplode(',', Tca::table($this->dataType)->getSearchFields(), true);
643
-
644
-            $constraints = [];
645
-            $likeClause = sprintf('%%%s%%', $matcher->getSearchTerm());
646
-            foreach ($fields as $fieldNameAndPath) {
647
-                if ($this->isSuitableForLike($fieldNameAndPath, $matcher->getSearchTerm())) {
648
-
649
-                    $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
650
-                    $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
651
-
652
-                    if (Tca::table($dataType)->hasField($fieldName) && Tca::table($dataType)->field($fieldName)->hasRelation()) {
653
-                        $foreignTable = Tca::table($dataType)->field($fieldName)->getForeignTable();
654
-                        $fieldNameAndPath = $fieldNameAndPath . '.' . Tca::table($foreignTable)->getLabelField();
655
-                    }
656
-                    $constraints[] = $query->like($fieldNameAndPath, $likeClause);
657
-                }
658
-            }
659
-            $logical = $matcher->getLogicalSeparatorForSearchTerm();
660
-            $result = $query->$logical($constraints);
661
-        }
662
-
663
-        return $result;
664
-    }
665
-
666
-    /**
667
-     * It does not make sense to have a "like" in presence of numerical field, e.g "uid".
668
-     * Tell whether the given value makes sense for a "like" clause.
669
-     *
670
-     * @param string $fieldNameAndPath
671
-     * @param string $value
672
-     * @return bool
673
-     */
674
-    protected function isSuitableForLike($fieldNameAndPath, $value): bool
675
-    {
676
-        $isSuitable = true;
677
-
678
-        // true means it is a string
679
-        if (!MathUtility::canBeInterpretedAsInteger($value)) {
680
-
681
-            $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
682
-            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
683
-
684
-            if (Tca::table($dataType)->field($fieldName)->isNumerical()
685
-                && !Tca::table($dataType)->field($fieldName)->hasRelation()
686
-            ) {
687
-                $isSuitable = false;
688
-            }
689
-        }
690
-
691
-        return $isSuitable;
692
-    }
693
-
694
-    /**
695
-     * Computes the constraint for matches and returns it.
696
-     *
697
-     * @param Query $query
698
-     * @param Matcher $matcher
699
-     * @param string $operator
700
-     * @return ConstraintInterface|null
701
-     */
702
-    protected function computeConstraint(Query $query, Matcher $matcher, $operator): ?ConstraintInterface
703
-    {
704
-        $result = null;
705
-
706
-        $operatorName = ucfirst($operator);
707
-        $getCriteria = sprintf('get%s', $operatorName);
708
-        $criteria = $matcher->$getCriteria();
709
-
710
-        if (!empty($criteria)) {
711
-            $constraints = [];
712
-
713
-            foreach ($criteria as $criterion) {
714
-
715
-                $fieldNameAndPath = $criterion['fieldNameAndPath'];
716
-                $operand = $criterion['operand'];
717
-
718
-                // Compute a few variables...
719
-                // $dataType is generally equals to $this->dataType but not always... if fieldName is a path.
720
-                $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
721
-                $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
722
-                $fieldPath = $this->getFieldPathResolver()->stripFieldName($fieldNameAndPath, $this->dataType);
723
-
724
-                if (Tca::table($dataType)->field($fieldName)->hasRelation()) {
725
-                    if (MathUtility::canBeInterpretedAsInteger($operand)) {
726
-                        $fieldNameAndPath = $fieldName . '.uid';
727
-                    } else {
728
-                        $foreignTableName = Tca::table($dataType)->field($fieldName)->getForeignTable();
729
-                        $foreignTable = Tca::table($foreignTableName);
730
-                        $fieldNameAndPath = $fieldName . '.' . $foreignTable->getLabelField();
731
-                    }
732
-
733
-                    // If different means we should restore the prepended path segment for proper SQL parser.
734
-                    // This is true for a composite field, e.g items.sys_file_metadata for categories.
735
-                    if ($fieldName !== $fieldPath) {
736
-                        $fieldNameAndPath = $fieldPath . '.' . $fieldNameAndPath;
737
-                    }
738
-                }
739
-
740
-                if (strpos($operator, 'not') === 0) {
741
-                    $strippedOperator = strtolower(substr($operator, 3));
742
-                    $constraints[] = $query->logicalNot($query->$strippedOperator($fieldNameAndPath, $criterion['operand']));
743
-                } else {
744
-                    $constraints[] = $query->$operator($fieldNameAndPath, $criterion['operand']);
745
-                }
746
-            }
747
-
748
-            $getLogicalSeparator = sprintf('getLogicalSeparatorFor%s', $operatorName);
749
-            $logical = method_exists($matcher, $getLogicalSeparator)
750
-                ? $matcher->$getLogicalSeparator()
751
-                : $matcher->getDefaultLogicalSeparator();
752
-
753
-            $result = $query->$logical($constraints);
754
-        }
755
-
756
-        return $result;
757
-    }
758
-
759
-    /**
760
-     * @return DataHandler
761
-     */
762
-    protected function getDataHandler(): DataHandler
763
-    {
764
-        if (!$this->dataHandler) {
765
-            $this->dataHandler = GeneralUtility::makeInstance(DataHandler::class);
766
-        }
767
-        return $this->dataHandler;
768
-    }
769
-
770
-    /**
771
-     * Handle the magic call by properly creating a Query object and returning its result.
772
-     *
773
-     * @param string $propertyName
774
-     * @param string $value
775
-     * @param string $flag
776
-     * @return mixed
777
-     */
778
-    protected function processMagicCall($propertyName, $value, $flag = '')
779
-    {
780
-
781
-        $fieldName = Property::name($propertyName)->of($this->dataType)->toFieldName();
782
-
783
-        /** @var $matcher Matcher */
784
-        $matcher = GeneralUtility::makeInstance(Matcher::class, [], $this->getDataType());
785
-
786
-        $table = Tca::table($this->dataType);
787
-        if ($table->field($fieldName)->isGroup()) {
788
-
789
-            $valueParts = explode('.', $value, 2);
790
-            $fieldName = $fieldName . '.' . $valueParts[0];
791
-            $value = $valueParts[1];
792
-        }
793
-
794
-        $matcher->equals($fieldName, $value);
795
-
796
-        if ($flag === 'count') {
797
-            $result = $this->countBy($matcher);
798
-        } else {
799
-            $result = $this->findBy($matcher);
800
-        }
801
-        return $flag === 'one' && !empty($result) ? reset($result) : $result;
802
-    }
803
-
804
-    /**
805
-     * @return DataHandlerFactory|object
806
-     */
807
-    protected function getDataHandlerFactory()
808
-    {
809
-        return GeneralUtility::makeInstance(DataHandlerFactory::class);
810
-    }
811
-
812
-    /**
813
-     * Returns whether the current mode is Backend
814
-     *
815
-     * @return bool
816
-     */
817
-    protected function isBackendMode(): bool
818
-    {
819
-        return ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend();
820
-    }
821
-
822
-    /**
823
-     * @return FieldPathResolver|object
824
-     */
825
-    protected function getFieldPathResolver()
826
-    {
827
-        return GeneralUtility::makeInstance(FieldPathResolver::class);
828
-    }
829
-
830
-    /**
831
-     * @return ContentValidator|object
832
-     */
833
-    protected function getContentValidator(): ContentValidator
834
-    {
835
-        return GeneralUtility::makeInstance(ContentValidator::class);
836
-    }
837
-
838
-    /**
839
-     * @return LanguageValidator|object
840
-     */
841
-    protected function getLanguageValidator(): LanguageValidator
842
-    {
843
-        return GeneralUtility::makeInstance(LanguageValidator::class);
844
-    }
845
-
846
-    /**
847
-     * Signal that is called for post-processing the computed constraints object.
848
-     *
849
-     * @param Query $query
850
-     * @param ConstraintInterface|null $constraints
851
-     * @return ConstraintInterface|null $constraints
852
-     */
853
-    protected function emitPostProcessConstraintsSignal(Query $query, $constraints): ?ConstraintInterface
854
-    {
855
-        /** @var ConstraintContainer $constraintContainer */
856
-        $constraintContainer = GeneralUtility::makeInstance(ConstraintContainer::class);
857
-        $result = $this->getSignalSlotDispatcher()->dispatch(
858
-            self::class,
859
-            'postProcessConstraintsObject',
860
-            [
861
-                $query,
862
-                $constraints,
863
-                $constraintContainer
864
-            ]
865
-        );
866
-
867
-        // Backward compatibility.
868
-        $processedConstraints = $result[1];
869
-
870
-        // New way to transmit the constraints.
871
-        if ($constraintContainer->getConstraint()) {
872
-            $processedConstraints = $constraintContainer->getConstraint();
873
-        }
874
-        return $processedConstraints;
875
-    }
876
-
877
-    /**
878
-     * @return Dispatcher
879
-     */
880
-    protected function getSignalSlotDispatcher(): Dispatcher
881
-    {
882
-        return GeneralUtility::makeInstance(Dispatcher::class);
883
-    }
40
+	/**
41
+	 * Tell whether it is a raw result (array) or object being returned.
42
+	 *
43
+	 * @var bool
44
+	 */
45
+	protected $rawResult = false;
46
+
47
+	/**
48
+	 * The data type to be returned, e.g fe_users, fe_groups, tt_content, etc...
49
+	 *
50
+	 * @var string
51
+	 */
52
+	protected $dataType;
53
+
54
+	/**
55
+	 * The source field is useful in the context of MM relations to know who is the caller
56
+	 * e.g findByItems which eventually corresponds to a field name.
57
+	 *
58
+	 * @var string
59
+	 */
60
+	protected $sourceFieldName = '';
61
+
62
+	/**
63
+	 * @var array
64
+	 */
65
+	protected $errorMessages = [];
66
+
67
+	/**
68
+	 * @var QuerySettingsInterface
69
+	 */
70
+	protected $defaultQuerySettings;
71
+
72
+	/**
73
+	 * @var DataHandler
74
+	 */
75
+	protected $dataHandler;
76
+
77
+	/**
78
+	 * Constructor
79
+	 *
80
+	 * @param string $dataType
81
+	 */
82
+	public function __construct($dataType)
83
+	{
84
+		$this->dataType = $dataType;
85
+	}
86
+
87
+	/**
88
+	 * Returns all objects of this repository.
89
+	 *
90
+	 * @return Content[]
91
+	 */
92
+	public function findAll()
93
+	{
94
+		$query = $this->createQuery();
95
+		return $query->execute();
96
+	}
97
+
98
+	/**
99
+	 * Returns all "distinct" values for a given property.
100
+	 *
101
+	 * @param string $propertyName
102
+	 * @param Matcher $matcher
103
+	 * @param Order|null $order
104
+	 * @return Content[]
105
+	 */
106
+	public function findDistinctValues($propertyName, Matcher $matcher = null, Order $order = null): array
107
+	{
108
+		$query = $this->createQuery();
109
+		$query->setDistinct($propertyName);
110
+
111
+		// Remove empty values from selection.
112
+		$constraint = $query->logicalNot($query->equals($propertyName, ''));
113
+
114
+		// Add some additional constraints from the Matcher object.
115
+		$matcherConstraint = null;
116
+		if ($matcher !== null) {
117
+			$matcherConstraint = $this->computeConstraints($query, $matcher);
118
+		}
119
+
120
+		// Assemble the final constraints or not.
121
+		if ($matcherConstraint) {
122
+			$query->logicalAnd([$matcherConstraint, $constraint]);
123
+			$query->matching($query->logicalAnd([$matcherConstraint, $constraint]));
124
+		} else {
125
+			$query->matching($constraint);
126
+		}
127
+
128
+		if ($order) {
129
+			$query->setOrderings($order->getOrderings());
130
+		}
131
+
132
+		return $query->execute();
133
+	}
134
+
135
+	/**
136
+	 * Returns all "distinct" values for a given property.
137
+	 *
138
+	 * @param string $propertyName
139
+	 * @param Matcher $matcher
140
+	 * @return int
141
+	 */
142
+	public function countDistinctValues($propertyName, Matcher $matcher = null): int
143
+	{
144
+		$query = $this->createQuery();
145
+		$query->setDistinct($propertyName);
146
+
147
+		// Remove empty values from selection.
148
+		$constraint = $query->logicalNot($query->equals($propertyName, ''));
149
+
150
+		// Add some additional constraints from the Matcher object.
151
+		$matcherConstraint = null;
152
+		if (!is_null($matcher)) {
153
+			$matcherConstraint = $this->computeConstraints($query, $matcher);
154
+		}
155
+
156
+		// Assemble the final constraints or not.
157
+		if ($matcherConstraint) {
158
+			$query->logicalAnd([$matcherConstraint, $constraint]);
159
+			$query->matching($query->logicalAnd([$matcherConstraint, $constraint]));
160
+		} else {
161
+			$query->matching($constraint);
162
+		}
163
+
164
+		return $query->count();
165
+	}
166
+
167
+	/**
168
+	 * Finds an object matching the given identifier.
169
+	 *
170
+	 * @param int $uid The identifier of the object to find
171
+	 * @return Content|null
172
+	 * @api
173
+	 */
174
+	public function findByUid($uid)
175
+	{
176
+		return $this->findByIdentifier($uid);
177
+	}
178
+
179
+	/**
180
+	 * Finds all Contents given specified matches.
181
+	 *
182
+	 * @param string $propertyName
183
+	 * @param array $values
184
+	 * @return Content[]
185
+	 */
186
+	public function findIn($propertyName, array $values): array
187
+	{
188
+		$query = $this->createQuery();
189
+		$query->matching($query->in($propertyName, $values));
190
+		return $query->execute();
191
+	}
192
+
193
+	/**
194
+	 * Finds all Contents given specified matches.
195
+	 *
196
+	 * @param Matcher $matcher
197
+	 * @param Order $order The order
198
+	 * @param int $limit
199
+	 * @param int $offset
200
+	 * @return Content[]
201
+	 */
202
+	public function findBy(Matcher $matcher, Order $order = null, $limit = null, $offset = null): array
203
+	{
204
+
205
+		$query = $this->createQuery();
206
+
207
+		$limit = (int)$limit; // make sure to cast
208
+		if ($limit > 0) {
209
+			$query->setLimit($limit);
210
+		}
211
+
212
+		if ($order) {
213
+			$query->setOrderings($order->getOrderings());
214
+
215
+			// Loops around the orderings adding if necessary a dummy condition
216
+			// to make sure the relations can be resolved when transforming the query to plain SQL.
217
+			foreach ($order->getOrderings() as $ordering => $direction) {
218
+				if ($this->hasForeignRelationIn($ordering)) {
219
+					$relationalField = $this->getForeignRelationFrom($ordering);
220
+					$matcher->like($relationalField . '.uid', '');
221
+				}
222
+			}
223
+		}
224
+
225
+		if ($offset) {
226
+			$query->setOffset($offset);
227
+		}
228
+
229
+		$constraints = $this->computeConstraints($query, $matcher);
230
+
231
+		if ($constraints) {
232
+			$query->matching($constraints);
233
+		}
234
+
235
+		return $query->execute();
236
+	}
237
+
238
+	/**
239
+	 * Find one Content object given specified matches.
240
+	 *
241
+	 * @param Matcher $matcher
242
+	 * @return Content
243
+	 */
244
+	public function findOneBy(Matcher $matcher): Content
245
+	{
246
+
247
+		$query = $this->createQuery();
248
+
249
+		$constraints = $this->computeConstraints($query, $matcher);
250
+
251
+		if ($constraints) {
252
+			$query->matching($constraints);
253
+		}
254
+
255
+		$query->setLimit(1); // only take one!
256
+
257
+		$resultSet = $query->execute();
258
+		if ($resultSet) {
259
+			$resultSet = current($resultSet);
260
+		}
261
+		return $resultSet;
262
+	}
263
+
264
+	/**
265
+	 * Count all Contents given specified matches.
266
+	 *
267
+	 * @param Matcher $matcher
268
+	 * @return int
269
+	 */
270
+	public function countBy(Matcher $matcher): int
271
+	{
272
+
273
+		$query = $this->createQuery();
274
+
275
+		$constraints = $this->computeConstraints($query, $matcher);
276
+
277
+		if ($constraints) {
278
+			$query->matching($constraints);
279
+		}
280
+
281
+		return $query->count();
282
+	}
283
+
284
+	/**
285
+	 * Update a content with new information.
286
+	 *
287
+	 * @param Content $content
288
+	 * @param $language
289
+	 * @return bool
290
+	 */
291
+	public function localize($content, $language): bool
292
+	{
293
+
294
+		// Security check
295
+		$this->getContentValidator()->validate($content);
296
+		$this->getLanguageValidator()->validate($language);
297
+
298
+		$dataType = $content->getDataType();
299
+		$handler = $this->getDataHandlerFactory()->action(ProcessAction::LOCALIZE)->forType($dataType)->getDataHandler();
300
+
301
+		$handlerResult = $handler->processLocalize($content, $language);
302
+		$this->errorMessages = $handler->getErrorMessages();
303
+		return $handlerResult;
304
+	}
305
+
306
+	/**
307
+	 * Update a content with new information.
308
+	 *
309
+	 * @param Content $content
310
+	 * @return bool
311
+	 */
312
+	public function update($content)
313
+	{
314
+
315
+		// Security check.
316
+		$this->getContentValidator()->validate($content);
317
+
318
+		$dataType = $content->getDataType();
319
+		$handler = $this->getDataHandlerFactory()->action(ProcessAction::UPDATE)->forType($dataType)->getDataHandler();
320
+
321
+		$handlerResult = $handler->processUpdate($content);
322
+		$this->errorMessages = $handler->getErrorMessages();
323
+		return $handlerResult;
324
+	}
325
+
326
+	/**
327
+	 * Removes an object from this repository.
328
+	 *
329
+	 * @param Content $content
330
+	 * @return boolean
331
+	 */
332
+	public function remove($content)
333
+	{
334
+		$dataType = $content->getDataType();
335
+		$handler = $this->getDataHandlerFactory()->action(ProcessAction::REMOVE)->forType($dataType)->getDataHandler();
336
+
337
+		$handlerResult = $handler->processRemove($content);
338
+		$this->errorMessages = $handler->getErrorMessages();
339
+		return $handlerResult;
340
+	}
341
+
342
+	/**
343
+	 * Move a content within this repository.
344
+	 * The $target corresponds to the pid to move the records to.
345
+	 * It can also be a negative value in case of sorting. The negative value would be the uid of its predecessor.
346
+	 *
347
+	 * @param Content $content
348
+	 * @param string $target
349
+	 * @return bool
350
+	 */
351
+	public function move($content, $target): bool
352
+	{
353
+
354
+		// Security check.
355
+		$this->getContentValidator()->validate($content);
356
+
357
+		$dataType = $content->getDataType();
358
+		$handler = $this->getDataHandlerFactory()->action(ProcessAction::MOVE)->forType($dataType)->getDataHandler();
359
+
360
+		$handlerResult = $handler->processMove($content, $target);
361
+		$this->errorMessages = $handler->getErrorMessages();
362
+		return $handlerResult;
363
+	}
364
+
365
+	/**
366
+	 * Copy a content within this repository.
367
+	 *
368
+	 * @param Content $content
369
+	 * @return bool
370
+	 */
371
+	public function copy($content, $target): bool
372
+	{
373
+
374
+		// Security check.
375
+		$this->getContentValidator()->validate($content);
376
+
377
+		$dataType = $content->getDataType();
378
+		$handler = $this->getDataHandlerFactory()->action(ProcessAction::COPY)->forType($dataType)->getDataHandler();
379
+
380
+		$handlerResult = $handler->processCopy($content, $target);
381
+		$this->errorMessages = $handler->getErrorMessages();
382
+		return $handlerResult;
383
+	}
384
+
385
+	/**
386
+	 * Adds an object to this repository.
387
+	 *
388
+	 * @param object $object The object to add
389
+	 * @return void
390
+	 * @api
391
+	 */
392
+	public function add($object)
393
+	{
394
+		throw new \BadMethodCallException('Repository does not support the add() method.', 1375805599);
395
+	}
396
+
397
+	/**
398
+	 * Returns the total number objects of this repository.
399
+	 *
400
+	 * @return integer The object count
401
+	 * @api
402
+	 */
403
+	public function countAll()
404
+	{
405
+		$query = $this->createQuery();
406
+		return $query->count();
407
+	}
408
+
409
+	/**
410
+	 * Removes all objects of this repository as if remove() was called for
411
+	 * all of them.
412
+	 *
413
+	 * @return void
414
+	 * @api
415
+	 */
416
+	public function removeAll()
417
+	{
418
+		// TODO: Implement removeAll() method.
419
+	}
420
+
421
+	/**
422
+	 * Finds an object matching the given identifier.
423
+	 *
424
+	 * @param mixed $identifier The identifier of the object to find
425
+	 * @return Content|null
426
+	 * @api
427
+	 */
428
+	public function findByIdentifier($identifier)
429
+	{
430
+		$query = $this->createQuery();
431
+
432
+		$result = $query->matching(
433
+			$query->equals('uid', $identifier)
434
+		)
435
+			->execute();
436
+
437
+		if (is_array($result)) {
438
+			$result = current($result);
439
+		}
440
+
441
+		return $result;
442
+	}
443
+
444
+	/**
445
+	 * Dispatches magic methods (findBy[Property]())
446
+	 *
447
+	 * @param string $methodName The name of the magic method
448
+	 * @param string $arguments The arguments of the magic method
449
+	 * @return mixed
450
+	 * @api
451
+	 */
452
+	public function __call($methodName, $arguments)
453
+	{
454
+		if (substr($methodName, 0, 6) === 'findBy' && strlen($methodName) > 7) {
455
+			$propertyName = strtolower(substr(substr($methodName, 6), 0, 1)) . substr(substr($methodName, 6), 1);
456
+			$result = $this->processMagicCall($propertyName, $arguments[0]);
457
+		} elseif (substr($methodName, 0, 9) === 'findOneBy' && strlen($methodName) > 10) {
458
+			$propertyName = strtolower(substr(substr($methodName, 9), 0, 1)) . substr(substr($methodName, 9), 1);
459
+			$result = $this->processMagicCall($propertyName, $arguments[0], 'one');
460
+		} elseif (substr($methodName, 0, 7) === 'countBy' && strlen($methodName) > 8) {
461
+			$propertyName = strtolower(substr(substr($methodName, 7), 0, 1)) . substr(substr($methodName, 7), 1);
462
+			$result = $this->processMagicCall($propertyName, $arguments[0], 'count');
463
+		} else {
464
+			throw new UnsupportedMethodException('The method "' . $methodName . '" is not supported by the repository.', 1360838010);
465
+		}
466
+		return $result;
467
+	}
468
+
469
+	/**
470
+	 * Returns a query for objects of this repository
471
+	 *
472
+	 * @return Query
473
+	 * @api
474
+	 */
475
+	public function createQuery()
476
+	{
477
+		/** @var Query $query */
478
+		$query = GeneralUtility::makeInstance(Query::class, $this->dataType);
479
+		$query->setSourceFieldName($this->sourceFieldName);
480
+
481
+		if ($this->defaultQuerySettings) {
482
+			$query->setTypo3QuerySettings($this->defaultQuerySettings);
483
+		} else {
484
+
485
+			// Initialize and pass the query settings at this level.
486
+			$querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class);
487
+
488
+			// Default choice for the BE.
489
+			if ($this->isBackendMode()) {
490
+				$querySettings->setIgnoreEnableFields(true);
491
+			}
492
+
493
+			$query->setTypo3QuerySettings($querySettings);
494
+		}
495
+
496
+		return $query;
497
+	}
498
+
499
+	/**
500
+	 * Sets the property names to order the result by per default.
501
+	 * Expected like this:
502
+	 * array(
503
+	 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
504
+	 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
505
+	 * )
506
+	 *
507
+	 * @param array $defaultOrderings The property names to order by
508
+	 * @return void
509
+	 * @api
510
+	 */
511
+	public function setDefaultOrderings(array $defaultOrderings)
512
+	{
513
+		throw new \BadMethodCallException('Repository does not support the setDefaultOrderings() method.', 1375805598);
514
+	}
515
+
516
+	/**
517
+	 * Sets the default query settings to be used in this repository
518
+	 *
519
+	 * @param QuerySettingsInterface $defaultQuerySettings The query settings to be used by default
520
+	 * @return void
521
+	 * @api
522
+	 */
523
+	public function setDefaultQuerySettings(QuerySettingsInterface $defaultQuerySettings)
524
+	{
525
+		$this->defaultQuerySettings = $defaultQuerySettings;
526
+	}
527
+
528
+	/**
529
+	 * @return void
530
+	 */
531
+	public function resetDefaultQuerySettings(): void
532
+	{
533
+		$this->defaultQuerySettings = null;
534
+	}
535
+
536
+
537
+	/**
538
+	 * @return array
539
+	 */
540
+	public function getErrorMessages(): array
541
+	{
542
+		return $this->errorMessages;
543
+	}
544
+
545
+	/**
546
+	 * @param string $sourceFieldName
547
+	 * @return $this
548
+	 */
549
+	public function setSourceFieldName($sourceFieldName): self
550
+	{
551
+		$this->sourceFieldName = $sourceFieldName;
552
+		return $this;
553
+	}
554
+
555
+	/**
556
+	 * @return string
557
+	 */
558
+	public function getDataType(): string
559
+	{
560
+		return $this->dataType;
561
+	}
562
+
563
+	/**
564
+	 * Tell whether the order has a foreign table in its expression, e.g. "metadata.title".
565
+	 *
566
+	 * @param string $ordering
567
+	 * @return bool
568
+	 */
569
+	protected function hasForeignRelationIn($ordering): bool
570
+	{
571
+		return strpos($ordering, '.') !== false;
572
+	}
573
+
574
+	/**
575
+	 * Extract the foreign relation of the ordering "metadata.title" -> "metadata"
576
+	 *
577
+	 * @param string $ordering
578
+	 * @return string
579
+	 */
580
+	protected function getForeignRelationFrom($ordering): string
581
+	{
582
+		$parts = explode('.', $ordering);
583
+		return $parts[0];
584
+	}
585
+
586
+	/**
587
+	 * Get the constraints
588
+	 *
589
+	 * @param Query $query
590
+	 * @param Matcher $matcher
591
+	 * @return ConstraintInterface|null
592
+	 */
593
+	protected function computeConstraints(Query $query, Matcher $matcher): ?ConstraintInterface
594
+	{
595
+		$constraints = null;
596
+
597
+		$collectedConstraints = [];
598
+
599
+		// Search term
600
+		$constraint = $this->computeSearchTermConstraint($query, $matcher);
601
+		if ($constraint) {
602
+			$collectedConstraints[] = $constraint;
603
+		}
604
+
605
+		foreach ($matcher->getSupportedOperators() as $operator) {
606
+			$constraint = $this->computeConstraint($query, $matcher, $operator);
607
+			if ($constraint) {
608
+				$collectedConstraints[] = $constraint;
609
+			}
610
+		}
611
+
612
+		if (count($collectedConstraints) > 1) {
613
+			$logical = $matcher->getDefaultLogicalSeparator();
614
+			$constraints = $query->$logical($collectedConstraints);
615
+		} elseif (!empty($collectedConstraints)) {
616
+
617
+			// true means there is one constraint only and should become the result
618
+			$constraints = current($collectedConstraints);
619
+		}
620
+
621
+		// Trigger signal for post processing the computed constraints object.
622
+		$constraints = $this->emitPostProcessConstraintsSignal($query, $constraints);
623
+
624
+		return $constraints;
625
+	}
626
+
627
+	/**
628
+	 * Computes the search constraint and returns it.
629
+	 *
630
+	 * @param Query $query
631
+	 * @param Matcher $matcher
632
+	 * @return ConstraintInterface|null
633
+	 */
634
+	protected function computeSearchTermConstraint(Query $query, Matcher $matcher): ?ConstraintInterface
635
+	{
636
+
637
+		$result = null;
638
+
639
+		// Search term case
640
+		if ($matcher->getSearchTerm()) {
641
+
642
+			$fields = GeneralUtility::trimExplode(',', Tca::table($this->dataType)->getSearchFields(), true);
643
+
644
+			$constraints = [];
645
+			$likeClause = sprintf('%%%s%%', $matcher->getSearchTerm());
646
+			foreach ($fields as $fieldNameAndPath) {
647
+				if ($this->isSuitableForLike($fieldNameAndPath, $matcher->getSearchTerm())) {
648
+
649
+					$dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
650
+					$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
651
+
652
+					if (Tca::table($dataType)->hasField($fieldName) && Tca::table($dataType)->field($fieldName)->hasRelation()) {
653
+						$foreignTable = Tca::table($dataType)->field($fieldName)->getForeignTable();
654
+						$fieldNameAndPath = $fieldNameAndPath . '.' . Tca::table($foreignTable)->getLabelField();
655
+					}
656
+					$constraints[] = $query->like($fieldNameAndPath, $likeClause);
657
+				}
658
+			}
659
+			$logical = $matcher->getLogicalSeparatorForSearchTerm();
660
+			$result = $query->$logical($constraints);
661
+		}
662
+
663
+		return $result;
664
+	}
665
+
666
+	/**
667
+	 * It does not make sense to have a "like" in presence of numerical field, e.g "uid".
668
+	 * Tell whether the given value makes sense for a "like" clause.
669
+	 *
670
+	 * @param string $fieldNameAndPath
671
+	 * @param string $value
672
+	 * @return bool
673
+	 */
674
+	protected function isSuitableForLike($fieldNameAndPath, $value): bool
675
+	{
676
+		$isSuitable = true;
677
+
678
+		// true means it is a string
679
+		if (!MathUtility::canBeInterpretedAsInteger($value)) {
680
+
681
+			$dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
682
+			$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
683
+
684
+			if (Tca::table($dataType)->field($fieldName)->isNumerical()
685
+				&& !Tca::table($dataType)->field($fieldName)->hasRelation()
686
+			) {
687
+				$isSuitable = false;
688
+			}
689
+		}
690
+
691
+		return $isSuitable;
692
+	}
693
+
694
+	/**
695
+	 * Computes the constraint for matches and returns it.
696
+	 *
697
+	 * @param Query $query
698
+	 * @param Matcher $matcher
699
+	 * @param string $operator
700
+	 * @return ConstraintInterface|null
701
+	 */
702
+	protected function computeConstraint(Query $query, Matcher $matcher, $operator): ?ConstraintInterface
703
+	{
704
+		$result = null;
705
+
706
+		$operatorName = ucfirst($operator);
707
+		$getCriteria = sprintf('get%s', $operatorName);
708
+		$criteria = $matcher->$getCriteria();
709
+
710
+		if (!empty($criteria)) {
711
+			$constraints = [];
712
+
713
+			foreach ($criteria as $criterion) {
714
+
715
+				$fieldNameAndPath = $criterion['fieldNameAndPath'];
716
+				$operand = $criterion['operand'];
717
+
718
+				// Compute a few variables...
719
+				// $dataType is generally equals to $this->dataType but not always... if fieldName is a path.
720
+				$dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->dataType);
721
+				$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->dataType);
722
+				$fieldPath = $this->getFieldPathResolver()->stripFieldName($fieldNameAndPath, $this->dataType);
723
+
724
+				if (Tca::table($dataType)->field($fieldName)->hasRelation()) {
725
+					if (MathUtility::canBeInterpretedAsInteger($operand)) {
726
+						$fieldNameAndPath = $fieldName . '.uid';
727
+					} else {
728
+						$foreignTableName = Tca::table($dataType)->field($fieldName)->getForeignTable();
729
+						$foreignTable = Tca::table($foreignTableName);
730
+						$fieldNameAndPath = $fieldName . '.' . $foreignTable->getLabelField();
731
+					}
732
+
733
+					// If different means we should restore the prepended path segment for proper SQL parser.
734
+					// This is true for a composite field, e.g items.sys_file_metadata for categories.
735
+					if ($fieldName !== $fieldPath) {
736
+						$fieldNameAndPath = $fieldPath . '.' . $fieldNameAndPath;
737
+					}
738
+				}
739
+
740
+				if (strpos($operator, 'not') === 0) {
741
+					$strippedOperator = strtolower(substr($operator, 3));
742
+					$constraints[] = $query->logicalNot($query->$strippedOperator($fieldNameAndPath, $criterion['operand']));
743
+				} else {
744
+					$constraints[] = $query->$operator($fieldNameAndPath, $criterion['operand']);
745
+				}
746
+			}
747
+
748
+			$getLogicalSeparator = sprintf('getLogicalSeparatorFor%s', $operatorName);
749
+			$logical = method_exists($matcher, $getLogicalSeparator)
750
+				? $matcher->$getLogicalSeparator()
751
+				: $matcher->getDefaultLogicalSeparator();
752
+
753
+			$result = $query->$logical($constraints);
754
+		}
755
+
756
+		return $result;
757
+	}
758
+
759
+	/**
760
+	 * @return DataHandler
761
+	 */
762
+	protected function getDataHandler(): DataHandler
763
+	{
764
+		if (!$this->dataHandler) {
765
+			$this->dataHandler = GeneralUtility::makeInstance(DataHandler::class);
766
+		}
767
+		return $this->dataHandler;
768
+	}
769
+
770
+	/**
771
+	 * Handle the magic call by properly creating a Query object and returning its result.
772
+	 *
773
+	 * @param string $propertyName
774
+	 * @param string $value
775
+	 * @param string $flag
776
+	 * @return mixed
777
+	 */
778
+	protected function processMagicCall($propertyName, $value, $flag = '')
779
+	{
780
+
781
+		$fieldName = Property::name($propertyName)->of($this->dataType)->toFieldName();
782
+
783
+		/** @var $matcher Matcher */
784
+		$matcher = GeneralUtility::makeInstance(Matcher::class, [], $this->getDataType());
785
+
786
+		$table = Tca::table($this->dataType);
787
+		if ($table->field($fieldName)->isGroup()) {
788
+
789
+			$valueParts = explode('.', $value, 2);
790
+			$fieldName = $fieldName . '.' . $valueParts[0];
791
+			$value = $valueParts[1];
792
+		}
793
+
794
+		$matcher->equals($fieldName, $value);
795
+
796
+		if ($flag === 'count') {
797
+			$result = $this->countBy($matcher);
798
+		} else {
799
+			$result = $this->findBy($matcher);
800
+		}
801
+		return $flag === 'one' && !empty($result) ? reset($result) : $result;
802
+	}
803
+
804
+	/**
805
+	 * @return DataHandlerFactory|object
806
+	 */
807
+	protected function getDataHandlerFactory()
808
+	{
809
+		return GeneralUtility::makeInstance(DataHandlerFactory::class);
810
+	}
811
+
812
+	/**
813
+	 * Returns whether the current mode is Backend
814
+	 *
815
+	 * @return bool
816
+	 */
817
+	protected function isBackendMode(): bool
818
+	{
819
+		return ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend();
820
+	}
821
+
822
+	/**
823
+	 * @return FieldPathResolver|object
824
+	 */
825
+	protected function getFieldPathResolver()
826
+	{
827
+		return GeneralUtility::makeInstance(FieldPathResolver::class);
828
+	}
829
+
830
+	/**
831
+	 * @return ContentValidator|object
832
+	 */
833
+	protected function getContentValidator(): ContentValidator
834
+	{
835
+		return GeneralUtility::makeInstance(ContentValidator::class);
836
+	}
837
+
838
+	/**
839
+	 * @return LanguageValidator|object
840
+	 */
841
+	protected function getLanguageValidator(): LanguageValidator
842
+	{
843
+		return GeneralUtility::makeInstance(LanguageValidator::class);
844
+	}
845
+
846
+	/**
847
+	 * Signal that is called for post-processing the computed constraints object.
848
+	 *
849
+	 * @param Query $query
850
+	 * @param ConstraintInterface|null $constraints
851
+	 * @return ConstraintInterface|null $constraints
852
+	 */
853
+	protected function emitPostProcessConstraintsSignal(Query $query, $constraints): ?ConstraintInterface
854
+	{
855
+		/** @var ConstraintContainer $constraintContainer */
856
+		$constraintContainer = GeneralUtility::makeInstance(ConstraintContainer::class);
857
+		$result = $this->getSignalSlotDispatcher()->dispatch(
858
+			self::class,
859
+			'postProcessConstraintsObject',
860
+			[
861
+				$query,
862
+				$constraints,
863
+				$constraintContainer
864
+			]
865
+		);
866
+
867
+		// Backward compatibility.
868
+		$processedConstraints = $result[1];
869
+
870
+		// New way to transmit the constraints.
871
+		if ($constraintContainer->getConstraint()) {
872
+			$processedConstraints = $constraintContainer->getConstraint();
873
+		}
874
+		return $processedConstraints;
875
+	}
876
+
877
+	/**
878
+	 * @return Dispatcher
879
+	 */
880
+	protected function getSignalSlotDispatcher(): Dispatcher
881
+	{
882
+		return GeneralUtility::makeInstance(Dispatcher::class);
883
+	}
884 884
 
885 885
 }
Please login to merge, or discard this patch.
Classes/Controller/ContentController.php 1 patch
Indentation   +745 added lines, -745 removed lines patch added patch discarded remove patch
@@ -44,750 +44,750 @@
 block discarded – undo
44 44
  */
45 45
 class ContentController extends ActionController
46 46
 {
47
-    /**
48
-     * Initialize every action.
49
-     */
50
-    public function initializeAction()
51
-    {
52
-        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
53
-        $pageRenderer->addInlineLanguageLabelFile('EXT:vidi/Resources/Private/Language/locallang.xlf');
54
-
55
-        // Configure property mapping to retrieve the file object.
56
-        if ($this->arguments->hasArgument('columns')) {
57
-
58
-            /** @var CsvToArrayConverter $typeConverter */
59
-            $typeConverter = GeneralUtility::makeInstance(CsvToArrayConverter::class);
60
-
61
-            $propertyMappingConfiguration = $this->arguments->getArgument('columns')->getPropertyMappingConfiguration();
62
-            $propertyMappingConfiguration->setTypeConverter($typeConverter);
63
-        }
64
-    }
65
-
66
-    /**
67
-     * List action for this controller.
68
-     *
69
-     * @return void
70
-     */
71
-    public function indexAction(): ResponseInterface
72
-    {
73
-        $dataType = $this->getModuleLoader()->getDataType();
74
-        $selections = $this->selectionRepository->findByDataTypeForCurrentBackendUser($dataType);
75
-        $this->view->assign('selections', $selections);
76
-
77
-        $columns = Tca::grid()->getFields();
78
-        $this->view->assign('columns', $columns);
79
-        $this->view->assign('numberOfColumns', count($columns));
80
-        return $this->htmlResponse();
81
-    }
82
-
83
-    /**
84
-     * List Row action for this controller. Output a json list of contents
85
-     *
86
-     * @param array $columns corresponds to columns to be rendered.
87
-     * @param array $matches
88
-     * @Validate("Fab\Vidi\Domain\Validator\ColumnsValidator", param="columns")
89
-     * @Validate("Fab\Vidi\Domain\Validator\MatchesValidator", param="matches")
90
-     * @return void
91
-     */
92
-    public function listAction(array $columns = [], $matches = []): ResponseInterface
93
-    {
94
-        // Initialize some objects related to the query.
95
-        $matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
96
-        $order = OrderObjectFactory::getInstance()->getOrder();
97
-        $pager = PagerObjectFactory::getInstance()->getPager();
98
-
99
-        // Fetch objects via the Content Service.
100
-        $contentService = $this->getContentService()->findBy($matcher, $order, $pager->getLimit(), $pager->getOffset());
101
-        $pager->setCount($contentService->getNumberOfObjects());
102
-
103
-        // Assign values.
104
-        $this->view->assign('columns', $columns);
105
-        $this->view->assign('objects', $contentService->getObjects());
106
-        $this->view->assign('numberOfObjects', $contentService->getNumberOfObjects());
107
-        $this->view->assign('pager', $pager);
108
-
109
-        $this->view->assign('response', $this->responseFactory->createResponse());
110
-        return $this->htmlResponse();
111
-    }
112
-
113
-    /**
114
-     * Retrieve Content objects first according to matching criteria and then "update" them.
115
-     * Important to notice the field name can contains a path, e.g. metadata.title and therefore must be analysed.
116
-     *
117
-     * Possible values for $matches:
118
-     * -----------------------------
119
-     *
120
-     * $matches = array(uid => 1), will be taken as $query->equals
121
-     * $matches = array(uid => 1,2,3), will be taken as $query->in
122
-     * $matches = array(field_name1 => bar, field_name2 => bax), will be separated by AND.
123
-     *
124
-     * Possible values for $content:
125
-     * -----------------------------
126
-     *
127
-     * $content = array(field_name => bar)
128
-     * $content = array(field_name => array(value1, value2)) <-- will be CSV converted by "value1,value2"
129
-     *
130
-     * @param string $fieldNameAndPath
131
-     * @param array $content
132
-     * @param array $matches
133
-     * @param string $savingBehavior
134
-     * @param int $language
135
-     * @param array $columns
136
-     * @throws InvalidKeyInArrayException
137
-     */
138
-    public function updateAction($fieldNameAndPath, array $content, array $matches = [], $savingBehavior = SavingBehavior::REPLACE, $language = 0, $columns = [])
139
-    {
140
-
141
-        // Instantiate the Matcher object according different rules.
142
-        $matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
143
-        $order = OrderObjectFactory::getInstance()->getOrder();
144
-
145
-        // Fetch objects via the Content Service.
146
-        $contentService = $this->getContentService()->findBy($matcher, $order);
147
-
148
-        // Get the real field that is going to be updated.
149
-        $updatedFieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath);
150
-
151
-        // Get result object for storing data along the processing.
152
-        $result = $this->getJsonResult();
153
-        $result->setNumberOfObjects($contentService->getNumberOfObjects());
154
-
155
-        foreach ($contentService->getObjects() as $index => $object) {
156
-
157
-            $identifier = $this->getContentObjectResolver()->getValue($object, $fieldNameAndPath, 'uid', $language);
158
-
159
-            // It could be the identifier is not found because the translation
160
-            // of the record does not yet exist when mass-editing
161
-            if ((int)$identifier <= 0) {
162
-                continue;
163
-            }
164
-
165
-            $dataType = $this->getContentObjectResolver()->getDataType($object, $fieldNameAndPath);
166
-
167
-            $signalResult = $this->emitProcessContentDataSignal($object, $fieldNameAndPath, $content, $index + 1, $savingBehavior, $language);
168
-            $contentData = $signalResult->getContentData();
169
-
170
-            // Add identifier to content data, required by TCEMain.
171
-            $contentData['uid'] = $identifier;
172
-
173
-            /** @var Content $dataObject */
174
-            $dataObject = GeneralUtility::makeInstance(Content::class, $dataType, $contentData);
175
-
176
-            // Properly update object.
177
-            ContentRepositoryFactory::getInstance($dataType)->update($dataObject);
178
-
179
-            // Get the possible error messages and store them.
180
-            $errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
181
-            $result->addErrorMessages($errorMessages);
182
-
183
-            // We only want to see the detail result if there is one object updated.
184
-            // Required for inline editing + it will display some useful info on the GUI in the flash messages.
185
-            if ($contentService->getNumberOfObjects() === 1) {
186
-
187
-                // Fetch the updated object from repository.
188
-                $updatedObject = ContentRepositoryFactory::getInstance()->findByUid($object->getUid());
189
-
190
-                // Re-fetch the updated result.
191
-                $updatedResult = $this->getContentObjectResolver()->getValue($updatedObject, $fieldNameAndPath, $updatedFieldName, $language);
192
-                if (is_array($updatedResult)) {
193
-                    $_updatedResult = []; // reset result set.
194
-
195
-                    /** @var Content $contentObject */
196
-                    foreach ($updatedResult as $contentObject) {
197
-                        $labelField = Tca::table($contentObject)->getLabelField();
198
-                        $values = array(
199
-                            'uid' => $contentObject->getUid(),
200
-                            'name' => $contentObject[$labelField],
201
-                        );
202
-                        $_updatedResult[] = $values;
203
-                    }
204
-
205
-                    $updatedResult = $_updatedResult;
206
-                }
207
-
208
-                $labelField = Tca::table($object)->getLabelField();
209
-
210
-                $processedObjectData = array(
211
-                    'uid' => $object->getUid(),
212
-                    'name' => $object[$labelField],
213
-                    'updatedField' => $fieldNameAndPath,
214
-                    'updatedValue' => $updatedResult,
215
-                );
216
-                $result->setProcessedObject($processedObjectData);
217
-
218
-                if (!empty($columns)) {
219
-                    /** @var Row $row */
220
-                    $row = GeneralUtility::makeInstance(Row::class, $columns);
221
-                    $result->setRow($row->render($updatedObject));
222
-                }
223
-            }
224
-        }
225
-
226
-        $response = $this->responseFactory->createResponse()
227
-            ->withHeader('Content-Type', 'application/json; charset=utf-8');
228
-        $response->getBody()->write(json_encode($result));
229
-        return $response;
230
-    }
231
-
232
-    /**
233
-     * Set the sorting of a record giving the previous object.
234
-     *
235
-     * @param array $matches
236
-     * @param int $previousIdentifier
237
-     */
238
-    public function sortAction(array $matches = [], $previousIdentifier = null)
239
-    {
240
-
241
-        $matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
242
-
243
-        // Fetch objects via the Content Service.
244
-        $contentService = $this->getContentService()->findBy($matcher);
245
-
246
-        // Compute the label field name of the table.
247
-        $tableTitleField = Tca::table()->getLabelField();
248
-
249
-        // Get result object for storing data along the processing.
250
-        $result = $this->getJsonResult();
251
-        $result->setNumberOfObjects($contentService->getNumberOfObjects());
252
-
253
-        foreach ($contentService->getObjects() as $object) {
254
-
255
-            // Store the first object, so that the "action" message can be more explicit when deleting only one record.
256
-            if ($contentService->getNumberOfObjects() === 1) {
257
-                $tableTitleValue = $object[$tableTitleField];
258
-                $processedObjectData = array(
259
-                    'uid' => $object->getUid(),
260
-                    'name' => $tableTitleValue,
261
-                );
262
-                $result->setProcessedObject($processedObjectData);
263
-            }
264
-
265
-            // The $target corresponds to the pid to move the records to.
266
-            // It can also be a negative value in case of sorting. The negative value would be the uid of its predecessor.
267
-            $target = is_null($previousIdentifier) ? $object->getPid() : (-(int)$previousIdentifier);
268
-
269
-            // Work out the object.
270
-            ContentRepositoryFactory::getInstance()->move($object, $target);
271
-
272
-            // Get the possible error messages and store them.
273
-            $errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
274
-            $result->addErrorMessages($errorMessages);
275
-        }
276
-
277
-        $response = $this->responseFactory->createResponse()
278
-            ->withHeader('Content-Type', 'application/json; charset=utf-8');
279
-        $response->getBody()->write(json_encode($result));
280
-        return $response;
281
-    }
282
-
283
-    /**
284
-     * Returns an editing form for a given field name of a Content object.
285
-     * Argument $fieldNameAndPath corresponds to the field name to be edited.
286
-     * Important to notice it can contains a path, e.g. metadata.title and therefore must be analysed.
287
-     *
288
-     * Possible values for $matches, refer to method "updateAction".
289
-     *
290
-     * @param string $fieldNameAndPath
291
-     * @param array $matches
292
-     * @param bool $hasRecursiveSelection
293
-     * @throws \Exception
294
-     */
295
-    public function editAction($fieldNameAndPath, array $matches = [], $hasRecursiveSelection = false): ResponseInterface
296
-    {
297
-
298
-        // Instantiate the Matcher object according different rules.
299
-        $matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
300
-
301
-        // Fetch objects via the Content Service.
302
-        $contentService = $this->getContentService()->findBy($matcher);
303
-
304
-        $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath);
305
-        $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath);
306
-
307
-        $fieldType = Tca::table($dataType)->field($fieldName)->getType();
308
-        $this->view->assign('fieldType', ucfirst($fieldType));
309
-        $this->view->assign('dataType', $dataType);
310
-        $this->view->assign('fieldName', $fieldName);
311
-        $this->view->assign('matches', $matches);
312
-        $this->view->assign('fieldNameAndPath', $fieldNameAndPath);
313
-        $this->view->assign('numberOfObjects', $contentService->getNumberOfObjects());
314
-        $this->view->assign('hasRecursiveSelection', $hasRecursiveSelection);
315
-        $this->view->assign('editWholeSelection', empty($matches['uid'])); // necessary??
316
-
317
-        // Fetch content and its relations.
318
-        if ($fieldType === FieldType::MULTISELECT) {
319
-
320
-            $object = ContentRepositoryFactory::getInstance()->findOneBy($matcher);
321
-            $identifier = $this->getContentObjectResolver()->getValue($object, $fieldNameAndPath, 'uid');
322
-            $dataType = $this->getContentObjectResolver()->getDataType($object, $fieldNameAndPath);
323
-
324
-            $content = ContentRepositoryFactory::getInstance($dataType)->findByUid($identifier);
325
-
326
-            // Makes sure the object was retrieved. Security!
327
-            if (!$content) {
328
-                $message = sprintf('I could not retrieved content object of type "%s" with identifier %s.', $dataType, $identifier);
329
-                throw new \Exception($message, 1402350182);
330
-            }
331
-
332
-            $relatedDataType = Tca::table($dataType)->field($fieldName)->getForeignTable();
333
-
334
-            // Initialize the matcher object.
335
-            /** @var Matcher $matcher */
336
-            $matcher = GeneralUtility::makeInstance(Matcher::class, [], $relatedDataType);
337
-
338
-            // Default ordering for related data type.
339
-            $defaultOrderings = Tca::table($relatedDataType)->getDefaultOrderings();
340
-            /** @var Order $order */
341
-            $defaultOrder = GeneralUtility::makeInstance(Order::class, $defaultOrderings);
342
-
343
-            // Fetch related contents
344
-            $relatedContents = ContentRepositoryFactory::getInstance($relatedDataType)->findBy($matcher, $defaultOrder);
345
-
346
-            if (Tca::table($dataType)->field($fieldName)->isRenderModeTree()) {
347
-
348
-                $fieldConfiguration = Tca::table($dataType)->field($fieldName)->getConfiguration();
349
-                $parentField = $fieldConfiguration['treeConfig']['parentField'];
350
-
351
-                $flatTree = [];
352
-                foreach ($relatedContents as $node) {
353
-                    $flatTree[$node->getUid()] = array(
354
-                        'item' => $node,
355
-                        'parent' => $node[$parentField] ? $node[$parentField]['uid'] : null,
356
-                    );
357
-                }
358
-
359
-                $tree = [];
360
-
361
-                // If leaves are selected without its parents selected, those are shown as parent
362
-                foreach ($flatTree as $id => &$flatNode) {
363
-                    if (!isset($flatTree[$flatNode['parent']])) {
364
-                        $flatNode['parent'] = null;
365
-                    }
366
-                }
367
-
368
-                foreach ($flatTree as $id => &$node) {
369
-                    if ($node['parent'] === null) {
370
-                        $tree[$id] = &$node;
371
-                    } else {
372
-                        $flatTree[$node['parent']]['children'][$id] = &$node;
373
-                    }
374
-                }
375
-
376
-                $relatedContents = $tree;
377
-            }
378
-
379
-            $this->view->assign('content', $content);
380
-            $this->view->assign('relatedContents', $relatedContents);
381
-            $this->view->assign('relatedDataType', $relatedDataType);
382
-            $this->view->assign('relatedContentTitle', Tca::table($relatedDataType)->getTitle());
383
-            $this->view->assign(
384
-                'renderMode',
385
-                Tca::table($dataType)->field($fieldName)->isRenderModeTree() ? FieldType::TREE : null
386
-            );
387
-        }
388
-        return $this->htmlResponse();
389
-    }
390
-
391
-    /**
392
-     * Retrieve Content objects first according to matching criteria and then "delete" them.
393
-     *
394
-     * Possible values for $matches, refer to method "updateAction".
395
-     *
396
-     * @param array $matches
397
-     */
398
-    public function deleteAction(array $matches = [])
399
-    {
400
-
401
-        $matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
402
-
403
-        // Fetch objects via the Content Service.
404
-        $contentService = $this->getContentService()->findBy($matcher);
405
-
406
-        // Compute the label field name of the table.
407
-        $tableTitleField = Tca::table()->getLabelField();
408
-
409
-        // Get result object for storing data along the processing.
410
-        $result = $this->getJsonResult();
411
-        $result->setNumberOfObjects($contentService->getNumberOfObjects());
412
-
413
-        foreach ($contentService->getObjects() as $object) {
414
-
415
-            // Store the first object, so that the delete message can be more explicit when deleting only one record.
416
-            if ($contentService->getNumberOfObjects() === 1) {
417
-                $tableTitleValue = $object[$tableTitleField];
418
-                $processedObjectData = array(
419
-                    'uid' => $object->getUid(),
420
-                    'name' => $tableTitleValue,
421
-                );
422
-                $result->setProcessedObject($processedObjectData);
423
-            }
424
-
425
-            // Properly delete object.
426
-            ContentRepositoryFactory::getInstance()->remove($object);
427
-
428
-            // Get the possible error messages and store them.
429
-            $errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
430
-            $result->addErrorMessages($errorMessages);
431
-        }
432
-
433
-        $response = $this->responseFactory->createResponse()
434
-            ->withHeader('Content-Type', 'application/json; charset=utf-8');
435
-        $response->getBody()->write(json_encode($result));
436
-        return $response;
437
-    }
438
-
439
-    /**
440
-     * Retrieve Content objects first according to matching criteria and then "copy" them.
441
-     *
442
-     * Possible values for $matches, refer to method "updateAction".
443
-     *
444
-     * @param string $target
445
-     * @param array $matches
446
-     * @throws \Exception
447
-     * @return string
448
-     */
449
-    public function copyAction($target, array $matches = [])
450
-    {
451
-        // @todo
452
-        throw new \Exception('Not yet implemented', 1410192546);
453
-    }
454
-
455
-    /**
456
-     * Retrieve Content objects from the Clipboard then "copy" them according to the target.
457
-     *
458
-     * @param string $target
459
-     * @throws \Exception
460
-     */
461
-    public function copyClipboardAction($target)
462
-    {
463
-
464
-        // Retrieve matcher object from clipboard.
465
-        $matcher = $this->getClipboardService()->getMatcher();
466
-
467
-        // Fetch objects via the Content Service.
468
-        $contentService = $this->getContentService()->findBy($matcher);
469
-
470
-        // Compute the label field name of the table.
471
-        $tableTitleField = Tca::table()->getLabelField();
472
-
473
-        // Get result object for storing data along the processing.
474
-        $result = $this->getJsonResult();
475
-        $result->setNumberOfObjects($contentService->getNumberOfObjects());
476
-
477
-        foreach ($contentService->getObjects() as $object) {
478
-
479
-            // Store the first object, so that the "action" message can be more explicit when deleting only one record.
480
-            if ($contentService->getNumberOfObjects() === 1) {
481
-                $tableTitleValue = $object[$tableTitleField];
482
-                $processedObjectData = array(
483
-                    'uid' => $object->getUid(),
484
-                    'name' => $tableTitleValue,
485
-                );
486
-                $result->setProcessedObject($processedObjectData);
487
-            }
488
-
489
-            // Work out the object.
490
-            ContentRepositoryFactory::getInstance()->copy($object, $target);
491
-
492
-            // Get the possible error messages and store them.
493
-            $errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
494
-            $result->addErrorMessages($errorMessages);
495
-        }
496
-
497
-        // Flush Clipboard if told so.
498
-        if (GeneralUtility::_GP('flushClipboard')) {
499
-            $this->getClipboardService()->flush();
500
-        }
501
-
502
-        $response = $this->responseFactory->createResponse()
503
-            ->withHeader('Content-Type', 'application/json; charset=utf-8');
504
-        $response->getBody()->write(json_encode($result));
505
-        return $response;
506
-    }
507
-
508
-    /**
509
-     * Retrieve Content objects first according to matching criteria and then "move" them.
510
-     *
511
-     * Possible values for $matches, refer to method "updateAction".
512
-     *
513
-     * @param string $target
514
-     * @param array $matches
515
-     */
516
-    public function moveAction($target, array $matches = [])
517
-    {
518
-
519
-        $matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
520
-
521
-        // Fetch objects via the Content Service.
522
-        $contentService = $this->getContentService()->findBy($matcher);
523
-
524
-        // Compute the label field name of the table.
525
-        $tableTitleField = Tca::table()->getLabelField();
526
-
527
-        // Get result object for storing data along the processing.
528
-        $result = $this->getJsonResult();
529
-        $result->setNumberOfObjects($contentService->getNumberOfObjects());
530
-
531
-        foreach ($contentService->getObjects() as $object) {
532
-
533
-            // Store the first object, so that the "action" message can be more explicit when deleting only one record.
534
-            if ($contentService->getNumberOfObjects() === 1) {
535
-                $tableTitleValue = $object[$tableTitleField];
536
-                $processedObjectData = array(
537
-                    'uid' => $object->getUid(),
538
-                    'name' => $tableTitleValue,
539
-                );
540
-                $result->setProcessedObject($processedObjectData);
541
-            }
542
-
543
-            // Work out the object.
544
-            ContentRepositoryFactory::getInstance()->move($object, $target);
545
-
546
-            // Get the possible error messages and store them.
547
-            $errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
548
-            $result->addErrorMessages($errorMessages);
549
-        }
550
-
551
-        $response = $this->responseFactory->createResponse()
552
-            ->withHeader('Content-Type', 'application/json; charset=utf-8');
553
-        $response->getBody()->write(json_encode($result));
554
-        return $response;
555
-    }
556
-
557
-    /**
558
-     * Retrieve Content objects from the Clipboard then "move" them according to the target.
559
-     *
560
-     * @param string $target
561
-     */
562
-    public function moveClipboardAction($target)
563
-    {
564
-
565
-        // Retrieve matcher object from clipboard.
566
-        $matcher = $this->getClipboardService()->getMatcher();
567
-
568
-        // Fetch objects via the Content Service.
569
-        $contentService = $this->getContentService()->findBy($matcher);
570
-
571
-        // Compute the label field name of the table.
572
-        $tableTitleField = Tca::table()->getLabelField();
573
-
574
-        // Get result object for storing data along the processing.
575
-        $result = $this->getJsonResult();
576
-        $result->setNumberOfObjects($contentService->getNumberOfObjects());
577
-
578
-        foreach ($contentService->getObjects() as $object) {
579
-
580
-            // Store the first object, so that the "action" message can be more explicit when deleting only one record.
581
-            if ($contentService->getNumberOfObjects() === 1) {
582
-                $tableTitleValue = $object[$tableTitleField];
583
-                $processedObjectData = array(
584
-                    'uid' => $object->getUid(),
585
-                    'name' => $tableTitleValue,
586
-                );
587
-                $result->setProcessedObject($processedObjectData);
588
-            }
589
-
590
-            // Work out the object.
591
-            ContentRepositoryFactory::getInstance()->move($object, $target);
592
-
593
-            // Get the possible error messages and store them.
594
-            $errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
595
-            $result->addErrorMessages($errorMessages);
596
-        }
597
-
598
-        // Flush Clipboard if told so.
599
-        if (GeneralUtility::_GP('flushClipboard')) {
600
-            $this->getClipboardService()->flush();
601
-        }
602
-
603
-        $response = $this->responseFactory->createResponse()
604
-            ->withHeader('Content-Type', 'application/json; charset=utf-8');
605
-        $response->getBody()->write(json_encode($result));
606
-        return $response;
607
-    }
608
-
609
-    /**
610
-     * Retrieve Content objects first according to matching criteria and then "localize" them.
611
-     *
612
-     * Possible values for $matches, refer to method "updateAction".
613
-     *
614
-     * @param string $fieldNameAndPath
615
-     * @param array $matches
616
-     * @param int $language
617
-     * @throws \Exception
618
-     */
619
-    public function localizeAction($fieldNameAndPath, array $matches = [], $language = 0)
620
-    {
621
-
622
-        $matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
623
-
624
-        // Fetch objects via the Content Service.
625
-        $contentService = $this->getContentService()->findBy($matcher);
626
-
627
-        // Get result object for storing data along the processing.
628
-        $result = $this->getJsonResult();
629
-        $result->setNumberOfObjects($contentService->getNumberOfObjects());
630
-
631
-        foreach ($contentService->getObjects() as $object) {
632
-
633
-            $identifier = $this->getContentObjectResolver()->getValue($object, $fieldNameAndPath, 'uid');
634
-            $dataType = $this->getContentObjectResolver()->getDataType($object, $fieldNameAndPath);
635
-
636
-            // Fetch the source object to be localized.
637
-            /** @var Content $content */
638
-            $content = ContentRepositoryFactory::getInstance($dataType)->findByIdentifier($identifier);
639
-
640
-            // Makes sure the object was retrieved. Security!
641
-            if (!$content) {
642
-                $message = sprintf('Something went wrong when retrieving content "%s" with identifier "%s".', $dataType, $identifier);
643
-                throw new \Exception($message, 1412343097);
644
-            }
645
-
646
-            // Handover the localization to the Repository.
647
-            ContentRepositoryFactory::getInstance($dataType)->localize($content, $language);
648
-
649
-            // Get the possible error messages and store them.
650
-            $errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
651
-
652
-            // Redirect to TCEForm so that the BE User can do its job!
653
-            if ($contentService->getNumberOfObjects() === 1) {
654
-
655
-                if (!empty($errorMessages)) {
656
-                    $message = sprintf('Something went wrong when localizing content "%s" with identifier "%s". <br/>%s',
657
-                        $dataType,
658
-                        $identifier,
659
-                        implode('<br/>', $errorMessages)
660
-                    );
661
-                    throw new \Exception($message, 1412343098);
662
-                }
663
-
664
-                $localizedContent = $this->getLanguageService()->getLocalizedContent($content, $language);
665
-                if (empty($localizedContent)) {
666
-                    $message = sprintf('Oups! I could not retrieve localized content of type "%s" with identifier "%s"',
667
-                        $content->getDataType(),
668
-                        $content->getUid()
669
-                    );
670
-                    throw new \Exception($message, 1412343099);
671
-                }
672
-
673
-                /** @var EditUri $uri */
674
-                $uriRenderer = GeneralUtility::makeInstance(EditUri::class);
675
-                $uri = $uriRenderer->render($localizedContent);
676
-                HttpUtility::redirect($uri);
677
-                break; // no need to further continue
678
-            }
679
-
680
-            $result->addErrorMessages($errorMessages);
681
-        }
682
-
683
-        $response = $this->responseFactory->createResponse()
684
-            ->withHeader('Content-Type', 'application/json; charset=utf-8');
685
-        $response->getBody()->write(json_encode($result));
686
-        return $response;
687
-    }
688
-
689
-    /**
690
-     * Get the Vidi Module Loader.
691
-     *
692
-     * @return ContentService
693
-     */
694
-    protected function getContentService()
695
-    {
696
-        return GeneralUtility::makeInstance(ContentService::class);
697
-    }
698
-
699
-    /**
700
-     * @return ContentObjectResolver
701
-     */
702
-    protected function getContentObjectResolver()
703
-    {
704
-        return GeneralUtility::makeInstance(ContentObjectResolver::class);
705
-    }
706
-
707
-    /**
708
-     * @return FieldPathResolver
709
-     */
710
-    protected function getFieldPathResolver()
711
-    {
712
-        return GeneralUtility::makeInstance(FieldPathResolver::class);
713
-    }
714
-
715
-    /**
716
-     * @return JsonResult|object
717
-     */
718
-    protected function getJsonResult()
719
-    {
720
-        return GeneralUtility::makeInstance(JsonResult::class);
721
-    }
722
-
723
-    /**
724
-     * Signal that is called for post-processing content data send to the server for update.
725
-     *
726
-     * @param Content $contentObject
727
-     * @param $fieldNameAndPath
728
-     * @param $contentData
729
-     * @param $counter
730
-     * @param $savingBehavior
731
-     * @param $language
732
-     * @return ProcessContentDataSignalArguments
733
-     */
734
-    protected function emitProcessContentDataSignal(Content $contentObject, $fieldNameAndPath, $contentData, $counter, $savingBehavior, $language)
735
-    {
736
-
737
-        /** @var ProcessContentDataSignalArguments $signalArguments */
738
-        $signalArguments = GeneralUtility::makeInstance(ProcessContentDataSignalArguments::class);
739
-        $signalArguments->setContentObject($contentObject)
740
-            ->setFieldNameAndPath($fieldNameAndPath)
741
-            ->setContentData($contentData)
742
-            ->setCounter($counter)
743
-            ->setSavingBehavior($savingBehavior)
744
-            ->setLanguage($language);
745
-
746
-        $signalResult = $this->getSignalSlotDispatcher()->dispatch('Fab\Vidi\Controller\Backend\ContentController', 'processContentData', array($signalArguments));
747
-        return $signalResult[0];
748
-    }
749
-
750
-    /**
751
-     * Get the SignalSlot dispatcher.
752
-     *
753
-     * @return Dispatcher
754
-     */
755
-    protected function getSignalSlotDispatcher()
756
-    {
757
-        return GeneralUtility::makeInstance(Dispatcher::class);
758
-    }
759
-
760
-    /**
761
-     * Get the Clipboard service.
762
-     *
763
-     * @return ClipboardService
764
-     */
765
-    protected function getClipboardService()
766
-    {
767
-        return GeneralUtility::makeInstance(ClipboardService::class);
768
-    }
769
-
770
-    /**
771
-     * @return LanguageService
772
-     */
773
-    protected function getLanguageService()
774
-    {
775
-        return GeneralUtility::makeInstance(LanguageService::class);
776
-    }
777
-
778
-    /**
779
-     * Get the Vidi Module Loader.
780
-     *
781
-     * @return ModuleLoader
782
-     */
783
-    protected function getModuleLoader()
784
-    {
785
-        return GeneralUtility::makeInstance(ModuleLoader::class);
786
-    }
787
-
788
-    public function injectSelectionRepository(SelectionRepository $selectionRepository): void
789
-    {
790
-        $this->selectionRepository = $selectionRepository;
791
-    }
47
+	/**
48
+	 * Initialize every action.
49
+	 */
50
+	public function initializeAction()
51
+	{
52
+		$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
53
+		$pageRenderer->addInlineLanguageLabelFile('EXT:vidi/Resources/Private/Language/locallang.xlf');
54
+
55
+		// Configure property mapping to retrieve the file object.
56
+		if ($this->arguments->hasArgument('columns')) {
57
+
58
+			/** @var CsvToArrayConverter $typeConverter */
59
+			$typeConverter = GeneralUtility::makeInstance(CsvToArrayConverter::class);
60
+
61
+			$propertyMappingConfiguration = $this->arguments->getArgument('columns')->getPropertyMappingConfiguration();
62
+			$propertyMappingConfiguration->setTypeConverter($typeConverter);
63
+		}
64
+	}
65
+
66
+	/**
67
+	 * List action for this controller.
68
+	 *
69
+	 * @return void
70
+	 */
71
+	public function indexAction(): ResponseInterface
72
+	{
73
+		$dataType = $this->getModuleLoader()->getDataType();
74
+		$selections = $this->selectionRepository->findByDataTypeForCurrentBackendUser($dataType);
75
+		$this->view->assign('selections', $selections);
76
+
77
+		$columns = Tca::grid()->getFields();
78
+		$this->view->assign('columns', $columns);
79
+		$this->view->assign('numberOfColumns', count($columns));
80
+		return $this->htmlResponse();
81
+	}
82
+
83
+	/**
84
+	 * List Row action for this controller. Output a json list of contents
85
+	 *
86
+	 * @param array $columns corresponds to columns to be rendered.
87
+	 * @param array $matches
88
+	 * @Validate("Fab\Vidi\Domain\Validator\ColumnsValidator", param="columns")
89
+	 * @Validate("Fab\Vidi\Domain\Validator\MatchesValidator", param="matches")
90
+	 * @return void
91
+	 */
92
+	public function listAction(array $columns = [], $matches = []): ResponseInterface
93
+	{
94
+		// Initialize some objects related to the query.
95
+		$matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
96
+		$order = OrderObjectFactory::getInstance()->getOrder();
97
+		$pager = PagerObjectFactory::getInstance()->getPager();
98
+
99
+		// Fetch objects via the Content Service.
100
+		$contentService = $this->getContentService()->findBy($matcher, $order, $pager->getLimit(), $pager->getOffset());
101
+		$pager->setCount($contentService->getNumberOfObjects());
102
+
103
+		// Assign values.
104
+		$this->view->assign('columns', $columns);
105
+		$this->view->assign('objects', $contentService->getObjects());
106
+		$this->view->assign('numberOfObjects', $contentService->getNumberOfObjects());
107
+		$this->view->assign('pager', $pager);
108
+
109
+		$this->view->assign('response', $this->responseFactory->createResponse());
110
+		return $this->htmlResponse();
111
+	}
112
+
113
+	/**
114
+	 * Retrieve Content objects first according to matching criteria and then "update" them.
115
+	 * Important to notice the field name can contains a path, e.g. metadata.title and therefore must be analysed.
116
+	 *
117
+	 * Possible values for $matches:
118
+	 * -----------------------------
119
+	 *
120
+	 * $matches = array(uid => 1), will be taken as $query->equals
121
+	 * $matches = array(uid => 1,2,3), will be taken as $query->in
122
+	 * $matches = array(field_name1 => bar, field_name2 => bax), will be separated by AND.
123
+	 *
124
+	 * Possible values for $content:
125
+	 * -----------------------------
126
+	 *
127
+	 * $content = array(field_name => bar)
128
+	 * $content = array(field_name => array(value1, value2)) <-- will be CSV converted by "value1,value2"
129
+	 *
130
+	 * @param string $fieldNameAndPath
131
+	 * @param array $content
132
+	 * @param array $matches
133
+	 * @param string $savingBehavior
134
+	 * @param int $language
135
+	 * @param array $columns
136
+	 * @throws InvalidKeyInArrayException
137
+	 */
138
+	public function updateAction($fieldNameAndPath, array $content, array $matches = [], $savingBehavior = SavingBehavior::REPLACE, $language = 0, $columns = [])
139
+	{
140
+
141
+		// Instantiate the Matcher object according different rules.
142
+		$matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
143
+		$order = OrderObjectFactory::getInstance()->getOrder();
144
+
145
+		// Fetch objects via the Content Service.
146
+		$contentService = $this->getContentService()->findBy($matcher, $order);
147
+
148
+		// Get the real field that is going to be updated.
149
+		$updatedFieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath);
150
+
151
+		// Get result object for storing data along the processing.
152
+		$result = $this->getJsonResult();
153
+		$result->setNumberOfObjects($contentService->getNumberOfObjects());
154
+
155
+		foreach ($contentService->getObjects() as $index => $object) {
156
+
157
+			$identifier = $this->getContentObjectResolver()->getValue($object, $fieldNameAndPath, 'uid', $language);
158
+
159
+			// It could be the identifier is not found because the translation
160
+			// of the record does not yet exist when mass-editing
161
+			if ((int)$identifier <= 0) {
162
+				continue;
163
+			}
164
+
165
+			$dataType = $this->getContentObjectResolver()->getDataType($object, $fieldNameAndPath);
166
+
167
+			$signalResult = $this->emitProcessContentDataSignal($object, $fieldNameAndPath, $content, $index + 1, $savingBehavior, $language);
168
+			$contentData = $signalResult->getContentData();
169
+
170
+			// Add identifier to content data, required by TCEMain.
171
+			$contentData['uid'] = $identifier;
172
+
173
+			/** @var Content $dataObject */
174
+			$dataObject = GeneralUtility::makeInstance(Content::class, $dataType, $contentData);
175
+
176
+			// Properly update object.
177
+			ContentRepositoryFactory::getInstance($dataType)->update($dataObject);
178
+
179
+			// Get the possible error messages and store them.
180
+			$errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
181
+			$result->addErrorMessages($errorMessages);
182
+
183
+			// We only want to see the detail result if there is one object updated.
184
+			// Required for inline editing + it will display some useful info on the GUI in the flash messages.
185
+			if ($contentService->getNumberOfObjects() === 1) {
186
+
187
+				// Fetch the updated object from repository.
188
+				$updatedObject = ContentRepositoryFactory::getInstance()->findByUid($object->getUid());
189
+
190
+				// Re-fetch the updated result.
191
+				$updatedResult = $this->getContentObjectResolver()->getValue($updatedObject, $fieldNameAndPath, $updatedFieldName, $language);
192
+				if (is_array($updatedResult)) {
193
+					$_updatedResult = []; // reset result set.
194
+
195
+					/** @var Content $contentObject */
196
+					foreach ($updatedResult as $contentObject) {
197
+						$labelField = Tca::table($contentObject)->getLabelField();
198
+						$values = array(
199
+							'uid' => $contentObject->getUid(),
200
+							'name' => $contentObject[$labelField],
201
+						);
202
+						$_updatedResult[] = $values;
203
+					}
204
+
205
+					$updatedResult = $_updatedResult;
206
+				}
207
+
208
+				$labelField = Tca::table($object)->getLabelField();
209
+
210
+				$processedObjectData = array(
211
+					'uid' => $object->getUid(),
212
+					'name' => $object[$labelField],
213
+					'updatedField' => $fieldNameAndPath,
214
+					'updatedValue' => $updatedResult,
215
+				);
216
+				$result->setProcessedObject($processedObjectData);
217
+
218
+				if (!empty($columns)) {
219
+					/** @var Row $row */
220
+					$row = GeneralUtility::makeInstance(Row::class, $columns);
221
+					$result->setRow($row->render($updatedObject));
222
+				}
223
+			}
224
+		}
225
+
226
+		$response = $this->responseFactory->createResponse()
227
+			->withHeader('Content-Type', 'application/json; charset=utf-8');
228
+		$response->getBody()->write(json_encode($result));
229
+		return $response;
230
+	}
231
+
232
+	/**
233
+	 * Set the sorting of a record giving the previous object.
234
+	 *
235
+	 * @param array $matches
236
+	 * @param int $previousIdentifier
237
+	 */
238
+	public function sortAction(array $matches = [], $previousIdentifier = null)
239
+	{
240
+
241
+		$matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
242
+
243
+		// Fetch objects via the Content Service.
244
+		$contentService = $this->getContentService()->findBy($matcher);
245
+
246
+		// Compute the label field name of the table.
247
+		$tableTitleField = Tca::table()->getLabelField();
248
+
249
+		// Get result object for storing data along the processing.
250
+		$result = $this->getJsonResult();
251
+		$result->setNumberOfObjects($contentService->getNumberOfObjects());
252
+
253
+		foreach ($contentService->getObjects() as $object) {
254
+
255
+			// Store the first object, so that the "action" message can be more explicit when deleting only one record.
256
+			if ($contentService->getNumberOfObjects() === 1) {
257
+				$tableTitleValue = $object[$tableTitleField];
258
+				$processedObjectData = array(
259
+					'uid' => $object->getUid(),
260
+					'name' => $tableTitleValue,
261
+				);
262
+				$result->setProcessedObject($processedObjectData);
263
+			}
264
+
265
+			// The $target corresponds to the pid to move the records to.
266
+			// It can also be a negative value in case of sorting. The negative value would be the uid of its predecessor.
267
+			$target = is_null($previousIdentifier) ? $object->getPid() : (-(int)$previousIdentifier);
268
+
269
+			// Work out the object.
270
+			ContentRepositoryFactory::getInstance()->move($object, $target);
271
+
272
+			// Get the possible error messages and store them.
273
+			$errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
274
+			$result->addErrorMessages($errorMessages);
275
+		}
276
+
277
+		$response = $this->responseFactory->createResponse()
278
+			->withHeader('Content-Type', 'application/json; charset=utf-8');
279
+		$response->getBody()->write(json_encode($result));
280
+		return $response;
281
+	}
282
+
283
+	/**
284
+	 * Returns an editing form for a given field name of a Content object.
285
+	 * Argument $fieldNameAndPath corresponds to the field name to be edited.
286
+	 * Important to notice it can contains a path, e.g. metadata.title and therefore must be analysed.
287
+	 *
288
+	 * Possible values for $matches, refer to method "updateAction".
289
+	 *
290
+	 * @param string $fieldNameAndPath
291
+	 * @param array $matches
292
+	 * @param bool $hasRecursiveSelection
293
+	 * @throws \Exception
294
+	 */
295
+	public function editAction($fieldNameAndPath, array $matches = [], $hasRecursiveSelection = false): ResponseInterface
296
+	{
297
+
298
+		// Instantiate the Matcher object according different rules.
299
+		$matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
300
+
301
+		// Fetch objects via the Content Service.
302
+		$contentService = $this->getContentService()->findBy($matcher);
303
+
304
+		$dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath);
305
+		$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath);
306
+
307
+		$fieldType = Tca::table($dataType)->field($fieldName)->getType();
308
+		$this->view->assign('fieldType', ucfirst($fieldType));
309
+		$this->view->assign('dataType', $dataType);
310
+		$this->view->assign('fieldName', $fieldName);
311
+		$this->view->assign('matches', $matches);
312
+		$this->view->assign('fieldNameAndPath', $fieldNameAndPath);
313
+		$this->view->assign('numberOfObjects', $contentService->getNumberOfObjects());
314
+		$this->view->assign('hasRecursiveSelection', $hasRecursiveSelection);
315
+		$this->view->assign('editWholeSelection', empty($matches['uid'])); // necessary??
316
+
317
+		// Fetch content and its relations.
318
+		if ($fieldType === FieldType::MULTISELECT) {
319
+
320
+			$object = ContentRepositoryFactory::getInstance()->findOneBy($matcher);
321
+			$identifier = $this->getContentObjectResolver()->getValue($object, $fieldNameAndPath, 'uid');
322
+			$dataType = $this->getContentObjectResolver()->getDataType($object, $fieldNameAndPath);
323
+
324
+			$content = ContentRepositoryFactory::getInstance($dataType)->findByUid($identifier);
325
+
326
+			// Makes sure the object was retrieved. Security!
327
+			if (!$content) {
328
+				$message = sprintf('I could not retrieved content object of type "%s" with identifier %s.', $dataType, $identifier);
329
+				throw new \Exception($message, 1402350182);
330
+			}
331
+
332
+			$relatedDataType = Tca::table($dataType)->field($fieldName)->getForeignTable();
333
+
334
+			// Initialize the matcher object.
335
+			/** @var Matcher $matcher */
336
+			$matcher = GeneralUtility::makeInstance(Matcher::class, [], $relatedDataType);
337
+
338
+			// Default ordering for related data type.
339
+			$defaultOrderings = Tca::table($relatedDataType)->getDefaultOrderings();
340
+			/** @var Order $order */
341
+			$defaultOrder = GeneralUtility::makeInstance(Order::class, $defaultOrderings);
342
+
343
+			// Fetch related contents
344
+			$relatedContents = ContentRepositoryFactory::getInstance($relatedDataType)->findBy($matcher, $defaultOrder);
345
+
346
+			if (Tca::table($dataType)->field($fieldName)->isRenderModeTree()) {
347
+
348
+				$fieldConfiguration = Tca::table($dataType)->field($fieldName)->getConfiguration();
349
+				$parentField = $fieldConfiguration['treeConfig']['parentField'];
350
+
351
+				$flatTree = [];
352
+				foreach ($relatedContents as $node) {
353
+					$flatTree[$node->getUid()] = array(
354
+						'item' => $node,
355
+						'parent' => $node[$parentField] ? $node[$parentField]['uid'] : null,
356
+					);
357
+				}
358
+
359
+				$tree = [];
360
+
361
+				// If leaves are selected without its parents selected, those are shown as parent
362
+				foreach ($flatTree as $id => &$flatNode) {
363
+					if (!isset($flatTree[$flatNode['parent']])) {
364
+						$flatNode['parent'] = null;
365
+					}
366
+				}
367
+
368
+				foreach ($flatTree as $id => &$node) {
369
+					if ($node['parent'] === null) {
370
+						$tree[$id] = &$node;
371
+					} else {
372
+						$flatTree[$node['parent']]['children'][$id] = &$node;
373
+					}
374
+				}
375
+
376
+				$relatedContents = $tree;
377
+			}
378
+
379
+			$this->view->assign('content', $content);
380
+			$this->view->assign('relatedContents', $relatedContents);
381
+			$this->view->assign('relatedDataType', $relatedDataType);
382
+			$this->view->assign('relatedContentTitle', Tca::table($relatedDataType)->getTitle());
383
+			$this->view->assign(
384
+				'renderMode',
385
+				Tca::table($dataType)->field($fieldName)->isRenderModeTree() ? FieldType::TREE : null
386
+			);
387
+		}
388
+		return $this->htmlResponse();
389
+	}
390
+
391
+	/**
392
+	 * Retrieve Content objects first according to matching criteria and then "delete" them.
393
+	 *
394
+	 * Possible values for $matches, refer to method "updateAction".
395
+	 *
396
+	 * @param array $matches
397
+	 */
398
+	public function deleteAction(array $matches = [])
399
+	{
400
+
401
+		$matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
402
+
403
+		// Fetch objects via the Content Service.
404
+		$contentService = $this->getContentService()->findBy($matcher);
405
+
406
+		// Compute the label field name of the table.
407
+		$tableTitleField = Tca::table()->getLabelField();
408
+
409
+		// Get result object for storing data along the processing.
410
+		$result = $this->getJsonResult();
411
+		$result->setNumberOfObjects($contentService->getNumberOfObjects());
412
+
413
+		foreach ($contentService->getObjects() as $object) {
414
+
415
+			// Store the first object, so that the delete message can be more explicit when deleting only one record.
416
+			if ($contentService->getNumberOfObjects() === 1) {
417
+				$tableTitleValue = $object[$tableTitleField];
418
+				$processedObjectData = array(
419
+					'uid' => $object->getUid(),
420
+					'name' => $tableTitleValue,
421
+				);
422
+				$result->setProcessedObject($processedObjectData);
423
+			}
424
+
425
+			// Properly delete object.
426
+			ContentRepositoryFactory::getInstance()->remove($object);
427
+
428
+			// Get the possible error messages and store them.
429
+			$errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
430
+			$result->addErrorMessages($errorMessages);
431
+		}
432
+
433
+		$response = $this->responseFactory->createResponse()
434
+			->withHeader('Content-Type', 'application/json; charset=utf-8');
435
+		$response->getBody()->write(json_encode($result));
436
+		return $response;
437
+	}
438
+
439
+	/**
440
+	 * Retrieve Content objects first according to matching criteria and then "copy" them.
441
+	 *
442
+	 * Possible values for $matches, refer to method "updateAction".
443
+	 *
444
+	 * @param string $target
445
+	 * @param array $matches
446
+	 * @throws \Exception
447
+	 * @return string
448
+	 */
449
+	public function copyAction($target, array $matches = [])
450
+	{
451
+		// @todo
452
+		throw new \Exception('Not yet implemented', 1410192546);
453
+	}
454
+
455
+	/**
456
+	 * Retrieve Content objects from the Clipboard then "copy" them according to the target.
457
+	 *
458
+	 * @param string $target
459
+	 * @throws \Exception
460
+	 */
461
+	public function copyClipboardAction($target)
462
+	{
463
+
464
+		// Retrieve matcher object from clipboard.
465
+		$matcher = $this->getClipboardService()->getMatcher();
466
+
467
+		// Fetch objects via the Content Service.
468
+		$contentService = $this->getContentService()->findBy($matcher);
469
+
470
+		// Compute the label field name of the table.
471
+		$tableTitleField = Tca::table()->getLabelField();
472
+
473
+		// Get result object for storing data along the processing.
474
+		$result = $this->getJsonResult();
475
+		$result->setNumberOfObjects($contentService->getNumberOfObjects());
476
+
477
+		foreach ($contentService->getObjects() as $object) {
478
+
479
+			// Store the first object, so that the "action" message can be more explicit when deleting only one record.
480
+			if ($contentService->getNumberOfObjects() === 1) {
481
+				$tableTitleValue = $object[$tableTitleField];
482
+				$processedObjectData = array(
483
+					'uid' => $object->getUid(),
484
+					'name' => $tableTitleValue,
485
+				);
486
+				$result->setProcessedObject($processedObjectData);
487
+			}
488
+
489
+			// Work out the object.
490
+			ContentRepositoryFactory::getInstance()->copy($object, $target);
491
+
492
+			// Get the possible error messages and store them.
493
+			$errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
494
+			$result->addErrorMessages($errorMessages);
495
+		}
496
+
497
+		// Flush Clipboard if told so.
498
+		if (GeneralUtility::_GP('flushClipboard')) {
499
+			$this->getClipboardService()->flush();
500
+		}
501
+
502
+		$response = $this->responseFactory->createResponse()
503
+			->withHeader('Content-Type', 'application/json; charset=utf-8');
504
+		$response->getBody()->write(json_encode($result));
505
+		return $response;
506
+	}
507
+
508
+	/**
509
+	 * Retrieve Content objects first according to matching criteria and then "move" them.
510
+	 *
511
+	 * Possible values for $matches, refer to method "updateAction".
512
+	 *
513
+	 * @param string $target
514
+	 * @param array $matches
515
+	 */
516
+	public function moveAction($target, array $matches = [])
517
+	{
518
+
519
+		$matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
520
+
521
+		// Fetch objects via the Content Service.
522
+		$contentService = $this->getContentService()->findBy($matcher);
523
+
524
+		// Compute the label field name of the table.
525
+		$tableTitleField = Tca::table()->getLabelField();
526
+
527
+		// Get result object for storing data along the processing.
528
+		$result = $this->getJsonResult();
529
+		$result->setNumberOfObjects($contentService->getNumberOfObjects());
530
+
531
+		foreach ($contentService->getObjects() as $object) {
532
+
533
+			// Store the first object, so that the "action" message can be more explicit when deleting only one record.
534
+			if ($contentService->getNumberOfObjects() === 1) {
535
+				$tableTitleValue = $object[$tableTitleField];
536
+				$processedObjectData = array(
537
+					'uid' => $object->getUid(),
538
+					'name' => $tableTitleValue,
539
+				);
540
+				$result->setProcessedObject($processedObjectData);
541
+			}
542
+
543
+			// Work out the object.
544
+			ContentRepositoryFactory::getInstance()->move($object, $target);
545
+
546
+			// Get the possible error messages and store them.
547
+			$errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
548
+			$result->addErrorMessages($errorMessages);
549
+		}
550
+
551
+		$response = $this->responseFactory->createResponse()
552
+			->withHeader('Content-Type', 'application/json; charset=utf-8');
553
+		$response->getBody()->write(json_encode($result));
554
+		return $response;
555
+	}
556
+
557
+	/**
558
+	 * Retrieve Content objects from the Clipboard then "move" them according to the target.
559
+	 *
560
+	 * @param string $target
561
+	 */
562
+	public function moveClipboardAction($target)
563
+	{
564
+
565
+		// Retrieve matcher object from clipboard.
566
+		$matcher = $this->getClipboardService()->getMatcher();
567
+
568
+		// Fetch objects via the Content Service.
569
+		$contentService = $this->getContentService()->findBy($matcher);
570
+
571
+		// Compute the label field name of the table.
572
+		$tableTitleField = Tca::table()->getLabelField();
573
+
574
+		// Get result object for storing data along the processing.
575
+		$result = $this->getJsonResult();
576
+		$result->setNumberOfObjects($contentService->getNumberOfObjects());
577
+
578
+		foreach ($contentService->getObjects() as $object) {
579
+
580
+			// Store the first object, so that the "action" message can be more explicit when deleting only one record.
581
+			if ($contentService->getNumberOfObjects() === 1) {
582
+				$tableTitleValue = $object[$tableTitleField];
583
+				$processedObjectData = array(
584
+					'uid' => $object->getUid(),
585
+					'name' => $tableTitleValue,
586
+				);
587
+				$result->setProcessedObject($processedObjectData);
588
+			}
589
+
590
+			// Work out the object.
591
+			ContentRepositoryFactory::getInstance()->move($object, $target);
592
+
593
+			// Get the possible error messages and store them.
594
+			$errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
595
+			$result->addErrorMessages($errorMessages);
596
+		}
597
+
598
+		// Flush Clipboard if told so.
599
+		if (GeneralUtility::_GP('flushClipboard')) {
600
+			$this->getClipboardService()->flush();
601
+		}
602
+
603
+		$response = $this->responseFactory->createResponse()
604
+			->withHeader('Content-Type', 'application/json; charset=utf-8');
605
+		$response->getBody()->write(json_encode($result));
606
+		return $response;
607
+	}
608
+
609
+	/**
610
+	 * Retrieve Content objects first according to matching criteria and then "localize" them.
611
+	 *
612
+	 * Possible values for $matches, refer to method "updateAction".
613
+	 *
614
+	 * @param string $fieldNameAndPath
615
+	 * @param array $matches
616
+	 * @param int $language
617
+	 * @throws \Exception
618
+	 */
619
+	public function localizeAction($fieldNameAndPath, array $matches = [], $language = 0)
620
+	{
621
+
622
+		$matcher = MatcherObjectFactory::getInstance()->getMatcher($matches);
623
+
624
+		// Fetch objects via the Content Service.
625
+		$contentService = $this->getContentService()->findBy($matcher);
626
+
627
+		// Get result object for storing data along the processing.
628
+		$result = $this->getJsonResult();
629
+		$result->setNumberOfObjects($contentService->getNumberOfObjects());
630
+
631
+		foreach ($contentService->getObjects() as $object) {
632
+
633
+			$identifier = $this->getContentObjectResolver()->getValue($object, $fieldNameAndPath, 'uid');
634
+			$dataType = $this->getContentObjectResolver()->getDataType($object, $fieldNameAndPath);
635
+
636
+			// Fetch the source object to be localized.
637
+			/** @var Content $content */
638
+			$content = ContentRepositoryFactory::getInstance($dataType)->findByIdentifier($identifier);
639
+
640
+			// Makes sure the object was retrieved. Security!
641
+			if (!$content) {
642
+				$message = sprintf('Something went wrong when retrieving content "%s" with identifier "%s".', $dataType, $identifier);
643
+				throw new \Exception($message, 1412343097);
644
+			}
645
+
646
+			// Handover the localization to the Repository.
647
+			ContentRepositoryFactory::getInstance($dataType)->localize($content, $language);
648
+
649
+			// Get the possible error messages and store them.
650
+			$errorMessages = ContentRepositoryFactory::getInstance()->getErrorMessages();
651
+
652
+			// Redirect to TCEForm so that the BE User can do its job!
653
+			if ($contentService->getNumberOfObjects() === 1) {
654
+
655
+				if (!empty($errorMessages)) {
656
+					$message = sprintf('Something went wrong when localizing content "%s" with identifier "%s". <br/>%s',
657
+						$dataType,
658
+						$identifier,
659
+						implode('<br/>', $errorMessages)
660
+					);
661
+					throw new \Exception($message, 1412343098);
662
+				}
663
+
664
+				$localizedContent = $this->getLanguageService()->getLocalizedContent($content, $language);
665
+				if (empty($localizedContent)) {
666
+					$message = sprintf('Oups! I could not retrieve localized content of type "%s" with identifier "%s"',
667
+						$content->getDataType(),
668
+						$content->getUid()
669
+					);
670
+					throw new \Exception($message, 1412343099);
671
+				}
672
+
673
+				/** @var EditUri $uri */
674
+				$uriRenderer = GeneralUtility::makeInstance(EditUri::class);
675
+				$uri = $uriRenderer->render($localizedContent);
676
+				HttpUtility::redirect($uri);
677
+				break; // no need to further continue
678
+			}
679
+
680
+			$result->addErrorMessages($errorMessages);
681
+		}
682
+
683
+		$response = $this->responseFactory->createResponse()
684
+			->withHeader('Content-Type', 'application/json; charset=utf-8');
685
+		$response->getBody()->write(json_encode($result));
686
+		return $response;
687
+	}
688
+
689
+	/**
690
+	 * Get the Vidi Module Loader.
691
+	 *
692
+	 * @return ContentService
693
+	 */
694
+	protected function getContentService()
695
+	{
696
+		return GeneralUtility::makeInstance(ContentService::class);
697
+	}
698
+
699
+	/**
700
+	 * @return ContentObjectResolver
701
+	 */
702
+	protected function getContentObjectResolver()
703
+	{
704
+		return GeneralUtility::makeInstance(ContentObjectResolver::class);
705
+	}
706
+
707
+	/**
708
+	 * @return FieldPathResolver
709
+	 */
710
+	protected function getFieldPathResolver()
711
+	{
712
+		return GeneralUtility::makeInstance(FieldPathResolver::class);
713
+	}
714
+
715
+	/**
716
+	 * @return JsonResult|object
717
+	 */
718
+	protected function getJsonResult()
719
+	{
720
+		return GeneralUtility::makeInstance(JsonResult::class);
721
+	}
722
+
723
+	/**
724
+	 * Signal that is called for post-processing content data send to the server for update.
725
+	 *
726
+	 * @param Content $contentObject
727
+	 * @param $fieldNameAndPath
728
+	 * @param $contentData
729
+	 * @param $counter
730
+	 * @param $savingBehavior
731
+	 * @param $language
732
+	 * @return ProcessContentDataSignalArguments
733
+	 */
734
+	protected function emitProcessContentDataSignal(Content $contentObject, $fieldNameAndPath, $contentData, $counter, $savingBehavior, $language)
735
+	{
736
+
737
+		/** @var ProcessContentDataSignalArguments $signalArguments */
738
+		$signalArguments = GeneralUtility::makeInstance(ProcessContentDataSignalArguments::class);
739
+		$signalArguments->setContentObject($contentObject)
740
+			->setFieldNameAndPath($fieldNameAndPath)
741
+			->setContentData($contentData)
742
+			->setCounter($counter)
743
+			->setSavingBehavior($savingBehavior)
744
+			->setLanguage($language);
745
+
746
+		$signalResult = $this->getSignalSlotDispatcher()->dispatch('Fab\Vidi\Controller\Backend\ContentController', 'processContentData', array($signalArguments));
747
+		return $signalResult[0];
748
+	}
749
+
750
+	/**
751
+	 * Get the SignalSlot dispatcher.
752
+	 *
753
+	 * @return Dispatcher
754
+	 */
755
+	protected function getSignalSlotDispatcher()
756
+	{
757
+		return GeneralUtility::makeInstance(Dispatcher::class);
758
+	}
759
+
760
+	/**
761
+	 * Get the Clipboard service.
762
+	 *
763
+	 * @return ClipboardService
764
+	 */
765
+	protected function getClipboardService()
766
+	{
767
+		return GeneralUtility::makeInstance(ClipboardService::class);
768
+	}
769
+
770
+	/**
771
+	 * @return LanguageService
772
+	 */
773
+	protected function getLanguageService()
774
+	{
775
+		return GeneralUtility::makeInstance(LanguageService::class);
776
+	}
777
+
778
+	/**
779
+	 * Get the Vidi Module Loader.
780
+	 *
781
+	 * @return ModuleLoader
782
+	 */
783
+	protected function getModuleLoader()
784
+	{
785
+		return GeneralUtility::makeInstance(ModuleLoader::class);
786
+	}
787
+
788
+	public function injectSelectionRepository(SelectionRepository $selectionRepository): void
789
+	{
790
+		$this->selectionRepository = $selectionRepository;
791
+	}
792 792
 
793 793
 }
Please login to merge, or discard this patch.
Classes/Controller/SelectionController.php 1 patch
Indentation   +75 added lines, -75 removed lines patch added patch discarded remove patch
@@ -24,88 +24,88 @@
 block discarded – undo
24 24
 class SelectionController extends ActionController
25 25
 {
26 26
 
27
-    /**
28
-     * @param Selection $selection
29
-     */
30
-    public function createAction(Selection $selection = null)
31
-    {
32
-        $selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
33
-        $selection->setDataType($this->getModuleLoader()->getDataType());
27
+	/**
28
+	 * @param Selection $selection
29
+	 */
30
+	public function createAction(Selection $selection = null)
31
+	{
32
+		$selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
33
+		$selection->setDataType($this->getModuleLoader()->getDataType());
34 34
 
35
-        $selection->setOwner($this->getBackendUser()->user['uid']);
36
-        $selectionRepository->add($selection);
37
-        $this->redirect('edit', 'Selection', 'vidi', array('dataType' => $selection->getDataType()));
38
-    }
35
+		$selection->setOwner($this->getBackendUser()->user['uid']);
36
+		$selectionRepository->add($selection);
37
+		$this->redirect('edit', 'Selection', 'vidi', array('dataType' => $selection->getDataType()));
38
+	}
39 39
 
40
-    /**
41
-     * @param Selection $selection
42
-     * @return string
43
-     */
44
-    public function deleteAction(Selection $selection)
45
-    {
46
-        $selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
47
-        $selectionRepository->remove($selection);
48
-        return 'ok';
49
-    }
40
+	/**
41
+	 * @param Selection $selection
42
+	 * @return string
43
+	 */
44
+	public function deleteAction(Selection $selection)
45
+	{
46
+		$selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
47
+		$selectionRepository->remove($selection);
48
+		return 'ok';
49
+	}
50 50
 
51
-    /**
52
-     * @param Selection $selection
53
-     */
54
-    public function updateAction(Selection $selection)
55
-    {
56
-        $selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
57
-        $selectionRepository->update($selection);
58
-        $this->redirect('show', 'Selection', 'vidi', array('selection' => $selection->getUid()));
59
-    }
51
+	/**
52
+	 * @param Selection $selection
53
+	 */
54
+	public function updateAction(Selection $selection)
55
+	{
56
+		$selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
57
+		$selectionRepository->update($selection);
58
+		$this->redirect('show', 'Selection', 'vidi', array('selection' => $selection->getUid()));
59
+	}
60 60
 
61
-    /**
62
-     * @param Selection $selection
63
-     */
64
-    public function showAction(Selection $selection)
65
-    {
66
-        $this->view->assign('selection', $selection);
67
-    }
61
+	/**
62
+	 * @param Selection $selection
63
+	 */
64
+	public function showAction(Selection $selection)
65
+	{
66
+		$this->view->assign('selection', $selection);
67
+	}
68 68
 
69
-    /**
70
-     * Returns an editing form for a given data type.
71
-     *
72
-     * @param string $dataType
73
-     */
74
-    public function editAction($dataType)
75
-    {
76
-        $selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
77
-        $selections = $selectionRepository->findByDataTypeForCurrentBackendUser($dataType);
78
-        $this->view->assign('selections', $selections);
79
-    }
69
+	/**
70
+	 * Returns an editing form for a given data type.
71
+	 *
72
+	 * @param string $dataType
73
+	 */
74
+	public function editAction($dataType)
75
+	{
76
+		$selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
77
+		$selections = $selectionRepository->findByDataTypeForCurrentBackendUser($dataType);
78
+		$this->view->assign('selections', $selections);
79
+	}
80 80
 
81
-    /**
82
-     * @param string $dataType
83
-     */
84
-    public function listAction($dataType)
85
-    {
86
-        $selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
87
-        $selections = $selectionRepository->findByDataTypeForCurrentBackendUser($dataType);
88
-        $this->view->assign('selections', $selections);
89
-    }
81
+	/**
82
+	 * @param string $dataType
83
+	 */
84
+	public function listAction($dataType)
85
+	{
86
+		$selectionRepository = GeneralUtility::makeInstance(SelectionRepository::class);
87
+		$selections = $selectionRepository->findByDataTypeForCurrentBackendUser($dataType);
88
+		$this->view->assign('selections', $selections);
89
+	}
90 90
 
91
-    /**
92
-     * Get the Vidi Module Loader.
93
-     *
94
-     * @return ModuleLoader
95
-     */
96
-    protected function getModuleLoader()
97
-    {
98
-        return GeneralUtility::makeInstance(ModuleLoader::class);
99
-    }
91
+	/**
92
+	 * Get the Vidi Module Loader.
93
+	 *
94
+	 * @return ModuleLoader
95
+	 */
96
+	protected function getModuleLoader()
97
+	{
98
+		return GeneralUtility::makeInstance(ModuleLoader::class);
99
+	}
100 100
 
101
-    /**
102
-     * Returns an instance of the current Backend User.
103
-     *
104
-     * @return BackendUserAuthentication
105
-     */
106
-    protected function getBackendUser()
107
-    {
108
-        return $GLOBALS['BE_USER'];
109
-    }
101
+	/**
102
+	 * Returns an instance of the current Backend User.
103
+	 *
104
+	 * @return BackendUserAuthentication
105
+	 */
106
+	protected function getBackendUser()
107
+	{
108
+		return $GLOBALS['BE_USER'];
109
+	}
110 110
 
111 111
 }
Please login to merge, or discard this patch.
Classes/Tool/AbstractTool.php 1 patch
Indentation   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -18,42 +18,42 @@
 block discarded – undo
18 18
 abstract class AbstractTool implements ToolInterface
19 19
 {
20 20
 
21
-    /**
22
-     * @param string $templateNameAndPath
23
-     * @return StandaloneView
24
-     * @throws \InvalidArgumentException
25
-     */
26
-    protected function initializeStandaloneView($templateNameAndPath)
27
-    {
28
-
29
-        $templateNameAndPath = GeneralUtility::getFileAbsFileName($templateNameAndPath);
30
-
31
-        /** @var StandaloneView $view */
32
-        $view = GeneralUtility::makeInstance(StandaloneView::class);
33
-
34
-        $view->setTemplatePathAndFilename($templateNameAndPath);
35
-        return $view;
36
-    }
37
-
38
-    /**
39
-     * Returns an instance of the current Backend User.
40
-     *
41
-     * @return BackendUserAuthentication
42
-     */
43
-    protected function getBackendUser()
44
-    {
45
-        return $GLOBALS['BE_USER'];
46
-    }
47
-
48
-    /**
49
-     * Get the Vidi Module Loader.
50
-     *
51
-     * @return ModuleLoader
52
-     * @throws \InvalidArgumentException
53
-     */
54
-    protected function getModuleLoader()
55
-    {
56
-        return GeneralUtility::makeInstance(ModuleLoader::class);
57
-    }
21
+	/**
22
+	 * @param string $templateNameAndPath
23
+	 * @return StandaloneView
24
+	 * @throws \InvalidArgumentException
25
+	 */
26
+	protected function initializeStandaloneView($templateNameAndPath)
27
+	{
28
+
29
+		$templateNameAndPath = GeneralUtility::getFileAbsFileName($templateNameAndPath);
30
+
31
+		/** @var StandaloneView $view */
32
+		$view = GeneralUtility::makeInstance(StandaloneView::class);
33
+
34
+		$view->setTemplatePathAndFilename($templateNameAndPath);
35
+		return $view;
36
+	}
37
+
38
+	/**
39
+	 * Returns an instance of the current Backend User.
40
+	 *
41
+	 * @return BackendUserAuthentication
42
+	 */
43
+	protected function getBackendUser()
44
+	{
45
+		return $GLOBALS['BE_USER'];
46
+	}
47
+
48
+	/**
49
+	 * Get the Vidi Module Loader.
50
+	 *
51
+	 * @return ModuleLoader
52
+	 * @throws \InvalidArgumentException
53
+	 */
54
+	protected function getModuleLoader()
55
+	{
56
+		return GeneralUtility::makeInstance(ModuleLoader::class);
57
+	}
58 58
 
59 59
 }
Please login to merge, or discard this patch.
Classes/Module/ModulePreferences.php 1 patch
Indentation   +215 added lines, -215 removed lines patch added patch discarded remove patch
@@ -18,220 +18,220 @@
 block discarded – undo
18 18
 class ModulePreferences implements SingletonInterface
19 19
 {
20 20
 
21
-    /**
22
-     * @var array
23
-     */
24
-    protected $preferences;
25
-
26
-    /**
27
-     * @var string
28
-     */
29
-    protected $tableName = 'tx_vidi_preference';
30
-
31
-    /**
32
-     * @param string $key
33
-     * @param string $dataType
34
-     * @return mixed
35
-     */
36
-    public function get($key, $dataType = '')
37
-    {
38
-
39
-        if (empty($dataType)) {
40
-            $dataType = $this->getModuleLoader()->getDataType();
41
-        }
42
-
43
-        if (!$this->isLoaded($dataType)) {
44
-            $this->load($dataType);
45
-        }
46
-
47
-        $value = empty($this->preferences[$dataType][$key]) ? null : $this->preferences[$dataType][$key];
48
-        return $value;
49
-    }
50
-
51
-    /**
52
-     * Tell whether the module is loaded.
53
-     *
54
-     * @param string $dataType
55
-     * @return bool
56
-     */
57
-    public function isLoaded($dataType)
58
-    {
59
-        return !empty($this->preferences[$dataType]);
60
-    }
61
-
62
-    /**
63
-     * @param string $dataType
64
-     * @return array
65
-     */
66
-    public function getAll($dataType = '')
67
-    {
68
-
69
-        if (empty($dataType)) {
70
-            $dataType = $this->getModuleLoader()->getDataType();
71
-        }
72
-        $this->load($dataType);
73
-        return $this->preferences[$dataType];
74
-    }
75
-
76
-    /**
77
-     * Get the md5 signature of the preferences.
78
-     *
79
-     * @param string $dataType
80
-     * @return bool
81
-     */
82
-    public function getSignature($dataType = '')
83
-    {
84
-        $preferences = $this->getAll($dataType);
85
-        return md5(serialize($preferences));
86
-    }
87
-
88
-    /**
89
-     * Load preferences.
90
-     *
91
-     * @param string $dataType
92
-     * @return void
93
-     */
94
-    public function load($dataType)
95
-    {
96
-
97
-        // Fetch preferences from different sources and overlay them
98
-        $databasePreferences = $this->fetchPreferencesFromDatabase($dataType);
99
-        $generalPreferences = $this->fetchGlobalPreferencesFromTypoScript();
100
-        $specificPreferences = $this->fetchExtraPreferencesFromTypoScript($dataType);
101
-
102
-        $preferences = array_merge($generalPreferences, $specificPreferences, $databasePreferences);
103
-        $this->preferences[$dataType] = $preferences;
104
-    }
105
-
106
-    /**
107
-     * Save preferences
108
-     *
109
-     * @param array $preferences
110
-     * @return void
111
-     */
112
-    public function save($preferences)
113
-    {
114
-        $configurableParts = ConfigurablePart::getParts();
115
-
116
-        $dataType = $this->getModuleLoader()->getDataType();
117
-        $this->getDataService()->delete(
118
-            $this->tableName,
119
-            [
120
-                'data_type' => $dataType
121
-            ]
122
-        );
123
-
124
-        $sanitizedPreferences = [];
125
-        foreach ($preferences as $key => $value) {
126
-            if (in_array($key, $configurableParts)) {
127
-                $sanitizedPreferences[$key] = $value;
128
-            }
129
-        }
130
-
131
-        $this->getDataService()->insert(
132
-            $this->tableName,
133
-            [
134
-                'data_type' => $dataType,
135
-                'preferences' => serialize($sanitizedPreferences),
136
-            ]
137
-        );
138
-    }
139
-
140
-    /**
141
-     * @param $dataType
142
-     * @return array
143
-     */
144
-    public function fetchPreferencesFromDatabase($dataType)
145
-    {
146
-        $preferences = [];
147
-        $record = $this->getDataService()->getRecord(
148
-            $this->tableName,
149
-            [
150
-                'data_type' => $dataType
151
-            ]
152
-        );
153
-
154
-        if (!empty($record)) {
155
-            $preferences = unserialize($record['preferences']);
156
-        }
157
-
158
-        return $preferences;
159
-    }
160
-
161
-    /**
162
-     * Returns the module settings.
163
-     *
164
-     * @return array
165
-     */
166
-    protected function fetchGlobalPreferencesFromTypoScript()
167
-    {
168
-        $settings = $this->getSettings();
169
-
170
-        $configurableParts = ConfigurablePart::getParts();
171
-        $preferences = [];
172
-        foreach ($settings as $key => $value) {
173
-            if (in_array($key, $configurableParts)) {
174
-                $preferences[$key] = $value;
175
-            }
176
-        }
177
-
178
-        return $preferences;
179
-    }
180
-
181
-    /**
182
-     * Returns the module settings.
183
-     *
184
-     * @param string $dataType
185
-     * @return array
186
-     */
187
-    protected function fetchExtraPreferencesFromTypoScript($dataType)
188
-    {
189
-        $generalSettings = $this->getSettings();
190
-
191
-        $preferences = [];
192
-        if (isset($generalSettings[$dataType . '.'])) {
193
-            $settings = $generalSettings[$dataType . '.'];
194
-
195
-            $configurableParts = ConfigurablePart::getParts();
196
-            foreach ($settings as $key => $value) {
197
-                if (in_array($key, $configurableParts)) {
198
-                    $preferences[$key] = $value;
199
-                }
200
-            }
201
-        }
202
-
203
-        return $preferences;
204
-    }
205
-
206
-    /**
207
-     * Returns the module settings.
208
-     *
209
-     * @return array
210
-     */
211
-    protected function getSettings()
212
-    {
213
-        /** @var BackendConfigurationManager $backendConfigurationManager */
214
-        $backendConfigurationManager = GeneralUtility::makeInstance(BackendConfigurationManager::class);
215
-        $configuration = $backendConfigurationManager->getTypoScriptSetup();
216
-        return $configuration['module.']['tx_vidi.']['settings.'];
217
-    }
218
-
219
-    /**
220
-     * @return object|DataService
221
-     */
222
-    protected function getDataService(): DataService
223
-    {
224
-        return GeneralUtility::makeInstance(DataService::class);
225
-    }
226
-
227
-    /**
228
-     * Get the Vidi Module Loader.
229
-     *
230
-     * @return ModuleLoader|object
231
-     */
232
-    protected function getModuleLoader()
233
-    {
234
-        return GeneralUtility::makeInstance(ModuleLoader::class);
235
-    }
21
+	/**
22
+	 * @var array
23
+	 */
24
+	protected $preferences;
25
+
26
+	/**
27
+	 * @var string
28
+	 */
29
+	protected $tableName = 'tx_vidi_preference';
30
+
31
+	/**
32
+	 * @param string $key
33
+	 * @param string $dataType
34
+	 * @return mixed
35
+	 */
36
+	public function get($key, $dataType = '')
37
+	{
38
+
39
+		if (empty($dataType)) {
40
+			$dataType = $this->getModuleLoader()->getDataType();
41
+		}
42
+
43
+		if (!$this->isLoaded($dataType)) {
44
+			$this->load($dataType);
45
+		}
46
+
47
+		$value = empty($this->preferences[$dataType][$key]) ? null : $this->preferences[$dataType][$key];
48
+		return $value;
49
+	}
50
+
51
+	/**
52
+	 * Tell whether the module is loaded.
53
+	 *
54
+	 * @param string $dataType
55
+	 * @return bool
56
+	 */
57
+	public function isLoaded($dataType)
58
+	{
59
+		return !empty($this->preferences[$dataType]);
60
+	}
61
+
62
+	/**
63
+	 * @param string $dataType
64
+	 * @return array
65
+	 */
66
+	public function getAll($dataType = '')
67
+	{
68
+
69
+		if (empty($dataType)) {
70
+			$dataType = $this->getModuleLoader()->getDataType();
71
+		}
72
+		$this->load($dataType);
73
+		return $this->preferences[$dataType];
74
+	}
75
+
76
+	/**
77
+	 * Get the md5 signature of the preferences.
78
+	 *
79
+	 * @param string $dataType
80
+	 * @return bool
81
+	 */
82
+	public function getSignature($dataType = '')
83
+	{
84
+		$preferences = $this->getAll($dataType);
85
+		return md5(serialize($preferences));
86
+	}
87
+
88
+	/**
89
+	 * Load preferences.
90
+	 *
91
+	 * @param string $dataType
92
+	 * @return void
93
+	 */
94
+	public function load($dataType)
95
+	{
96
+
97
+		// Fetch preferences from different sources and overlay them
98
+		$databasePreferences = $this->fetchPreferencesFromDatabase($dataType);
99
+		$generalPreferences = $this->fetchGlobalPreferencesFromTypoScript();
100
+		$specificPreferences = $this->fetchExtraPreferencesFromTypoScript($dataType);
101
+
102
+		$preferences = array_merge($generalPreferences, $specificPreferences, $databasePreferences);
103
+		$this->preferences[$dataType] = $preferences;
104
+	}
105
+
106
+	/**
107
+	 * Save preferences
108
+	 *
109
+	 * @param array $preferences
110
+	 * @return void
111
+	 */
112
+	public function save($preferences)
113
+	{
114
+		$configurableParts = ConfigurablePart::getParts();
115
+
116
+		$dataType = $this->getModuleLoader()->getDataType();
117
+		$this->getDataService()->delete(
118
+			$this->tableName,
119
+			[
120
+				'data_type' => $dataType
121
+			]
122
+		);
123
+
124
+		$sanitizedPreferences = [];
125
+		foreach ($preferences as $key => $value) {
126
+			if (in_array($key, $configurableParts)) {
127
+				$sanitizedPreferences[$key] = $value;
128
+			}
129
+		}
130
+
131
+		$this->getDataService()->insert(
132
+			$this->tableName,
133
+			[
134
+				'data_type' => $dataType,
135
+				'preferences' => serialize($sanitizedPreferences),
136
+			]
137
+		);
138
+	}
139
+
140
+	/**
141
+	 * @param $dataType
142
+	 * @return array
143
+	 */
144
+	public function fetchPreferencesFromDatabase($dataType)
145
+	{
146
+		$preferences = [];
147
+		$record = $this->getDataService()->getRecord(
148
+			$this->tableName,
149
+			[
150
+				'data_type' => $dataType
151
+			]
152
+		);
153
+
154
+		if (!empty($record)) {
155
+			$preferences = unserialize($record['preferences']);
156
+		}
157
+
158
+		return $preferences;
159
+	}
160
+
161
+	/**
162
+	 * Returns the module settings.
163
+	 *
164
+	 * @return array
165
+	 */
166
+	protected function fetchGlobalPreferencesFromTypoScript()
167
+	{
168
+		$settings = $this->getSettings();
169
+
170
+		$configurableParts = ConfigurablePart::getParts();
171
+		$preferences = [];
172
+		foreach ($settings as $key => $value) {
173
+			if (in_array($key, $configurableParts)) {
174
+				$preferences[$key] = $value;
175
+			}
176
+		}
177
+
178
+		return $preferences;
179
+	}
180
+
181
+	/**
182
+	 * Returns the module settings.
183
+	 *
184
+	 * @param string $dataType
185
+	 * @return array
186
+	 */
187
+	protected function fetchExtraPreferencesFromTypoScript($dataType)
188
+	{
189
+		$generalSettings = $this->getSettings();
190
+
191
+		$preferences = [];
192
+		if (isset($generalSettings[$dataType . '.'])) {
193
+			$settings = $generalSettings[$dataType . '.'];
194
+
195
+			$configurableParts = ConfigurablePart::getParts();
196
+			foreach ($settings as $key => $value) {
197
+				if (in_array($key, $configurableParts)) {
198
+					$preferences[$key] = $value;
199
+				}
200
+			}
201
+		}
202
+
203
+		return $preferences;
204
+	}
205
+
206
+	/**
207
+	 * Returns the module settings.
208
+	 *
209
+	 * @return array
210
+	 */
211
+	protected function getSettings()
212
+	{
213
+		/** @var BackendConfigurationManager $backendConfigurationManager */
214
+		$backendConfigurationManager = GeneralUtility::makeInstance(BackendConfigurationManager::class);
215
+		$configuration = $backendConfigurationManager->getTypoScriptSetup();
216
+		return $configuration['module.']['tx_vidi.']['settings.'];
217
+	}
218
+
219
+	/**
220
+	 * @return object|DataService
221
+	 */
222
+	protected function getDataService(): DataService
223
+	{
224
+		return GeneralUtility::makeInstance(DataService::class);
225
+	}
226
+
227
+	/**
228
+	 * Get the Vidi Module Loader.
229
+	 *
230
+	 * @return ModuleLoader|object
231
+	 */
232
+	protected function getModuleLoader()
233
+	{
234
+		return GeneralUtility::makeInstance(ModuleLoader::class);
235
+	}
236 236
 
237 237
 }
Please login to merge, or discard this patch.
Classes/Service/ContentService.php 1 patch
Indentation   +119 added lines, -119 removed lines patch added patch discarded remove patch
@@ -22,124 +22,124 @@
 block discarded – undo
22 22
 class ContentService
23 23
 {
24 24
 
25
-    /**
26
-     * @var string
27
-     */
28
-    protected $dataType;
29
-
30
-    /**
31
-     * @var Content[]
32
-     */
33
-    protected $objects = [];
34
-
35
-    /**
36
-     * @var int
37
-     */
38
-    protected $numberOfObjects = 0;
39
-
40
-    /**
41
-     * Constructor
42
-     *
43
-     * @param string $dataType
44
-     */
45
-    public function __construct($dataType = '')
46
-    {
47
-        if (empty($dataType)) {
48
-            $dataType = $this->getModuleLoader()->getDataType();
49
-        }
50
-        $this->dataType = $dataType;
51
-    }
52
-
53
-    /**
54
-     * Fetch the files given an object assuming
55
-     *
56
-     * @param Matcher $matcher
57
-     * @param Order $order The order
58
-     * @param int $limit
59
-     * @param int $offset
60
-     * @return $this
61
-     */
62
-    public function findBy(Matcher $matcher, Order $order = null, $limit = null, $offset = null)
63
-    {
64
-
65
-        // Query the repository.
66
-        $objects = ContentRepositoryFactory::getInstance($this->dataType)->findBy($matcher, $order, $limit, $offset);
67
-        $signalResult = $this->emitAfterFindContentObjectsSignal($objects, $matcher, $order, $limit, $offset);
68
-
69
-        // Reset objects variable after possible signal / slot processing.
70
-        $this->objects = $signalResult->getContentObjects();
71
-
72
-        // Count number of content objects.
73
-        if ($signalResult->getHasBeenProcessed()) {
74
-            $this->numberOfObjects = $signalResult->getNumberOfObjects();
75
-        } else {
76
-            $this->numberOfObjects = ContentRepositoryFactory::getInstance($this->dataType)->countBy($matcher);
77
-        }
78
-
79
-        return $this;
80
-    }
81
-
82
-    /**
83
-     * Signal that is called after the content objects have been found.
84
-     *
85
-     * @param array $contentObjects
86
-     * @param Matcher $matcher
87
-     * @param Order $order
88
-     * @param int $limit
89
-     * @param int $offset
90
-     * @return AfterFindContentObjectsSignalArguments
91
-     */
92
-    protected function emitAfterFindContentObjectsSignal($contentObjects, Matcher $matcher, Order $order = null, $limit = 0, $offset = 0)
93
-    {
94
-
95
-        /** @var AfterFindContentObjectsSignalArguments $signalArguments */
96
-        $signalArguments = GeneralUtility::makeInstance(AfterFindContentObjectsSignalArguments::class);
97
-        $signalArguments->setDataType($this->dataType)
98
-            ->setContentObjects($contentObjects)
99
-            ->setMatcher($matcher)
100
-            ->setOrder($order)
101
-            ->setLimit($limit)
102
-            ->setOffset($offset)
103
-            ->setHasBeenProcessed(false);
104
-
105
-        $signalResult = $this->getSignalSlotDispatcher()->dispatch(ContentService::class, 'afterFindContentObjects', array($signalArguments));
106
-        return $signalResult[0];
107
-    }
108
-
109
-    /**
110
-     * Get the Vidi Module Loader.
111
-     *
112
-     * @return ModuleLoader|object
113
-     */
114
-    protected function getModuleLoader()
115
-    {
116
-        return GeneralUtility::makeInstance(ModuleLoader::class);
117
-    }
118
-
119
-    /**
120
-     * Get the SignalSlot dispatcher.
121
-     *
122
-     * @return Dispatcher|object
123
-     */
124
-    protected function getSignalSlotDispatcher()
125
-    {
126
-        return GeneralUtility::makeInstance(Dispatcher::class);
127
-    }
128
-
129
-    /**
130
-     * @return Content[]
131
-     */
132
-    public function getObjects()
133
-    {
134
-        return $this->objects;
135
-    }
136
-
137
-    /**
138
-     * @return int
139
-     */
140
-    public function getNumberOfObjects()
141
-    {
142
-        return $this->numberOfObjects;
143
-    }
25
+	/**
26
+	 * @var string
27
+	 */
28
+	protected $dataType;
29
+
30
+	/**
31
+	 * @var Content[]
32
+	 */
33
+	protected $objects = [];
34
+
35
+	/**
36
+	 * @var int
37
+	 */
38
+	protected $numberOfObjects = 0;
39
+
40
+	/**
41
+	 * Constructor
42
+	 *
43
+	 * @param string $dataType
44
+	 */
45
+	public function __construct($dataType = '')
46
+	{
47
+		if (empty($dataType)) {
48
+			$dataType = $this->getModuleLoader()->getDataType();
49
+		}
50
+		$this->dataType = $dataType;
51
+	}
52
+
53
+	/**
54
+	 * Fetch the files given an object assuming
55
+	 *
56
+	 * @param Matcher $matcher
57
+	 * @param Order $order The order
58
+	 * @param int $limit
59
+	 * @param int $offset
60
+	 * @return $this
61
+	 */
62
+	public function findBy(Matcher $matcher, Order $order = null, $limit = null, $offset = null)
63
+	{
64
+
65
+		// Query the repository.
66
+		$objects = ContentRepositoryFactory::getInstance($this->dataType)->findBy($matcher, $order, $limit, $offset);
67
+		$signalResult = $this->emitAfterFindContentObjectsSignal($objects, $matcher, $order, $limit, $offset);
68
+
69
+		// Reset objects variable after possible signal / slot processing.
70
+		$this->objects = $signalResult->getContentObjects();
71
+
72
+		// Count number of content objects.
73
+		if ($signalResult->getHasBeenProcessed()) {
74
+			$this->numberOfObjects = $signalResult->getNumberOfObjects();
75
+		} else {
76
+			$this->numberOfObjects = ContentRepositoryFactory::getInstance($this->dataType)->countBy($matcher);
77
+		}
78
+
79
+		return $this;
80
+	}
81
+
82
+	/**
83
+	 * Signal that is called after the content objects have been found.
84
+	 *
85
+	 * @param array $contentObjects
86
+	 * @param Matcher $matcher
87
+	 * @param Order $order
88
+	 * @param int $limit
89
+	 * @param int $offset
90
+	 * @return AfterFindContentObjectsSignalArguments
91
+	 */
92
+	protected function emitAfterFindContentObjectsSignal($contentObjects, Matcher $matcher, Order $order = null, $limit = 0, $offset = 0)
93
+	{
94
+
95
+		/** @var AfterFindContentObjectsSignalArguments $signalArguments */
96
+		$signalArguments = GeneralUtility::makeInstance(AfterFindContentObjectsSignalArguments::class);
97
+		$signalArguments->setDataType($this->dataType)
98
+			->setContentObjects($contentObjects)
99
+			->setMatcher($matcher)
100
+			->setOrder($order)
101
+			->setLimit($limit)
102
+			->setOffset($offset)
103
+			->setHasBeenProcessed(false);
104
+
105
+		$signalResult = $this->getSignalSlotDispatcher()->dispatch(ContentService::class, 'afterFindContentObjects', array($signalArguments));
106
+		return $signalResult[0];
107
+	}
108
+
109
+	/**
110
+	 * Get the Vidi Module Loader.
111
+	 *
112
+	 * @return ModuleLoader|object
113
+	 */
114
+	protected function getModuleLoader()
115
+	{
116
+		return GeneralUtility::makeInstance(ModuleLoader::class);
117
+	}
118
+
119
+	/**
120
+	 * Get the SignalSlot dispatcher.
121
+	 *
122
+	 * @return Dispatcher|object
123
+	 */
124
+	protected function getSignalSlotDispatcher()
125
+	{
126
+		return GeneralUtility::makeInstance(Dispatcher::class);
127
+	}
128
+
129
+	/**
130
+	 * @return Content[]
131
+	 */
132
+	public function getObjects()
133
+	{
134
+		return $this->objects;
135
+	}
136
+
137
+	/**
138
+	 * @return int
139
+	 */
140
+	public function getNumberOfObjects()
141
+	{
142
+		return $this->numberOfObjects;
143
+	}
144 144
 
145 145
 }
Please login to merge, or discard this patch.
Classes/Persistence/MatcherObjectFactory.php 1 patch
Indentation   +263 added lines, -263 removed lines patch added patch discarded remove patch
@@ -25,267 +25,267 @@
 block discarded – undo
25 25
 class MatcherObjectFactory implements SingletonInterface
26 26
 {
27 27
 
28
-    /**
29
-     * Gets a singleton instance of this class.
30
-     *
31
-     * @return $this
32
-     */
33
-    static public function getInstance(): self
34
-    {
35
-        return GeneralUtility::makeInstance(self::class);
36
-    }
37
-
38
-    /**
39
-     * Returns a matcher object.
40
-     *
41
-     * @param array $matches
42
-     * @param string $dataType
43
-     * @return Matcher
44
-     */
45
-    public function getMatcher(array $matches = [], $dataType = ''): Matcher
46
-    {
47
-        if ($dataType === '') {
48
-            $dataType = $this->getModuleLoader()->getDataType();
49
-        }
50
-
51
-        /** @var $matcher Matcher */
52
-        $matcher = GeneralUtility::makeInstance(Matcher::class, [], $dataType);
53
-
54
-        $matcher = $this->applyCriteriaFromDataTables($matcher);
55
-        $matcher = $this->applyCriteriaFromMatchesArgument($matcher, $matches);
56
-
57
-        if ($this->isBackendMode()) {
58
-            $matcher = $this->applyCriteriaFromUrl($matcher);
59
-            $matcher = $this->applyCriteriaFromTSConfig($matcher);
60
-        }
61
-
62
-        // Trigger signal for post processing Matcher Object.
63
-        $this->emitPostProcessMatcherObjectSignal($matcher);
64
-
65
-        return $matcher;
66
-    }
67
-
68
-    /**
69
-     * Get a possible id from the URL and apply as filter criteria.
70
-     * Except if the main module belongs to the File. The id would be a combined identifier
71
-     * including the storage and a mount point.
72
-     *
73
-     * @param Matcher $matcher
74
-     * @return Matcher $matcher
75
-     */
76
-    protected function applyCriteriaFromUrl(Matcher $matcher): Matcher
77
-    {
78
-        if (GeneralUtility::_GP('id')
79
-            && !$this->getModuleLoader()->isPidIgnored()
80
-            && $this->getModuleLoader()->getMainModule() !== ModuleName::FILE) {
81
-            $matcher->equals('pid', GeneralUtility::_GP('id'));
82
-        }
83
-
84
-        return $matcher;
85
-    }
86
-
87
-    /**
88
-     * @param Matcher $matcher
89
-     * @return Matcher $matcher
90
-     */
91
-    protected function applyCriteriaFromTSConfig(Matcher $matcher): Matcher
92
-    {
93
-        $dataType = $matcher->getDataType();
94
-        $tsConfigPath = sprintf('tx_vidi.dataType.%s.constraints', $dataType);
95
-        $tsConfig = $this->getBackendUser()->getTSConfig($tsConfigPath);
96
-
97
-        if (is_array($tsConfig['properties']) && !empty($tsConfig['properties'])) {
98
-
99
-            foreach ($tsConfig['properties'] as $constraint) {
100
-
101
-                if (preg_match('/(.+) (>=|>|<|<=|=|like) (.+)/is', $constraint, $matches) && count($matches) === 4) {
102
-
103
-                    $operator = $matcher->getSupportedOperators()[strtolower(trim($matches[2]))];
104
-                    $operand = trim($matches[1]);
105
-                    $value = trim($matches[3]);
106
-
107
-                    $matcher->$operator($operand, $value);
108
-                } elseif (preg_match('/(.+) (in) (.+)/is', $constraint, $matches) && count($matches) === 4) {
109
-
110
-                    $operator = $matcher->getSupportedOperators()[trim($matches[2])];
111
-                    $operand = trim($matches[1]);
112
-                    $value = trim($matches[3]);
113
-                    $matcher->$operator($operand, GeneralUtility::trimExplode(',', $value, true));
114
-                }
115
-            }
116
-        }
117
-
118
-        return $matcher;
119
-    }
120
-
121
-    /**
122
-     * @param Matcher $matcher
123
-     * @param array $matches
124
-     * @return Matcher $matcher
125
-     */
126
-    protected function applyCriteriaFromMatchesArgument(Matcher $matcher, $matches): Matcher
127
-    {
128
-        foreach ($matches as $fieldNameAndPath => $value) {
129
-            // CSV values should be considered as "in" operator in the query, otherwise "equals".
130
-            $explodedValues = GeneralUtility::trimExplode(',', $value, true);
131
-            if (count($explodedValues) > 1) {
132
-                $matcher->in($fieldNameAndPath, $explodedValues);
133
-            } else {
134
-                $matcher->equals($fieldNameAndPath, $explodedValues[0]);
135
-            }
136
-        }
137
-
138
-        return $matcher;
139
-    }
140
-
141
-    /**
142
-     * Apply criteria specific to jQuery plugin DataTable.
143
-     *
144
-     * @param Matcher $matcher
145
-     * @return Matcher $matcher
146
-     */
147
-    protected function applyCriteriaFromDataTables(Matcher $matcher): Matcher
148
-    {
149
-
150
-        // Special case for Grid in the BE using jQuery DataTables plugin.
151
-        // Retrieve a possible search term from GP.
152
-        $query = GeneralUtility::_GP('search');
153
-        if (is_array($query)) {
154
-            if (!empty($query['value'])) {
155
-                $query = $query['value'];
156
-            } else {
157
-                $query = '';
158
-            }
159
-        }
160
-
161
-        if (strlen($query) > 0) {
162
-
163
-            // Parse the json query coming from the Visual Search.
164
-            $query = rawurldecode($query);
165
-            $queryParts = json_decode($query, true);
166
-
167
-            if (is_array($queryParts)) {
168
-                $matcher = $this->parseQuery($queryParts, $matcher);
169
-            } else {
170
-                $matcher->setSearchTerm($query);
171
-            }
172
-        }
173
-        return $matcher;
174
-    }
175
-
176
-    /**
177
-     * @param array $queryParts
178
-     * @param Matcher $matcher
179
-     * @return Matcher $matcher
180
-     */
181
-    protected function parseQuery(array $queryParts, Matcher $matcher): Matcher
182
-    {
183
-        $dataType = $matcher->getDataType();
184
-        foreach ($queryParts as $term) {
185
-            $fieldNameAndPath = key($term);
186
-
187
-            $resolvedDataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $dataType);
188
-            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $dataType);
189
-
190
-            // Retrieve the value.
191
-            $value = current($term);
192
-
193
-            if (Tca::grid($resolvedDataType)->hasFacet($fieldName) && Tca::grid($resolvedDataType)->facet($fieldName)->canModifyMatcher()) {
194
-                $matcher = Tca::grid($resolvedDataType)->facet($fieldName)->modifyMatcher($matcher, $value);
195
-            } elseif (Tca::table($resolvedDataType)->hasField($fieldName)) {
196
-                // Check whether the field exists and set it as "equal" or "like".
197
-                if ($this->isOperatorEquals($fieldNameAndPath, $dataType, $value)) {
198
-                    $matcher->equals($fieldNameAndPath, $value);
199
-                } else {
200
-                    $matcher->like($fieldNameAndPath, $value);
201
-                }
202
-            } elseif ($fieldNameAndPath === 'text') {
203
-                // Special case if field is "text" which is a pseudo field in this case.
204
-                // Set the search term which means Vidi will
205
-                // search in various fields with operator "like". The fields come from key "searchFields" in the TCA.
206
-                $matcher->setSearchTerm($value);
207
-            }
208
-        }
209
-        return $matcher;
210
-    }
211
-
212
-    /**
213
-     * Tell whether the operator should be equals instead of like for a search, e.g. if the value is numerical.
214
-     *
215
-     * @param string $fieldName
216
-     * @param string $dataType
217
-     * @param string $value
218
-     * @return bool
219
-     */
220
-    protected function isOperatorEquals($fieldName, $dataType, $value): bool
221
-    {
222
-        return (Tca::table($dataType)->field($fieldName)->hasRelation() && MathUtility::canBeInterpretedAsInteger($value))
223
-            || Tca::table($dataType)->field($fieldName)->isNumerical();
224
-    }
225
-
226
-    /**
227
-     * Signal that is called for post-processing a matcher object.
228
-     *
229
-     * @param Matcher $matcher
230
-     */
231
-    protected function emitPostProcessMatcherObjectSignal(Matcher $matcher): void
232
-    {
233
-
234
-        if (strlen($matcher->getDataType()) <= 0) {
235
-
236
-            /** @var ModuleLoader $moduleLoader */
237
-            $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
238
-            $matcher->setDataType($moduleLoader->getDataType());
239
-        }
240
-
241
-        $this->getSignalSlotDispatcher()->dispatch('Fab\Vidi\Controller\Backend\ContentController', 'postProcessMatcherObject', array($matcher, $matcher->getDataType()));
242
-    }
243
-
244
-    /**
245
-     * Get the SignalSlot dispatcher
246
-     *
247
-     * @return Dispatcher|object
248
-     */
249
-    protected function getSignalSlotDispatcher()
250
-    {
251
-        return GeneralUtility::makeInstance(Dispatcher::class);
252
-    }
253
-
254
-    /**
255
-     * Get the Vidi Module Loader.
256
-     *
257
-     * @return ModuleLoader|object
258
-     */
259
-    protected function getModuleLoader()
260
-    {
261
-        return GeneralUtility::makeInstance(ModuleLoader::class);
262
-    }
263
-
264
-    /**
265
-     * @return FieldPathResolver|object
266
-     */
267
-    protected function getFieldPathResolver()
268
-    {
269
-        return GeneralUtility::makeInstance(FieldPathResolver::class);
270
-    }
271
-
272
-    /**
273
-     * Returns an instance of the current Backend User.
274
-     *
275
-     * @return BackendUserAuthentication
276
-     */
277
-    protected function getBackendUser(): BackendUserAuthentication
278
-    {
279
-        return $GLOBALS['BE_USER'];
280
-    }
281
-
282
-    /**
283
-     * Returns whether the current mode is Backend
284
-     *
285
-     * @return bool
286
-     */
287
-    protected function isBackendMode(): bool
288
-    {
289
-        return ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend();
290
-    }
28
+	/**
29
+	 * Gets a singleton instance of this class.
30
+	 *
31
+	 * @return $this
32
+	 */
33
+	static public function getInstance(): self
34
+	{
35
+		return GeneralUtility::makeInstance(self::class);
36
+	}
37
+
38
+	/**
39
+	 * Returns a matcher object.
40
+	 *
41
+	 * @param array $matches
42
+	 * @param string $dataType
43
+	 * @return Matcher
44
+	 */
45
+	public function getMatcher(array $matches = [], $dataType = ''): Matcher
46
+	{
47
+		if ($dataType === '') {
48
+			$dataType = $this->getModuleLoader()->getDataType();
49
+		}
50
+
51
+		/** @var $matcher Matcher */
52
+		$matcher = GeneralUtility::makeInstance(Matcher::class, [], $dataType);
53
+
54
+		$matcher = $this->applyCriteriaFromDataTables($matcher);
55
+		$matcher = $this->applyCriteriaFromMatchesArgument($matcher, $matches);
56
+
57
+		if ($this->isBackendMode()) {
58
+			$matcher = $this->applyCriteriaFromUrl($matcher);
59
+			$matcher = $this->applyCriteriaFromTSConfig($matcher);
60
+		}
61
+
62
+		// Trigger signal for post processing Matcher Object.
63
+		$this->emitPostProcessMatcherObjectSignal($matcher);
64
+
65
+		return $matcher;
66
+	}
67
+
68
+	/**
69
+	 * Get a possible id from the URL and apply as filter criteria.
70
+	 * Except if the main module belongs to the File. The id would be a combined identifier
71
+	 * including the storage and a mount point.
72
+	 *
73
+	 * @param Matcher $matcher
74
+	 * @return Matcher $matcher
75
+	 */
76
+	protected function applyCriteriaFromUrl(Matcher $matcher): Matcher
77
+	{
78
+		if (GeneralUtility::_GP('id')
79
+			&& !$this->getModuleLoader()->isPidIgnored()
80
+			&& $this->getModuleLoader()->getMainModule() !== ModuleName::FILE) {
81
+			$matcher->equals('pid', GeneralUtility::_GP('id'));
82
+		}
83
+
84
+		return $matcher;
85
+	}
86
+
87
+	/**
88
+	 * @param Matcher $matcher
89
+	 * @return Matcher $matcher
90
+	 */
91
+	protected function applyCriteriaFromTSConfig(Matcher $matcher): Matcher
92
+	{
93
+		$dataType = $matcher->getDataType();
94
+		$tsConfigPath = sprintf('tx_vidi.dataType.%s.constraints', $dataType);
95
+		$tsConfig = $this->getBackendUser()->getTSConfig($tsConfigPath);
96
+
97
+		if (is_array($tsConfig['properties']) && !empty($tsConfig['properties'])) {
98
+
99
+			foreach ($tsConfig['properties'] as $constraint) {
100
+
101
+				if (preg_match('/(.+) (>=|>|<|<=|=|like) (.+)/is', $constraint, $matches) && count($matches) === 4) {
102
+
103
+					$operator = $matcher->getSupportedOperators()[strtolower(trim($matches[2]))];
104
+					$operand = trim($matches[1]);
105
+					$value = trim($matches[3]);
106
+
107
+					$matcher->$operator($operand, $value);
108
+				} elseif (preg_match('/(.+) (in) (.+)/is', $constraint, $matches) && count($matches) === 4) {
109
+
110
+					$operator = $matcher->getSupportedOperators()[trim($matches[2])];
111
+					$operand = trim($matches[1]);
112
+					$value = trim($matches[3]);
113
+					$matcher->$operator($operand, GeneralUtility::trimExplode(',', $value, true));
114
+				}
115
+			}
116
+		}
117
+
118
+		return $matcher;
119
+	}
120
+
121
+	/**
122
+	 * @param Matcher $matcher
123
+	 * @param array $matches
124
+	 * @return Matcher $matcher
125
+	 */
126
+	protected function applyCriteriaFromMatchesArgument(Matcher $matcher, $matches): Matcher
127
+	{
128
+		foreach ($matches as $fieldNameAndPath => $value) {
129
+			// CSV values should be considered as "in" operator in the query, otherwise "equals".
130
+			$explodedValues = GeneralUtility::trimExplode(',', $value, true);
131
+			if (count($explodedValues) > 1) {
132
+				$matcher->in($fieldNameAndPath, $explodedValues);
133
+			} else {
134
+				$matcher->equals($fieldNameAndPath, $explodedValues[0]);
135
+			}
136
+		}
137
+
138
+		return $matcher;
139
+	}
140
+
141
+	/**
142
+	 * Apply criteria specific to jQuery plugin DataTable.
143
+	 *
144
+	 * @param Matcher $matcher
145
+	 * @return Matcher $matcher
146
+	 */
147
+	protected function applyCriteriaFromDataTables(Matcher $matcher): Matcher
148
+	{
149
+
150
+		// Special case for Grid in the BE using jQuery DataTables plugin.
151
+		// Retrieve a possible search term from GP.
152
+		$query = GeneralUtility::_GP('search');
153
+		if (is_array($query)) {
154
+			if (!empty($query['value'])) {
155
+				$query = $query['value'];
156
+			} else {
157
+				$query = '';
158
+			}
159
+		}
160
+
161
+		if (strlen($query) > 0) {
162
+
163
+			// Parse the json query coming from the Visual Search.
164
+			$query = rawurldecode($query);
165
+			$queryParts = json_decode($query, true);
166
+
167
+			if (is_array($queryParts)) {
168
+				$matcher = $this->parseQuery($queryParts, $matcher);
169
+			} else {
170
+				$matcher->setSearchTerm($query);
171
+			}
172
+		}
173
+		return $matcher;
174
+	}
175
+
176
+	/**
177
+	 * @param array $queryParts
178
+	 * @param Matcher $matcher
179
+	 * @return Matcher $matcher
180
+	 */
181
+	protected function parseQuery(array $queryParts, Matcher $matcher): Matcher
182
+	{
183
+		$dataType = $matcher->getDataType();
184
+		foreach ($queryParts as $term) {
185
+			$fieldNameAndPath = key($term);
186
+
187
+			$resolvedDataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $dataType);
188
+			$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $dataType);
189
+
190
+			// Retrieve the value.
191
+			$value = current($term);
192
+
193
+			if (Tca::grid($resolvedDataType)->hasFacet($fieldName) && Tca::grid($resolvedDataType)->facet($fieldName)->canModifyMatcher()) {
194
+				$matcher = Tca::grid($resolvedDataType)->facet($fieldName)->modifyMatcher($matcher, $value);
195
+			} elseif (Tca::table($resolvedDataType)->hasField($fieldName)) {
196
+				// Check whether the field exists and set it as "equal" or "like".
197
+				if ($this->isOperatorEquals($fieldNameAndPath, $dataType, $value)) {
198
+					$matcher->equals($fieldNameAndPath, $value);
199
+				} else {
200
+					$matcher->like($fieldNameAndPath, $value);
201
+				}
202
+			} elseif ($fieldNameAndPath === 'text') {
203
+				// Special case if field is "text" which is a pseudo field in this case.
204
+				// Set the search term which means Vidi will
205
+				// search in various fields with operator "like". The fields come from key "searchFields" in the TCA.
206
+				$matcher->setSearchTerm($value);
207
+			}
208
+		}
209
+		return $matcher;
210
+	}
211
+
212
+	/**
213
+	 * Tell whether the operator should be equals instead of like for a search, e.g. if the value is numerical.
214
+	 *
215
+	 * @param string $fieldName
216
+	 * @param string $dataType
217
+	 * @param string $value
218
+	 * @return bool
219
+	 */
220
+	protected function isOperatorEquals($fieldName, $dataType, $value): bool
221
+	{
222
+		return (Tca::table($dataType)->field($fieldName)->hasRelation() && MathUtility::canBeInterpretedAsInteger($value))
223
+			|| Tca::table($dataType)->field($fieldName)->isNumerical();
224
+	}
225
+
226
+	/**
227
+	 * Signal that is called for post-processing a matcher object.
228
+	 *
229
+	 * @param Matcher $matcher
230
+	 */
231
+	protected function emitPostProcessMatcherObjectSignal(Matcher $matcher): void
232
+	{
233
+
234
+		if (strlen($matcher->getDataType()) <= 0) {
235
+
236
+			/** @var ModuleLoader $moduleLoader */
237
+			$moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
238
+			$matcher->setDataType($moduleLoader->getDataType());
239
+		}
240
+
241
+		$this->getSignalSlotDispatcher()->dispatch('Fab\Vidi\Controller\Backend\ContentController', 'postProcessMatcherObject', array($matcher, $matcher->getDataType()));
242
+	}
243
+
244
+	/**
245
+	 * Get the SignalSlot dispatcher
246
+	 *
247
+	 * @return Dispatcher|object
248
+	 */
249
+	protected function getSignalSlotDispatcher()
250
+	{
251
+		return GeneralUtility::makeInstance(Dispatcher::class);
252
+	}
253
+
254
+	/**
255
+	 * Get the Vidi Module Loader.
256
+	 *
257
+	 * @return ModuleLoader|object
258
+	 */
259
+	protected function getModuleLoader()
260
+	{
261
+		return GeneralUtility::makeInstance(ModuleLoader::class);
262
+	}
263
+
264
+	/**
265
+	 * @return FieldPathResolver|object
266
+	 */
267
+	protected function getFieldPathResolver()
268
+	{
269
+		return GeneralUtility::makeInstance(FieldPathResolver::class);
270
+	}
271
+
272
+	/**
273
+	 * Returns an instance of the current Backend User.
274
+	 *
275
+	 * @return BackendUserAuthentication
276
+	 */
277
+	protected function getBackendUser(): BackendUserAuthentication
278
+	{
279
+		return $GLOBALS['BE_USER'];
280
+	}
281
+
282
+	/**
283
+	 * Returns whether the current mode is Backend
284
+	 *
285
+	 * @return bool
286
+	 */
287
+	protected function isBackendMode(): bool
288
+	{
289
+		return ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend();
290
+	}
291 291
 }
Please login to merge, or discard this patch.
Classes/Persistence/Query.php 2 patches
Indentation   +622 added lines, -622 removed lines patch added patch discarded remove patch
@@ -41,626 +41,626 @@
 block discarded – undo
41 41
 class Query implements QueryInterface
42 42
 {
43 43
 
44
-    /**
45
-     * An inner join.
46
-     */
47
-    const JCR_JOIN_TYPE_INNER = '{http://www.jcp.org/jcr/1.0}joinTypeInner';
48
-
49
-    /**
50
-     * A left-outer join.
51
-     */
52
-    const JCR_JOIN_TYPE_LEFT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeLeftOuter';
53
-
54
-    /**
55
-     * A right-outer join.
56
-     */
57
-    const JCR_JOIN_TYPE_RIGHT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeRightOuter';
58
-
59
-    /**
60
-     * Charset of strings in QOM
61
-     */
62
-    const CHARSET = 'utf-8';
63
-
64
-    /**
65
-     * @var string
66
-     */
67
-    protected $sourceFieldName;
68
-
69
-    /**
70
-     * @var string
71
-     */
72
-    protected $type;
73
-
74
-    /**
75
-     * @var PersistenceManagerInterface
76
-     */
77
-    protected $persistenceManager;
78
-
79
-    /**
80
-     * @var QueryObjectModelFactory
81
-     */
82
-    protected $qomFactory;
83
-
84
-    /**
85
-     * @var SourceInterface
86
-     */
87
-    protected $source;
88
-
89
-    /**
90
-     * @var ConstraintInterface
91
-     */
92
-    protected $constraint;
93
-
94
-    /**
95
-     * @var Statement
96
-     */
97
-    protected $statement;
98
-
99
-    /**
100
-     * @var array
101
-     */
102
-    protected $orderings = [];
103
-
104
-    /**
105
-     * @var int
106
-     */
107
-    protected $limit;
108
-
109
-    /**
110
-     * @var int
111
-     */
112
-    protected $offset;
113
-
114
-    /**
115
-     * Apply DISTINCT upon property.
116
-     *
117
-     * @var string
118
-     */
119
-    protected $distinct;
120
-
121
-    /**
122
-     * The query settings.
123
-     *
124
-     * @var Typo3QuerySettings
125
-     */
126
-    public Typo3QuerySettings $typo3QuerySettings;
127
-
128
-    /**
129
-     * Constructs a query object working on the given class name
130
-     *
131
-     * @param string $type
132
-     */
133
-    public function __construct($type)
134
-    {
135
-        $this->type = $type;
136
-        $this->persistenceManager = GeneralUtility::makeInstance(PersistenceManagerInterface::class);
137
-        $this->qomFactory = GeneralUtility::makeInstance(QueryObjectModelFactory::class);
138
-    }
139
-
140
-    public function injectTypo3QuerySettings(Typo3QuerySettings $querySettings): void
141
-    {
142
-        $this->typo3QuerySettings = $querySettings;
143
-    }
144
-
145
-    /**
146
-     * Sets the Query Settings. These Query settings must match the settings expected by
147
-     * the specific Storage Backend.
148
-     *
149
-     * @param QuerySettingsInterface $typo3QuerySettings The Query Settings
150
-     * @return void
151
-     */
152
-    public function setTypo3QuerySettings(QuerySettingsInterface $typo3QuerySettings)
153
-    {
154
-        $this->typo3QuerySettings = $typo3QuerySettings;
155
-    }
156
-
157
-    /**
158
-     * Returns the Query Settings.
159
-     *
160
-     * @throws \Exception
161
-     * @return Typo3QuerySettings $querySettings The Query Settings
162
-     * @api This method is not part of FLOW3 API
163
-     */
164
-    public function getTypo3QuerySettings()
165
-    {
166
-        if (!$this->typo3QuerySettings instanceof QuerySettingsInterface) {
167
-            throw new Exception('Tried to get the query settings without setting them before.', 1248689115);
168
-        }
169
-
170
-        // Apply possible settings to the query.
171
-        if ($this->isBackendMode()) {
172
-            /** @var BackendConfigurationManager $backendConfigurationManager */
173
-            $backendConfigurationManager = GeneralUtility::makeInstance(BackendConfigurationManager::class);
174
-            $configuration = $backendConfigurationManager->getTypoScriptSetup();
175
-            $querySettings = array('respectSysLanguage');
176
-            foreach ($querySettings as $setting) {
177
-                if (isset($configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type . '.'][$setting])) {
178
-                    $value = (bool)$configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type . '.'][$setting];
179
-                    ObjectAccess::setProperty($this->typo3QuerySettings, $setting, $value);
180
-                }
181
-            }
182
-        }
183
-
184
-        return $this->typo3QuerySettings;
185
-    }
186
-
187
-    /**
188
-     * Returns the type this query cares for.
189
-     *
190
-     * @return string
191
-     * @api
192
-     */
193
-    public function getType()
194
-    {
195
-        return $this->type;
196
-    }
197
-
198
-    /**
199
-     * Sets the source to fetch the result from
200
-     *
201
-     * @param SourceInterface $source
202
-     */
203
-    public function setSource(SourceInterface $source)
204
-    {
205
-        $this->source = $source;
206
-    }
207
-
208
-    /**
209
-     * Returns the selectorn name or an empty string, if the source is not a selector
210
-     * TODO This has to be checked at another place
211
-     *
212
-     * @return string The selector name
213
-     */
214
-    protected function getSelectorName()
215
-    {
216
-        if ($this->getSource() instanceof SelectorInterface) {
217
-            return $this->source->getSelectorName();
218
-        } else {
219
-            return '';
220
-        }
221
-    }
222
-
223
-    /**
224
-     * Gets the node-tuple source for this query.
225
-     *
226
-     * @return SourceInterface the node-tuple source; non-null
227
-     */
228
-    public function getSource()
229
-    {
230
-        if ($this->source === null) {
231
-            $this->source = $this->qomFactory->selector($this->getType());
232
-        }
233
-        return $this->source;
234
-    }
235
-
236
-    /**
237
-     * Executes the query against the database and returns the result
238
-     *
239
-     * @return QueryResultInterface|array The query result object or an array if $this->getQuerySettings()->getReturnRawQueryResult() is true
240
-     * @api
241
-     */
242
-    public function execute($returnRawQueryResult = false)
243
-    {
244
-        /** @var VidiDbBackend $backend */
245
-        $backend = GeneralUtility::makeInstance(VidiDbBackend::class, $this);
246
-        return $backend->fetchResult();
247
-    }
248
-
249
-    /**
250
-     * Sets the property names to order the result by. Expected like this:
251
-     * array(
252
-     * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
253
-     * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
254
-     * )
255
-     * where 'foo' and 'bar' are property names.
256
-     *
257
-     * @param array $orderings The property names to order by
258
-     * @return QueryInterface
259
-     * @api
260
-     */
261
-    public function setOrderings(array $orderings)
262
-    {
263
-        $this->orderings = $orderings;
264
-        return $this;
265
-    }
266
-
267
-    /**
268
-     * Returns the property names to order the result by. Like this:
269
-     * array(
270
-     * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
271
-     * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
272
-     * )
273
-     *
274
-     * @return array
275
-     */
276
-    public function getOrderings()
277
-    {
278
-        return $this->orderings;
279
-    }
280
-
281
-    /**
282
-     * Sets the maximum size of the result set to limit. Returns $this to allow
283
-     * for chaining (fluid interface)
284
-     *
285
-     * @param integer $limit
286
-     * @throws \InvalidArgumentException
287
-     * @return QueryInterface
288
-     * @api
289
-     */
290
-    public function setLimit($limit)
291
-    {
292
-        if (!is_int($limit) || $limit < 1) {
293
-            throw new \InvalidArgumentException('The limit must be an integer >= 1', 1245071870);
294
-        }
295
-        $this->limit = $limit;
296
-        return $this;
297
-    }
298
-
299
-    /**
300
-     * Resets a previously set maximum size of the result set. Returns $this to allow
301
-     * for chaining (fluid interface)
302
-     *
303
-     * @return QueryInterface
304
-     * @api
305
-     */
306
-    public function unsetLimit()
307
-    {
308
-        unset($this->limit);
309
-        return $this;
310
-    }
311
-
312
-    /**
313
-     * Returns the maximum size of the result set to limit.
314
-     *
315
-     * @return integer
316
-     * @api
317
-     */
318
-    public function getLimit()
319
-    {
320
-        return $this->limit;
321
-    }
322
-
323
-    /**
324
-     * Sets the start offset of the result set to offset. Returns $this to
325
-     * allow for chaining (fluid interface)
326
-     *
327
-     * @param integer $offset
328
-     * @throws \InvalidArgumentException
329
-     * @return QueryInterface
330
-     * @api
331
-     */
332
-    public function setOffset($offset)
333
-    {
334
-        if (!is_int($offset) || $offset < 0) {
335
-            throw new \InvalidArgumentException('The offset must be a positive integer', 1245071872);
336
-        }
337
-        $this->offset = $offset;
338
-        return $this;
339
-    }
340
-
341
-    /**
342
-     * Returns the start offset of the result set.
343
-     *
344
-     * @return integer
345
-     * @api
346
-     */
347
-    public function getOffset()
348
-    {
349
-        return $this->offset;
350
-    }
351
-
352
-    /**
353
-     * The constraint used to limit the result set. Returns $this to allow
354
-     * for chaining (fluid interface)
355
-     *
356
-     * @param ConstraintInterface $constraint
357
-     * @return QueryInterface
358
-     * @api
359
-     */
360
-    public function matching($constraint)
361
-    {
362
-        $this->constraint = $constraint;
363
-        return $this;
364
-    }
365
-
366
-    /**
367
-     * Gets the constraint for this query.
368
-     *
369
-     * @return ConstraintInterface the constraint, or null if none
370
-     * @api
371
-     */
372
-    public function getConstraint()
373
-    {
374
-        return $this->constraint;
375
-    }
376
-
377
-    /**
378
-     * Performs a logical conjunction of the given constraints. The method takes one or more contraints and concatenates them with a boolean AND.
379
-     * It also scepts a single array of constraints to be concatenated.
380
-     *
381
-     * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
382
-     * @throws InvalidNumberOfConstraintsException
383
-     * @return AndInterface
384
-     * @api
385
-     */
386
-    public function logicalAnd($constraint1)
387
-    {
388
-        if (is_array($constraint1)) {
389
-            $resultingConstraint = array_shift($constraint1);
390
-            $constraints = $constraint1;
391
-        } else {
392
-            $constraints = func_get_args();
393
-            $resultingConstraint = array_shift($constraints);
394
-        }
395
-        if ($resultingConstraint === null) {
396
-            throw new InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1401289500);
397
-        }
398
-        foreach ($constraints as $constraint) {
399
-            $resultingConstraint = $this->qomFactory->_and($resultingConstraint, $constraint);
400
-        }
401
-        return $resultingConstraint;
402
-    }
403
-
404
-    /**
405
-     * Performs a logical disjunction of the two given constraints
406
-     *
407
-     * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
408
-     * @throws InvalidNumberOfConstraintsException
409
-     * @return OrInterface
410
-     * @api
411
-     */
412
-    public function logicalOr($constraint1)
413
-    {
414
-        if (is_array($constraint1)) {
415
-            $resultingConstraint = array_shift($constraint1);
416
-            $constraints = $constraint1;
417
-        } else {
418
-            $constraints = func_get_args();
419
-            $resultingConstraint = array_shift($constraints);
420
-        }
421
-        if ($resultingConstraint === null) {
422
-            throw new InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1401289501);
423
-        }
424
-        foreach ($constraints as $constraint) {
425
-            $resultingConstraint = $this->qomFactory->_or($resultingConstraint, $constraint);
426
-        }
427
-        return $resultingConstraint;
428
-    }
429
-
430
-    /**
431
-     * Performs a logical negation of the given constraint
432
-     *
433
-     * @param ConstraintInterface $constraint Constraint to negate
434
-     * @throws \RuntimeException
435
-     * @return NotInterface
436
-     * @api
437
-     */
438
-    public function logicalNot(ConstraintInterface $constraint)
439
-    {
440
-        return $this->qomFactory->not($constraint);
441
-    }
442
-
443
-    /**
444
-     * Returns an equals criterion used for matching objects against a query
445
-     *
446
-     * @param string $propertyName The name of the property to compare against
447
-     * @param mixed $operand The value to compare with
448
-     * @param boolean $caseSensitive Whether the equality test should be done case-sensitive
449
-     * @return ComparisonInterface
450
-     * @api
451
-     */
452
-    public function equals($propertyName, $operand, $caseSensitive = true)
453
-    {
454
-        if (is_object($operand) || $caseSensitive) {
455
-            $comparison = $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_EQUAL_TO, $operand);
456
-        } else {
457
-            $comparison = $this->qomFactory->comparison($this->qomFactory->lowerCase($this->qomFactory->propertyValue($propertyName, $this->getSelectorName())), QueryInterface::OPERATOR_EQUAL_TO, mb_strtolower($operand, \TYPO3\CMS\Extbase\Persistence\Generic\Query::CHARSET));
458
-        }
459
-        return $comparison;
460
-    }
461
-
462
-    /**
463
-     * Returns a like criterion used for matching objects against a query
464
-     *
465
-     * @param string $propertyName The name of the property to compare against
466
-     * @param mixed $operand The value to compare with
467
-     * @param boolean $caseSensitive Whether the matching should be done case-sensitive
468
-     * @return ComparisonInterface
469
-     * @api
470
-     */
471
-    public function like($propertyName, $operand, $caseSensitive = true)
472
-    {
473
-        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LIKE, $operand);
474
-    }
475
-
476
-    /**
477
-     * Returns a "contains" criterion used for matching objects against a query.
478
-     * It matches if the multivalued property contains the given operand.
479
-     *
480
-     * @param string $propertyName The name of the (multivalued) property to compare against
481
-     * @param mixed $operand The value to compare with
482
-     * @return ComparisonInterface
483
-     * @api
484
-     */
485
-    public function contains($propertyName, $operand)
486
-    {
487
-        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_CONTAINS, $operand);
488
-    }
489
-
490
-    /**
491
-     * Returns an "in" criterion used for matching objects against a query. It
492
-     * matches if the property's value is contained in the multivalued operand.
493
-     *
494
-     * @param string $propertyName The name of the property to compare against
495
-     * @param mixed $operand The value to compare with, multivalued
496
-     * @throws UnexpectedTypeException
497
-     * @return ComparisonInterface
498
-     * @api
499
-     */
500
-    public function in($propertyName, $operand)
501
-    {
502
-        if (!is_array($operand) && !$operand instanceof \ArrayAccess && !$operand instanceof \Traversable) {
503
-            throw new UnexpectedTypeException('The "in" operator must be given a mutlivalued operand (array, ArrayAccess, Traversable).', 1264678095);
504
-        }
505
-        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_IN, $operand);
506
-    }
507
-
508
-    /**
509
-     * Returns a less than criterion used for matching objects against a query
510
-     *
511
-     * @param string $propertyName The name of the property to compare against
512
-     * @param mixed $operand The value to compare with
513
-     * @return ComparisonInterface
514
-     * @api
515
-     */
516
-    public function lessThan($propertyName, $operand)
517
-    {
518
-        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN, $operand);
519
-    }
520
-
521
-    /**
522
-     * Returns a less or equal than criterion used for matching objects against a query
523
-     *
524
-     * @param string $propertyName The name of the property to compare against
525
-     * @param mixed $operand The value to compare with
526
-     * @return ComparisonInterface
527
-     * @api
528
-     */
529
-    public function lessThanOrEqual($propertyName, $operand)
530
-    {
531
-        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO, $operand);
532
-    }
533
-
534
-    /**
535
-     * Returns a greater than criterion used for matching objects against a query
536
-     *
537
-     * @param string $propertyName The name of the property to compare against
538
-     * @param mixed $operand The value to compare with
539
-     * @return ComparisonInterface
540
-     * @api
541
-     */
542
-    public function greaterThan($propertyName, $operand)
543
-    {
544
-        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN, $operand);
545
-    }
546
-
547
-    /**
548
-     * Returns a greater than or equal criterion used for matching objects against a query
549
-     *
550
-     * @param string $propertyName The name of the property to compare against
551
-     * @param mixed $operand The value to compare with
552
-     * @return ComparisonInterface
553
-     * @api
554
-     */
555
-    public function greaterThanOrEqual($propertyName, $operand)
556
-    {
557
-        return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $operand);
558
-    }
559
-
560
-    /**
561
-     * Returns the query result count.
562
-     *
563
-     * @return integer The query result count
564
-     * @api
565
-     */
566
-    public function count()
567
-    {
568
-        /** @var VidiDbBackend $backend */
569
-        $backend = GeneralUtility::makeInstance(VidiDbBackend::class, $this);
570
-        return $backend->countResult();
571
-    }
572
-
573
-    /**
574
-     * Returns an "isEmpty" criterion used for matching objects against a query.
575
-     * It matches if the multivalued property contains no values or is null.
576
-     *
577
-     * @param string $propertyName The name of the multivalued property to compare against
578
-     * @throws NotImplementedException
579
-     * @throws InvalidQueryException if used on a single-valued property
580
-     * @api
581
-     */
582
-    public function isEmpty($propertyName)
583
-    {
584
-        throw new NotImplementedException(__METHOD__);
585
-    }
586
-
587
-    /**
588
-     * @return string
589
-     */
590
-    public function getDistinct()
591
-    {
592
-        return $this->distinct;
593
-    }
594
-
595
-    /**
596
-     * @param string $distinct
597
-     * @return $this
598
-     */
599
-    public function setDistinct($distinct)
600
-    {
601
-        $this->distinct = $distinct;
602
-        return $this;
603
-    }
604
-
605
-    /**
606
-     * Sets the statement of this query. If you use this, you will lose the abstraction from a concrete storage
607
-     * backend (database).
608
-     *
609
-     * @param string|\TYPO3\CMS\Core\Database\PreparedStatement $statement The statement
610
-     * @param array $parameters An array of parameters. These will be bound to placeholders '?' in the $statement.
611
-     * @return QueryInterface
612
-     */
613
-    public function statement($statement, array $parameters = array())
614
-    {
615
-        $this->statement = $this->qomFactory->statement($statement, $parameters);
616
-        return $this;
617
-    }
618
-
619
-    /**
620
-     * Returns the statement of this query.
621
-     *
622
-     * @return Statement
623
-     */
624
-    public function getStatement()
625
-    {
626
-        return $this->statement;
627
-    }
628
-
629
-    /**
630
-     * Returns whether the current mode is Backend.
631
-     *
632
-     * @return bool
633
-     */
634
-    protected function isBackendMode()
635
-    {
636
-        return ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend();
637
-    }
638
-
639
-    /**
640
-     * @return string
641
-     */
642
-    public function getSourceFieldName()
643
-    {
644
-        return $this->sourceFieldName;
645
-    }
646
-
647
-    /**
648
-     * @param string $sourceFieldName
649
-     * @return $this
650
-     */
651
-    public function setSourceFieldName($sourceFieldName)
652
-    {
653
-        $this->sourceFieldName = $sourceFieldName;
654
-        return $this;
655
-    }
656
-
657
-    public function setQuerySettings(QuerySettingsInterface $querySettings)
658
-    {
659
-        $this->typo3QuerySettings = $querySettings;
660
-    }
661
-
662
-    public function getQuerySettings()
663
-    {
664
-        return $this->typo3QuerySettings;
665
-    }
44
+	/**
45
+	 * An inner join.
46
+	 */
47
+	const JCR_JOIN_TYPE_INNER = '{http://www.jcp.org/jcr/1.0}joinTypeInner';
48
+
49
+	/**
50
+	 * A left-outer join.
51
+	 */
52
+	const JCR_JOIN_TYPE_LEFT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeLeftOuter';
53
+
54
+	/**
55
+	 * A right-outer join.
56
+	 */
57
+	const JCR_JOIN_TYPE_RIGHT_OUTER = '{http://www.jcp.org/jcr/1.0}joinTypeRightOuter';
58
+
59
+	/**
60
+	 * Charset of strings in QOM
61
+	 */
62
+	const CHARSET = 'utf-8';
63
+
64
+	/**
65
+	 * @var string
66
+	 */
67
+	protected $sourceFieldName;
68
+
69
+	/**
70
+	 * @var string
71
+	 */
72
+	protected $type;
73
+
74
+	/**
75
+	 * @var PersistenceManagerInterface
76
+	 */
77
+	protected $persistenceManager;
78
+
79
+	/**
80
+	 * @var QueryObjectModelFactory
81
+	 */
82
+	protected $qomFactory;
83
+
84
+	/**
85
+	 * @var SourceInterface
86
+	 */
87
+	protected $source;
88
+
89
+	/**
90
+	 * @var ConstraintInterface
91
+	 */
92
+	protected $constraint;
93
+
94
+	/**
95
+	 * @var Statement
96
+	 */
97
+	protected $statement;
98
+
99
+	/**
100
+	 * @var array
101
+	 */
102
+	protected $orderings = [];
103
+
104
+	/**
105
+	 * @var int
106
+	 */
107
+	protected $limit;
108
+
109
+	/**
110
+	 * @var int
111
+	 */
112
+	protected $offset;
113
+
114
+	/**
115
+	 * Apply DISTINCT upon property.
116
+	 *
117
+	 * @var string
118
+	 */
119
+	protected $distinct;
120
+
121
+	/**
122
+	 * The query settings.
123
+	 *
124
+	 * @var Typo3QuerySettings
125
+	 */
126
+	public Typo3QuerySettings $typo3QuerySettings;
127
+
128
+	/**
129
+	 * Constructs a query object working on the given class name
130
+	 *
131
+	 * @param string $type
132
+	 */
133
+	public function __construct($type)
134
+	{
135
+		$this->type = $type;
136
+		$this->persistenceManager = GeneralUtility::makeInstance(PersistenceManagerInterface::class);
137
+		$this->qomFactory = GeneralUtility::makeInstance(QueryObjectModelFactory::class);
138
+	}
139
+
140
+	public function injectTypo3QuerySettings(Typo3QuerySettings $querySettings): void
141
+	{
142
+		$this->typo3QuerySettings = $querySettings;
143
+	}
144
+
145
+	/**
146
+	 * Sets the Query Settings. These Query settings must match the settings expected by
147
+	 * the specific Storage Backend.
148
+	 *
149
+	 * @param QuerySettingsInterface $typo3QuerySettings The Query Settings
150
+	 * @return void
151
+	 */
152
+	public function setTypo3QuerySettings(QuerySettingsInterface $typo3QuerySettings)
153
+	{
154
+		$this->typo3QuerySettings = $typo3QuerySettings;
155
+	}
156
+
157
+	/**
158
+	 * Returns the Query Settings.
159
+	 *
160
+	 * @throws \Exception
161
+	 * @return Typo3QuerySettings $querySettings The Query Settings
162
+	 * @api This method is not part of FLOW3 API
163
+	 */
164
+	public function getTypo3QuerySettings()
165
+	{
166
+		if (!$this->typo3QuerySettings instanceof QuerySettingsInterface) {
167
+			throw new Exception('Tried to get the query settings without setting them before.', 1248689115);
168
+		}
169
+
170
+		// Apply possible settings to the query.
171
+		if ($this->isBackendMode()) {
172
+			/** @var BackendConfigurationManager $backendConfigurationManager */
173
+			$backendConfigurationManager = GeneralUtility::makeInstance(BackendConfigurationManager::class);
174
+			$configuration = $backendConfigurationManager->getTypoScriptSetup();
175
+			$querySettings = array('respectSysLanguage');
176
+			foreach ($querySettings as $setting) {
177
+				if (isset($configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type . '.'][$setting])) {
178
+					$value = (bool)$configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type . '.'][$setting];
179
+					ObjectAccess::setProperty($this->typo3QuerySettings, $setting, $value);
180
+				}
181
+			}
182
+		}
183
+
184
+		return $this->typo3QuerySettings;
185
+	}
186
+
187
+	/**
188
+	 * Returns the type this query cares for.
189
+	 *
190
+	 * @return string
191
+	 * @api
192
+	 */
193
+	public function getType()
194
+	{
195
+		return $this->type;
196
+	}
197
+
198
+	/**
199
+	 * Sets the source to fetch the result from
200
+	 *
201
+	 * @param SourceInterface $source
202
+	 */
203
+	public function setSource(SourceInterface $source)
204
+	{
205
+		$this->source = $source;
206
+	}
207
+
208
+	/**
209
+	 * Returns the selectorn name or an empty string, if the source is not a selector
210
+	 * TODO This has to be checked at another place
211
+	 *
212
+	 * @return string The selector name
213
+	 */
214
+	protected function getSelectorName()
215
+	{
216
+		if ($this->getSource() instanceof SelectorInterface) {
217
+			return $this->source->getSelectorName();
218
+		} else {
219
+			return '';
220
+		}
221
+	}
222
+
223
+	/**
224
+	 * Gets the node-tuple source for this query.
225
+	 *
226
+	 * @return SourceInterface the node-tuple source; non-null
227
+	 */
228
+	public function getSource()
229
+	{
230
+		if ($this->source === null) {
231
+			$this->source = $this->qomFactory->selector($this->getType());
232
+		}
233
+		return $this->source;
234
+	}
235
+
236
+	/**
237
+	 * Executes the query against the database and returns the result
238
+	 *
239
+	 * @return QueryResultInterface|array The query result object or an array if $this->getQuerySettings()->getReturnRawQueryResult() is true
240
+	 * @api
241
+	 */
242
+	public function execute($returnRawQueryResult = false)
243
+	{
244
+		/** @var VidiDbBackend $backend */
245
+		$backend = GeneralUtility::makeInstance(VidiDbBackend::class, $this);
246
+		return $backend->fetchResult();
247
+	}
248
+
249
+	/**
250
+	 * Sets the property names to order the result by. Expected like this:
251
+	 * array(
252
+	 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
253
+	 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
254
+	 * )
255
+	 * where 'foo' and 'bar' are property names.
256
+	 *
257
+	 * @param array $orderings The property names to order by
258
+	 * @return QueryInterface
259
+	 * @api
260
+	 */
261
+	public function setOrderings(array $orderings)
262
+	{
263
+		$this->orderings = $orderings;
264
+		return $this;
265
+	}
266
+
267
+	/**
268
+	 * Returns the property names to order the result by. Like this:
269
+	 * array(
270
+	 * 'foo' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
271
+	 * 'bar' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
272
+	 * )
273
+	 *
274
+	 * @return array
275
+	 */
276
+	public function getOrderings()
277
+	{
278
+		return $this->orderings;
279
+	}
280
+
281
+	/**
282
+	 * Sets the maximum size of the result set to limit. Returns $this to allow
283
+	 * for chaining (fluid interface)
284
+	 *
285
+	 * @param integer $limit
286
+	 * @throws \InvalidArgumentException
287
+	 * @return QueryInterface
288
+	 * @api
289
+	 */
290
+	public function setLimit($limit)
291
+	{
292
+		if (!is_int($limit) || $limit < 1) {
293
+			throw new \InvalidArgumentException('The limit must be an integer >= 1', 1245071870);
294
+		}
295
+		$this->limit = $limit;
296
+		return $this;
297
+	}
298
+
299
+	/**
300
+	 * Resets a previously set maximum size of the result set. Returns $this to allow
301
+	 * for chaining (fluid interface)
302
+	 *
303
+	 * @return QueryInterface
304
+	 * @api
305
+	 */
306
+	public function unsetLimit()
307
+	{
308
+		unset($this->limit);
309
+		return $this;
310
+	}
311
+
312
+	/**
313
+	 * Returns the maximum size of the result set to limit.
314
+	 *
315
+	 * @return integer
316
+	 * @api
317
+	 */
318
+	public function getLimit()
319
+	{
320
+		return $this->limit;
321
+	}
322
+
323
+	/**
324
+	 * Sets the start offset of the result set to offset. Returns $this to
325
+	 * allow for chaining (fluid interface)
326
+	 *
327
+	 * @param integer $offset
328
+	 * @throws \InvalidArgumentException
329
+	 * @return QueryInterface
330
+	 * @api
331
+	 */
332
+	public function setOffset($offset)
333
+	{
334
+		if (!is_int($offset) || $offset < 0) {
335
+			throw new \InvalidArgumentException('The offset must be a positive integer', 1245071872);
336
+		}
337
+		$this->offset = $offset;
338
+		return $this;
339
+	}
340
+
341
+	/**
342
+	 * Returns the start offset of the result set.
343
+	 *
344
+	 * @return integer
345
+	 * @api
346
+	 */
347
+	public function getOffset()
348
+	{
349
+		return $this->offset;
350
+	}
351
+
352
+	/**
353
+	 * The constraint used to limit the result set. Returns $this to allow
354
+	 * for chaining (fluid interface)
355
+	 *
356
+	 * @param ConstraintInterface $constraint
357
+	 * @return QueryInterface
358
+	 * @api
359
+	 */
360
+	public function matching($constraint)
361
+	{
362
+		$this->constraint = $constraint;
363
+		return $this;
364
+	}
365
+
366
+	/**
367
+	 * Gets the constraint for this query.
368
+	 *
369
+	 * @return ConstraintInterface the constraint, or null if none
370
+	 * @api
371
+	 */
372
+	public function getConstraint()
373
+	{
374
+		return $this->constraint;
375
+	}
376
+
377
+	/**
378
+	 * Performs a logical conjunction of the given constraints. The method takes one or more contraints and concatenates them with a boolean AND.
379
+	 * It also scepts a single array of constraints to be concatenated.
380
+	 *
381
+	 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
382
+	 * @throws InvalidNumberOfConstraintsException
383
+	 * @return AndInterface
384
+	 * @api
385
+	 */
386
+	public function logicalAnd($constraint1)
387
+	{
388
+		if (is_array($constraint1)) {
389
+			$resultingConstraint = array_shift($constraint1);
390
+			$constraints = $constraint1;
391
+		} else {
392
+			$constraints = func_get_args();
393
+			$resultingConstraint = array_shift($constraints);
394
+		}
395
+		if ($resultingConstraint === null) {
396
+			throw new InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1401289500);
397
+		}
398
+		foreach ($constraints as $constraint) {
399
+			$resultingConstraint = $this->qomFactory->_and($resultingConstraint, $constraint);
400
+		}
401
+		return $resultingConstraint;
402
+	}
403
+
404
+	/**
405
+	 * Performs a logical disjunction of the two given constraints
406
+	 *
407
+	 * @param mixed $constraint1 The first of multiple constraints or an array of constraints.
408
+	 * @throws InvalidNumberOfConstraintsException
409
+	 * @return OrInterface
410
+	 * @api
411
+	 */
412
+	public function logicalOr($constraint1)
413
+	{
414
+		if (is_array($constraint1)) {
415
+			$resultingConstraint = array_shift($constraint1);
416
+			$constraints = $constraint1;
417
+		} else {
418
+			$constraints = func_get_args();
419
+			$resultingConstraint = array_shift($constraints);
420
+		}
421
+		if ($resultingConstraint === null) {
422
+			throw new InvalidNumberOfConstraintsException('There must be at least one constraint or a non-empty array of constraints given.', 1401289501);
423
+		}
424
+		foreach ($constraints as $constraint) {
425
+			$resultingConstraint = $this->qomFactory->_or($resultingConstraint, $constraint);
426
+		}
427
+		return $resultingConstraint;
428
+	}
429
+
430
+	/**
431
+	 * Performs a logical negation of the given constraint
432
+	 *
433
+	 * @param ConstraintInterface $constraint Constraint to negate
434
+	 * @throws \RuntimeException
435
+	 * @return NotInterface
436
+	 * @api
437
+	 */
438
+	public function logicalNot(ConstraintInterface $constraint)
439
+	{
440
+		return $this->qomFactory->not($constraint);
441
+	}
442
+
443
+	/**
444
+	 * Returns an equals criterion used for matching objects against a query
445
+	 *
446
+	 * @param string $propertyName The name of the property to compare against
447
+	 * @param mixed $operand The value to compare with
448
+	 * @param boolean $caseSensitive Whether the equality test should be done case-sensitive
449
+	 * @return ComparisonInterface
450
+	 * @api
451
+	 */
452
+	public function equals($propertyName, $operand, $caseSensitive = true)
453
+	{
454
+		if (is_object($operand) || $caseSensitive) {
455
+			$comparison = $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_EQUAL_TO, $operand);
456
+		} else {
457
+			$comparison = $this->qomFactory->comparison($this->qomFactory->lowerCase($this->qomFactory->propertyValue($propertyName, $this->getSelectorName())), QueryInterface::OPERATOR_EQUAL_TO, mb_strtolower($operand, \TYPO3\CMS\Extbase\Persistence\Generic\Query::CHARSET));
458
+		}
459
+		return $comparison;
460
+	}
461
+
462
+	/**
463
+	 * Returns a like criterion used for matching objects against a query
464
+	 *
465
+	 * @param string $propertyName The name of the property to compare against
466
+	 * @param mixed $operand The value to compare with
467
+	 * @param boolean $caseSensitive Whether the matching should be done case-sensitive
468
+	 * @return ComparisonInterface
469
+	 * @api
470
+	 */
471
+	public function like($propertyName, $operand, $caseSensitive = true)
472
+	{
473
+		return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LIKE, $operand);
474
+	}
475
+
476
+	/**
477
+	 * Returns a "contains" criterion used for matching objects against a query.
478
+	 * It matches if the multivalued property contains the given operand.
479
+	 *
480
+	 * @param string $propertyName The name of the (multivalued) property to compare against
481
+	 * @param mixed $operand The value to compare with
482
+	 * @return ComparisonInterface
483
+	 * @api
484
+	 */
485
+	public function contains($propertyName, $operand)
486
+	{
487
+		return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_CONTAINS, $operand);
488
+	}
489
+
490
+	/**
491
+	 * Returns an "in" criterion used for matching objects against a query. It
492
+	 * matches if the property's value is contained in the multivalued operand.
493
+	 *
494
+	 * @param string $propertyName The name of the property to compare against
495
+	 * @param mixed $operand The value to compare with, multivalued
496
+	 * @throws UnexpectedTypeException
497
+	 * @return ComparisonInterface
498
+	 * @api
499
+	 */
500
+	public function in($propertyName, $operand)
501
+	{
502
+		if (!is_array($operand) && !$operand instanceof \ArrayAccess && !$operand instanceof \Traversable) {
503
+			throw new UnexpectedTypeException('The "in" operator must be given a mutlivalued operand (array, ArrayAccess, Traversable).', 1264678095);
504
+		}
505
+		return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_IN, $operand);
506
+	}
507
+
508
+	/**
509
+	 * Returns a less than criterion used for matching objects against a query
510
+	 *
511
+	 * @param string $propertyName The name of the property to compare against
512
+	 * @param mixed $operand The value to compare with
513
+	 * @return ComparisonInterface
514
+	 * @api
515
+	 */
516
+	public function lessThan($propertyName, $operand)
517
+	{
518
+		return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN, $operand);
519
+	}
520
+
521
+	/**
522
+	 * Returns a less or equal than criterion used for matching objects against a query
523
+	 *
524
+	 * @param string $propertyName The name of the property to compare against
525
+	 * @param mixed $operand The value to compare with
526
+	 * @return ComparisonInterface
527
+	 * @api
528
+	 */
529
+	public function lessThanOrEqual($propertyName, $operand)
530
+	{
531
+		return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO, $operand);
532
+	}
533
+
534
+	/**
535
+	 * Returns a greater than criterion used for matching objects against a query
536
+	 *
537
+	 * @param string $propertyName The name of the property to compare against
538
+	 * @param mixed $operand The value to compare with
539
+	 * @return ComparisonInterface
540
+	 * @api
541
+	 */
542
+	public function greaterThan($propertyName, $operand)
543
+	{
544
+		return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN, $operand);
545
+	}
546
+
547
+	/**
548
+	 * Returns a greater than or equal criterion used for matching objects against a query
549
+	 *
550
+	 * @param string $propertyName The name of the property to compare against
551
+	 * @param mixed $operand The value to compare with
552
+	 * @return ComparisonInterface
553
+	 * @api
554
+	 */
555
+	public function greaterThanOrEqual($propertyName, $operand)
556
+	{
557
+		return $this->qomFactory->comparison($this->qomFactory->propertyValue($propertyName, $this->getSelectorName()), QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO, $operand);
558
+	}
559
+
560
+	/**
561
+	 * Returns the query result count.
562
+	 *
563
+	 * @return integer The query result count
564
+	 * @api
565
+	 */
566
+	public function count()
567
+	{
568
+		/** @var VidiDbBackend $backend */
569
+		$backend = GeneralUtility::makeInstance(VidiDbBackend::class, $this);
570
+		return $backend->countResult();
571
+	}
572
+
573
+	/**
574
+	 * Returns an "isEmpty" criterion used for matching objects against a query.
575
+	 * It matches if the multivalued property contains no values or is null.
576
+	 *
577
+	 * @param string $propertyName The name of the multivalued property to compare against
578
+	 * @throws NotImplementedException
579
+	 * @throws InvalidQueryException if used on a single-valued property
580
+	 * @api
581
+	 */
582
+	public function isEmpty($propertyName)
583
+	{
584
+		throw new NotImplementedException(__METHOD__);
585
+	}
586
+
587
+	/**
588
+	 * @return string
589
+	 */
590
+	public function getDistinct()
591
+	{
592
+		return $this->distinct;
593
+	}
594
+
595
+	/**
596
+	 * @param string $distinct
597
+	 * @return $this
598
+	 */
599
+	public function setDistinct($distinct)
600
+	{
601
+		$this->distinct = $distinct;
602
+		return $this;
603
+	}
604
+
605
+	/**
606
+	 * Sets the statement of this query. If you use this, you will lose the abstraction from a concrete storage
607
+	 * backend (database).
608
+	 *
609
+	 * @param string|\TYPO3\CMS\Core\Database\PreparedStatement $statement The statement
610
+	 * @param array $parameters An array of parameters. These will be bound to placeholders '?' in the $statement.
611
+	 * @return QueryInterface
612
+	 */
613
+	public function statement($statement, array $parameters = array())
614
+	{
615
+		$this->statement = $this->qomFactory->statement($statement, $parameters);
616
+		return $this;
617
+	}
618
+
619
+	/**
620
+	 * Returns the statement of this query.
621
+	 *
622
+	 * @return Statement
623
+	 */
624
+	public function getStatement()
625
+	{
626
+		return $this->statement;
627
+	}
628
+
629
+	/**
630
+	 * Returns whether the current mode is Backend.
631
+	 *
632
+	 * @return bool
633
+	 */
634
+	protected function isBackendMode()
635
+	{
636
+		return ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend();
637
+	}
638
+
639
+	/**
640
+	 * @return string
641
+	 */
642
+	public function getSourceFieldName()
643
+	{
644
+		return $this->sourceFieldName;
645
+	}
646
+
647
+	/**
648
+	 * @param string $sourceFieldName
649
+	 * @return $this
650
+	 */
651
+	public function setSourceFieldName($sourceFieldName)
652
+	{
653
+		$this->sourceFieldName = $sourceFieldName;
654
+		return $this;
655
+	}
656
+
657
+	public function setQuerySettings(QuerySettingsInterface $querySettings)
658
+	{
659
+		$this->typo3QuerySettings = $querySettings;
660
+	}
661
+
662
+	public function getQuerySettings()
663
+	{
664
+		return $this->typo3QuerySettings;
665
+	}
666 666
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -174,8 +174,8 @@
 block discarded – undo
174 174
             $configuration = $backendConfigurationManager->getTypoScriptSetup();
175 175
             $querySettings = array('respectSysLanguage');
176 176
             foreach ($querySettings as $setting) {
177
-                if (isset($configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type . '.'][$setting])) {
178
-                    $value = (bool)$configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type . '.'][$setting];
177
+                if (isset($configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type.'.'][$setting])) {
178
+                    $value = (bool)$configuration['config.']['tx_vidi.']['persistence.']['backend.'][$this->type.'.'][$setting];
179 179
                     ObjectAccess::setProperty($this->typo3QuerySettings, $setting, $value);
180 180
                 }
181 181
             }
Please login to merge, or discard this patch.
Classes/Persistence/Storage/VidiDbBackend.php 1 patch
Indentation   +1047 added lines, -1047 removed lines patch added patch discarded remove patch
@@ -51,1052 +51,1052 @@
 block discarded – undo
51 51
 class VidiDbBackend
52 52
 {
53 53
 
54
-    const OPERATOR_EQUAL_TO_NULL = 'operatorEqualToNull';
55
-    const OPERATOR_NOT_EQUAL_TO_NULL = 'operatorNotEqualToNull';
56
-
57
-    /**
58
-     * The TYPO3 page repository. Used for language and workspace overlay
59
-     *
60
-     * @var PageRepository
61
-     */
62
-    protected $pageRepository;
63
-
64
-    /**
65
-     * @var Query
66
-     */
67
-    protected $query;
68
-
69
-    /**
70
-     * Store some info related to table name and its aliases.
71
-     *
72
-     * @var array
73
-     */
74
-    protected $tableNameAliases = array(
75
-        'aliases' => [],
76
-        'aliasIncrement' => [],
77
-    );
78
-
79
-    /**
80
-     * Use to store the current foreign table name alias.
81
-     *
82
-     * @var string
83
-     */
84
-    protected $currentChildTableNameAlias = '';
85
-
86
-    /**
87
-     * @param Query $query
88
-     */
89
-    public function __construct(Query $query)
90
-    {
91
-        $this->query = $query;
92
-    }
93
-
94
-    /**
95
-     * @param $parameters
96
-     * @return array
97
-     */
98
-    protected static function getTypes($parameters)
99
-    {
100
-        $types = [];
101
-        foreach ($parameters as $parameter) {
102
-            if (is_array($parameter)) {
103
-                if (MathUtility::canBeInterpretedAsInteger($parameter[0])) {
104
-                    $types[] = \Doctrine\DBAL\Connection::PARAM_INT_ARRAY;
105
-                } else {
106
-                    $types[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
107
-                }
108
-            } else {
109
-                if (MathUtility::canBeInterpretedAsInteger($parameter)) {
110
-                    $types[] = ParameterType::INTEGER;
111
-                } else {
112
-                    $types[] = ParameterType::STRING;
113
-                }
114
-            }
115
-        }
116
-        return $types;
117
-    }
118
-
119
-    /**
120
-     * Returns the result of the query
121
-     */
122
-    public function fetchResult()
123
-    {
124
-        $parameters = [];
125
-        $statementParts = $this->parseQuery($parameters);
126
-        $statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
127
-        $sql = $this->buildQuery($statementParts);
128
-        //print $sql; exit();
129
-
130
-        $rows = $this->getConnection()
131
-            ->executeQuery($sql, $parameters, self::getTypes($parameters))
132
-            ->fetchAll();
133
-
134
-        return $this->getContentObjects($rows);
135
-    }
136
-
137
-    /**
138
-     * Returns the number of tuples matching the query.
139
-     *
140
-     * @return int The number of matching tuples
141
-     */
142
-    public function countResult()
143
-    {
144
-        $parameters = [];
145
-        $statementParts = $this->parseQuery($parameters);
146
-        $statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
147
-        $types = self::getTypes($parameters);
148
-
149
-        // if limit is set, we need to count the rows "manually" as COUNT(*) ignores LIMIT constraints
150
-        if (!empty($statementParts['limit'])) {
151
-            $sql = $this->buildQuery($statementParts);
152
-
153
-            $count = $this
154
-                ->getConnection()
155
-                ->executeQuery($sql, $parameters, $types)
156
-                ->rowCount();
157
-        } else {
158
-            $statementParts['fields'] = array('COUNT(*)');
159
-            // having orderings without grouping is not compatible with non-MySQL DBMS
160
-            $statementParts['orderings'] = [];
161
-            if (isset($statementParts['keywords']['distinct'])) {
162
-                unset($statementParts['keywords']['distinct']);
163
-                $distinctField = $this->query->getDistinct() ? $this->query->getDistinct() : 'uid';
164
-                $statementParts['fields'] = array('COUNT(DISTINCT ' . $statementParts['mainTable'] . '.' . $distinctField . ')');
165
-            }
166
-
167
-            $sql = $this->buildQuery($statementParts);
168
-            $count = $this
169
-                ->getConnection()
170
-                ->executeQuery($sql, $parameters, $types)
171
-                ->fetchColumn(0);
172
-        }
173
-        return (int)$count;
174
-    }
175
-
176
-    /**
177
-     * Parses the query and returns the SQL statement parts.
178
-     *
179
-     * @param array &$parameters
180
-     * @return array
181
-     */
182
-    public function parseQuery(array &$parameters)
183
-    {
184
-        $statementParts = [];
185
-        $statementParts['keywords'] = [];
186
-        $statementParts['tables'] = [];
187
-        $statementParts['unions'] = [];
188
-        $statementParts['fields'] = [];
189
-        $statementParts['where'] = [];
190
-        $statementParts['additionalWhereClause'] = [];
191
-        $statementParts['orderings'] = [];
192
-        $statementParts['limit'] = [];
193
-        $query = $this->query;
194
-        $source = $query->getSource();
195
-        $this->parseSource($source, $statementParts);
196
-        $this->parseConstraint($query->getConstraint(), $source, $statementParts, $parameters);
197
-        $this->parseOrderings($query->getOrderings(), $source, $statementParts);
198
-        $this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $statementParts);
199
-        $tableNames = array_unique(array_keys($statementParts['tables'] + $statementParts['unions']));
200
-        foreach ($tableNames as $tableNameOrAlias) {
201
-            if (is_string($tableNameOrAlias) && strlen($tableNameOrAlias) > 0) {
202
-                $this->addAdditionalWhereClause($query->getTypo3QuerySettings(), $tableNameOrAlias, $statementParts);
203
-            }
204
-        }
205
-
206
-        return $statementParts;
207
-    }
208
-
209
-    /**
210
-     * Fiddle with the statement structure to handle recursive MM relations.
211
-     * For the recursive MM query to work, we must invert some values.
212
-     * Let see if that is the best way of doing that...
213
-     *
214
-     * @param array $statementParts
215
-     * @return array
216
-     */
217
-    public function processStatementStructureForRecursiveMMRelation(array $statementParts)
218
-    {
219
-
220
-        if ($this->hasRecursiveMMRelation()) {
221
-            $tableName = $this->query->getType();
222
-
223
-            // In order the MM query to work for a recursive MM query, we must invert some values.
224
-            // tx_domain_model_foo0 (the alias) <--> tx_domain_model_foo (the origin table name)
225
-            $values = [];
226
-            foreach ($statementParts['fields'] as $key => $value) {
227
-                $values[$key] = str_replace($tableName, $tableName . '0', $value);
228
-            }
229
-            $statementParts['fields'] = $values;
230
-
231
-            // Same comment as above.
232
-            $values = [];
233
-            foreach ($statementParts['where'] as $key => $value) {
234
-                $values[$key] = str_replace($tableName . '0', $tableName, $value);
235
-            }
236
-            $statementParts['where'] = $values;
237
-
238
-            // We must be more restrictive by transforming the "left" union by "inner"
239
-            $values = [];
240
-            foreach ($statementParts['unions'] as $key => $value) {
241
-                $values[$key] = str_replace('LEFT JOIN', 'INNER JOIN', $value);
242
-            }
243
-            $statementParts['unions'] = $values;
244
-        }
245
-
246
-        return $statementParts;
247
-    }
248
-
249
-    /**
250
-     * Tell whether there is a recursive MM relation.
251
-     *
252
-     * @return bool
253
-     */
254
-    public function hasRecursiveMMRelation()
255
-    {
256
-        return isset($this->tableNameAliases['aliasIncrement'][$this->query->getType()]);
257
-
258
-    }
259
-
260
-    /**
261
-     * Returns the statement, ready to be executed.
262
-     *
263
-     * @param array $statementParts The SQL statement parts
264
-     * @return string The SQL statement
265
-     */
266
-    public function buildQuery(array $statementParts)
267
-    {
268
-
269
-        // Add more statement to the UNION part.
270
-        if (!empty($statementParts['unions'])) {
271
-            foreach ($statementParts['unions'] as $tableName => $unionPart) {
272
-                if (!empty($statementParts['additionalWhereClause'][$tableName])) {
273
-                    $statementParts['unions'][$tableName] .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
274
-                }
275
-            }
276
-        }
277
-
278
-        $statement = 'SELECT ' . implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']) . ' FROM ' . implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']);
279
-        if (!empty($statementParts['where'])) {
280
-            $statement .= ' WHERE ' . implode('', $statementParts['where']);
281
-            if (!empty($statementParts['additionalWhereClause'][$this->query->getType()])) {
282
-                $statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
283
-            }
284
-        } elseif (!empty($statementParts['additionalWhereClause'])) {
285
-            $statement .= ' WHERE ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
286
-        }
287
-        if (!empty($statementParts['orderings'])) {
288
-            $statement .= ' ORDER BY ' . implode(', ', $statementParts['orderings']);
289
-        }
290
-        if (!empty($statementParts['limit'])) {
291
-            $statement .= ' LIMIT ' . $statementParts['limit'];
292
-        }
293
-
294
-        return $statement;
295
-    }
296
-
297
-    /**
298
-     * Transforms a Query Source into SQL and parameter arrays
299
-     *
300
-     * @param SourceInterface $source The source
301
-     * @param array &$sql
302
-     * @return void
303
-     */
304
-    protected function parseSource(SourceInterface $source, array &$sql)
305
-    {
306
-        $tableName = $this->getTableName();
307
-        $sql['fields'][$tableName] = $tableName . '.*';
308
-        if ($this->query->getDistinct()) {
309
-            $sql['fields'][$tableName] = $tableName . '.' . $this->query->getDistinct();
310
-            $sql['keywords']['distinct'] = 'DISTINCT';
311
-        }
312
-        $sql['tables'][$tableName] = $tableName;
313
-        $sql['mainTable'] = $tableName;
314
-    }
315
-
316
-    /**
317
-     * Transforms a constraint into SQL and parameter arrays
318
-     *
319
-     * @param ConstraintInterface $constraint The constraint
320
-     * @param SourceInterface $source The source
321
-     * @param array &$statementParts The query parts
322
-     * @param array &$parameters The parameters that will replace the markers
323
-     * @return void
324
-     */
325
-    protected function parseConstraint(ConstraintInterface $constraint = null, SourceInterface $source, array &$statementParts, array &$parameters)
326
-    {
327
-        if ($constraint instanceof AndInterface) {
328
-            $statementParts['where'][] = '(';
329
-            $this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
330
-            $statementParts['where'][] = ' AND ';
331
-            $this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
332
-            $statementParts['where'][] = ')';
333
-        } elseif ($constraint instanceof OrInterface) {
334
-            $statementParts['where'][] = '(';
335
-            $this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
336
-            $statementParts['where'][] = ' OR ';
337
-            $this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
338
-            $statementParts['where'][] = ')';
339
-        } elseif ($constraint instanceof NotInterface) {
340
-            $statementParts['where'][] = 'NOT (';
341
-            $this->parseConstraint($constraint->getConstraint(), $source, $statementParts, $parameters);
342
-            $statementParts['where'][] = ')';
343
-        } elseif ($constraint instanceof ComparisonInterface) {
344
-            $this->parseComparison($constraint, $source, $statementParts, $parameters);
345
-        }
346
-    }
347
-
348
-    /**
349
-     * Parse a Comparison into SQL and parameter arrays.
350
-     *
351
-     * @param ComparisonInterface $comparison The comparison to parse
352
-     * @param SourceInterface $source The source
353
-     * @param array &$statementParts SQL query parts to add to
354
-     * @param array &$parameters Parameters to bind to the SQL
355
-     * @return void
356
-     * @throws Exception\RepositoryException
357
-     */
358
-    protected function parseComparison(ComparisonInterface $comparison, SourceInterface $source, array &$statementParts, array &$parameters)
359
-    {
360
-        $operand1 = $comparison->getOperand1();
361
-        $operator = $comparison->getOperator();
362
-        $operand2 = $comparison->getOperand2();
363
-        if ($operator === QueryInterface::OPERATOR_IN) {
364
-            $items = [];
365
-            $hasValue = false;
366
-            foreach ($operand2 as $value) {
367
-                $value = $this->getPlainValue($value);
368
-                if ($value !== null) {
369
-                    $items[] = $value;
370
-                    $hasValue = true;
371
-                }
372
-            }
373
-            if ($hasValue === false) {
374
-                $statementParts['where'][] = '1<>1';
375
-            } else {
376
-                $this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters, null);
377
-                $parameters[] = $items;
378
-            }
379
-        } elseif ($operator === QueryInterface::OPERATOR_CONTAINS) {
380
-            if ($operand2 === null) {
381
-                $statementParts['where'][] = '1<>1';
382
-            } else {
383
-                throw new \Exception('Not implemented! Contact extension author.', 1412931227);
384
-                # @todo re-implement me if necessary.
385
-                #$tableName = $this->query->getType();
386
-                #$propertyName = $operand1->getPropertyName();
387
-                #while (strpos($propertyName, '.') !== false) {
388
-                #	$this->addUnionStatement($tableName, $propertyName, $statementParts);
389
-                #}
390
-                #$columnName = $propertyName;
391
-                #$columnMap = $propertyName;
392
-                #$typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
393
-                #if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
394
-                #	$relationTableName = $columnMap->getRelationTableName();
395
-                #	$statementParts['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=?)';
396
-                #	$parameters[] = intval($this->getPlainValue($operand2));
397
-                #} elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
398
-                #	$parentKeyFieldName = $columnMap->getParentKeyFieldName();
399
-                #	if (isset($parentKeyFieldName)) {
400
-                #		$childTableName = $columnMap->getChildTableName();
401
-                #		$statementParts['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=?)';
402
-                #		$parameters[] = intval($this->getPlainValue($operand2));
403
-                #	} else {
404
-                #		$statementParts['where'][] = 'FIND_IN_SET(?,' . $tableName . '.' . $columnName . ')';
405
-                #		$parameters[] = intval($this->getPlainValue($operand2));
406
-                #	}
407
-                #} else {
408
-                #	throw new Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
409
-                #}
410
-            }
411
-        } else {
412
-            if ($operand2 === null) {
413
-                if ($operator === QueryInterface::OPERATOR_EQUAL_TO) {
414
-                    $operator = self::OPERATOR_EQUAL_TO_NULL;
415
-                } elseif ($operator === QueryInterface::OPERATOR_NOT_EQUAL_TO) {
416
-                    $operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
417
-                }
418
-            }
419
-            $this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters);
420
-            $parameters[] = $this->getPlainValue($operand2);
421
-        }
422
-    }
423
-
424
-    /**
425
-     * Returns a plain value, i.e. objects are flattened if possible.
426
-     *
427
-     * @param mixed $input
428
-     * @return mixed
429
-     * @throws UnexpectedTypeException
430
-     */
431
-    protected function getPlainValue($input)
432
-    {
433
-        if (is_array($input)) {
434
-            throw new UnexpectedTypeException('An array could not be converted to a plain value.', 1274799932);
435
-        }
436
-        if ($input instanceof \DateTime) {
437
-            return $input->format('U');
438
-        } elseif (is_object($input)) {
439
-            if ($input instanceof LazyLoadingProxy) {
440
-                $realInput = $input->_loadRealInstance();
441
-            } else {
442
-                $realInput = $input;
443
-            }
444
-            if ($realInput instanceof DomainObjectInterface) {
445
-                return $realInput->getUid();
446
-            } else {
447
-                throw new UnexpectedTypeException('An object of class "' . get_class($realInput) . '" could not be converted to a plain value.', 1274799934);
448
-            }
449
-        } elseif (is_bool($input)) {
450
-            return $input === true ? 1 : 0;
451
-        } else {
452
-            return $input;
453
-        }
454
-    }
455
-
456
-    /**
457
-     * Parse a DynamicOperand into SQL and parameter arrays.
458
-     *
459
-     * @param DynamicOperandInterface $operand
460
-     * @param string $operator One of the JCR_OPERATOR_* constants
461
-     * @param SourceInterface $source The source
462
-     * @param array &$statementParts The query parts
463
-     * @param array &$parameters The parameters that will replace the markers
464
-     * @param string $valueFunction an optional SQL function to apply to the operand value
465
-     * @return void
466
-     */
467
-    protected function parseDynamicOperand(DynamicOperandInterface $operand, $operator, SourceInterface $source, array &$statementParts, array &$parameters, $valueFunction = null)
468
-    {
469
-        if ($operand instanceof LowerCaseInterface) {
470
-            $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'LOWER');
471
-        } elseif ($operand instanceof UpperCaseInterface) {
472
-            $this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'UPPER');
473
-        } elseif ($operand instanceof PropertyValueInterface) {
474
-            $propertyName = $operand->getPropertyName();
475
-
476
-            // Reset value.
477
-            $this->currentChildTableNameAlias = '';
478
-
479
-            if ($source instanceof SelectorInterface) {
480
-                $tableName = $this->query->getType();
481
-                while (strpos($propertyName, '.') !== false) {
482
-                    $this->addUnionStatement($tableName, $propertyName, $statementParts);
483
-                }
484
-            } elseif ($source instanceof JoinInterface) {
485
-                $tableName = $source->getJoinCondition()->getSelector1Name();
486
-            }
487
-
488
-            $columnName = $propertyName;
489
-            $resolvedOperator = $this->resolveOperator($operator);
490
-            $constraintSQL = '';
491
-
492
-            $marker = $operator === QueryInterface::OPERATOR_IN
493
-                ? '(?)'
494
-                : '?';
495
-
496
-            if ($valueFunction === null) {
497
-                $constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $resolvedOperator . ' ' . $marker;
498
-            } else {
499
-                $constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $resolvedOperator . ' ' . $marker;
500
-            }
501
-
502
-            if (isset($tableName) && !empty($this->currentChildTableNameAlias)) {
503
-                $constraintSQL = $this->replaceTableNameByAlias($tableName, $this->currentChildTableNameAlias, $constraintSQL);
504
-            }
505
-            $statementParts['where'][] = $constraintSQL;
506
-        }
507
-    }
508
-
509
-    /**
510
-     * @param string &$tableName
511
-     * @param string &$propertyPath
512
-     * @param array &$statementParts
513
-     */
514
-    protected function addUnionStatement(&$tableName, &$propertyPath, array &$statementParts)
515
-    {
516
-
517
-        $table = Tca::table($tableName);
518
-
519
-        $explodedPropertyPath = explode('.', $propertyPath, 2);
520
-        $fieldName = $explodedPropertyPath[0];
521
-
522
-        // Field of type "group" are special because property path must contain the table name
523
-        // to determine the relation type. Example for sys_category, property path will look like "items.sys_file"
524
-        $parts = explode('.', $propertyPath, 3);
525
-        if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
526
-            $explodedPropertyPath[0] = $parts[0] . '.' . $parts[1];
527
-            $explodedPropertyPath[1] = $parts[2];
528
-            $fieldName = $explodedPropertyPath[0];
529
-        }
530
-
531
-        $parentKeyFieldName = $table->field($fieldName)->getForeignField();
532
-        $childTableName = $table->field($fieldName)->getForeignTable();
533
-
534
-        if ($childTableName === null) {
535
-            throw new InvalidRelationConfigurationException('The relation information for property "' . $fieldName . '" of class "' . $tableName . '" is missing.', 1353170925);
536
-        }
537
-
538
-        if ($table->field($fieldName)->hasOne()) { // includes relation "one-to-one" and "many-to-one"
539
-            // sometimes the opposite relation is not defined. We don't want to force this config for backward compatibility reasons.
540
-            // $parentKeyFieldName === null does the trick somehow. Before condition was if (isset($parentKeyFieldName))
541
-            if ($table->field($fieldName)->hasRelationManyToOne() || $parentKeyFieldName === null) {
542
-                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $fieldName . '=' . $childTableName . '.uid';
543
-            } else {
544
-                $statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
545
-            }
546
-        } elseif ($table->field($fieldName)->hasRelationManyToMany()) {
547
-            $relationTableName = $table->field($fieldName)->getManyToManyTable();
548
-
549
-            $parentKeyFieldName = $table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
550
-            $childKeyFieldName = !$table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
551
-
552
-            // MM table e.g sys_category_record_mm
553
-            $relationTableNameAlias = $this->generateAlias($relationTableName);
554
-            $join = sprintf(
555
-                'LEFT JOIN %s AS %s ON %s.uid=%s.%s', $relationTableName,
556
-                $relationTableNameAlias,
557
-                $tableName,
558
-                $relationTableNameAlias,
559
-                $parentKeyFieldName
560
-            );
561
-            $statementParts['unions'][$relationTableNameAlias] = $join;
562
-
563
-            // Foreign table e.g sys_category
564
-            $childTableNameAlias = $this->generateAlias($childTableName);
565
-            $this->currentChildTableNameAlias = $childTableNameAlias;
566
-            $join = sprintf(
567
-                'LEFT JOIN %s AS %s ON %s.%s=%s.uid',
568
-                $childTableName,
569
-                $childTableNameAlias,
570
-                $relationTableNameAlias,
571
-                $childKeyFieldName,
572
-                $childTableNameAlias
573
-            );
574
-            $statementParts['unions'][$childTableNameAlias] = $join;
575
-
576
-            // Find a possible table name for a MM condition.
577
-            $tableNameCondition = $table->field($fieldName)->getAdditionalTableNameCondition();
578
-            if ($tableNameCondition) {
579
-
580
-                // If we can find a source file name,  we can then retrieve more MM conditions from the TCA such as a field name.
581
-                $sourceFileName = $this->query->getSourceFieldName();
582
-                if (empty($sourceFileName)) {
583
-                    $additionalMMConditions = array(
584
-                        'tablenames' => $tableNameCondition,
585
-                    );
586
-                } else {
587
-                    $additionalMMConditions = Tca::table($tableNameCondition)->field($sourceFileName)->getAdditionalMMCondition();
588
-                }
589
-
590
-                foreach ($additionalMMConditions as $additionalFieldName => $additionalMMCondition) {
591
-                    $additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
592
-                    $statementParts['unions'][$relationTableNameAlias] .= $additionalJoin;
593
-
594
-                    $additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
595
-                    $statementParts['unions'][$childTableNameAlias] .= $additionalJoin;
596
-                }
597
-            }
598
-
599
-        } elseif ($table->field($fieldName)->hasMany()) { // includes relations "many-to-one" and "csv" relations
600
-            $childTableNameAlias = $this->generateAlias($childTableName);
601
-            $this->currentChildTableNameAlias = $childTableNameAlias;
602
-
603
-            if (isset($parentKeyFieldName)) {
604
-                $join = sprintf(
605
-                    'LEFT JOIN %s AS %s ON %s.uid=%s.%s',
606
-                    $childTableName,
607
-                    $childTableNameAlias,
608
-                    $tableName,
609
-                    $childTableNameAlias,
610
-                    $parentKeyFieldName
611
-                );
612
-                $statementParts['unions'][$childTableNameAlias] = $join;
613
-            } else {
614
-                $join = sprintf(
615
-                    'LEFT JOIN %s AS %s ON (FIND_IN_SET(%s.uid, %s.%s))',
616
-                    $childTableName,
617
-                    $childTableNameAlias,
618
-                    $childTableNameAlias,
619
-                    $tableName,
620
-                    $fieldName
621
-                );
622
-                $statementParts['unions'][$childTableNameAlias] = $join;
623
-            }
624
-        } else {
625
-            throw new Exception('Could not determine type of relation.', 1252502725);
626
-        }
627
-
628
-        $statementParts['keywords']['distinct'] = 'DISTINCT';
629
-        $propertyPath = $explodedPropertyPath[1];
630
-        $tableName = $childTableName;
631
-    }
632
-
633
-    /**
634
-     * Returns the SQL operator for the given JCR operator type.
635
-     *
636
-     * @param string $operator One of the JCR_OPERATOR_* constants
637
-     * @return string an SQL operator
638
-     * @throws Exception
639
-     */
640
-    protected function resolveOperator($operator)
641
-    {
642
-        switch ($operator) {
643
-            case self::OPERATOR_EQUAL_TO_NULL:
644
-                $operator = 'IS';
645
-                break;
646
-            case self::OPERATOR_NOT_EQUAL_TO_NULL:
647
-                $operator = 'IS NOT';
648
-                break;
649
-            case QueryInterface::OPERATOR_IN:
650
-                $operator = 'IN';
651
-                break;
652
-            case QueryInterface::OPERATOR_EQUAL_TO:
653
-                $operator = '=';
654
-                break;
655
-            case QueryInterface::OPERATOR_NOT_EQUAL_TO:
656
-                $operator = '!=';
657
-                break;
658
-            case QueryInterface::OPERATOR_LESS_THAN:
659
-                $operator = '<';
660
-                break;
661
-            case QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO:
662
-                $operator = '<=';
663
-                break;
664
-            case QueryInterface::OPERATOR_GREATER_THAN:
665
-                $operator = '>';
666
-                break;
667
-            case QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO:
668
-                $operator = '>=';
669
-                break;
670
-            case QueryInterface::OPERATOR_LIKE:
671
-                $operator = 'LIKE';
672
-                break;
673
-            default:
674
-                throw new Exception('Unsupported operator encountered.', 1242816073);
675
-        }
676
-        return $operator;
677
-    }
678
-
679
-    /**
680
-     * Adds additional WHERE statements according to the query settings.
681
-     *
682
-     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
683
-     * @param string $tableNameOrAlias The table name to add the additional where clause for
684
-     * @param array &$statementParts
685
-     * @return void
686
-     */
687
-    protected function addAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableNameOrAlias, &$statementParts)
688
-    {
689
-        $this->addVisibilityConstraintStatement($querySettings, $tableNameOrAlias, $statementParts);
690
-        if ($querySettings->getRespectSysLanguage()) {
691
-            $this->addSysLanguageStatement($tableNameOrAlias, $statementParts, $querySettings);
692
-        }
693
-    }
694
-
695
-    /**
696
-     * Adds enableFields and deletedClause to the query if necessary
697
-     *
698
-     * @param QuerySettingsInterface $querySettings
699
-     * @param string $tableNameOrAlias The database table name
700
-     * @param array &$statementParts The query parts
701
-     * @return void
702
-     */
703
-    protected function addVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableNameOrAlias, array &$statementParts)
704
-    {
705
-        $statement = '';
706
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
707
-        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
708
-            $ignoreEnableFields = $querySettings->getIgnoreEnableFields();
709
-            $enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
710
-            $includeDeleted = $querySettings->getIncludeDeleted();
711
-            if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()) {
712
-                $statement .= $this->getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
713
-            } else {
714
-                // 'BE' case
715
-                $statement .= $this->getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted);
716
-            }
717
-
718
-            // Remove the prefixing "AND" if any.
719
-            if (!empty($statement)) {
720
-                $statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
721
-                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = $statement;
722
-            }
723
-        }
724
-    }
725
-
726
-    /**
727
-     * Returns constraint statement for frontend context
728
-     *
729
-     * @param string $tableNameOrAlias
730
-     * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
731
-     * @param array $enableFieldsToBeIgnored If $ignoreEnableFields is true, this array specifies enable fields to be ignored. If it is null or an empty array (default) all enable fields are ignored.
732
-     * @param boolean $includeDeleted A flag indicating whether deleted records should be included
733
-     * @return string
734
-     * @throws Exception\InconsistentQuerySettingsException
735
-     */
736
-    protected function getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted)
737
-    {
738
-        $statement = '';
739
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
740
-        if ($ignoreEnableFields && !$includeDeleted) {
741
-            if (count($enableFieldsToBeIgnored)) {
742
-                // array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
743
-                $statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
744
-            } else {
745
-                $statement .= $this->getPageRepository()->deleteClause($tableName);
746
-            }
747
-        } elseif (!$ignoreEnableFields && !$includeDeleted) {
748
-            $statement .= $this->getPageRepository()->enableFields($tableName);
749
-        } elseif (!$ignoreEnableFields && $includeDeleted) {
750
-            throw new InconsistentQuerySettingsException('Query setting "ignoreEnableFields=false" can not be used together with "includeDeleted=true" in frontend context.', 1327678173);
751
-        }
752
-        return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
753
-    }
754
-
755
-    /**
756
-     * Returns constraint statement for backend context
757
-     *
758
-     * @param string $tableNameOrAlias
759
-     * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
760
-     * @param boolean $includeDeleted A flag indicating whether deleted records should be included
761
-     * @return string
762
-     */
763
-    protected function getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted)
764
-    {
765
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
766
-        $statement = '';
767
-        if (!$ignoreEnableFields) {
768
-            $statement .= BackendUtility::BEenableFields($tableName);
769
-        }
770
-
771
-        // If the table is found to have "workspace" support, add the corresponding fields in the statement.
772
-        if (Tca::table($tableName)->hasWorkspaceSupport()) {
773
-            if ($this->getBackendUser()->workspace === 0) {
774
-                $statement .= ' AND ' . $tableName . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
775
-            } else {
776
-                // Show only records of live and of the current workspace
777
-                // In case we are in a Versioning preview
778
-                $statement .= ' AND (' .
779
-                    $tableName . '.t3ver_wsid=0 OR ' .
780
-                    $tableName . '.t3ver_wsid=' . (int)$this->getBackendUser()->workspace .
781
-                    ')';
782
-            }
783
-
784
-            // Check if this segment make sense here or whether it should be in the "if" part when we have workspace = 0
785
-            $statement .= ' AND ' . $tableName . '.pid<>-1';
786
-        }
787
-
788
-        if (!$includeDeleted) {
789
-            $statement .= BackendUtility::deleteClause($tableName);
790
-        }
791
-
792
-        return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
793
-    }
794
-
795
-    /**
796
-     * Builds the language field statement
797
-     *
798
-     * @param string $tableNameOrAlias The database table name
799
-     * @param array &$statementParts The query parts
800
-     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
801
-     * @return void
802
-     * @throws Exception
803
-     */
804
-    protected function addSysLanguageStatement($tableNameOrAlias, array &$statementParts, $querySettings)
805
-    {
806
-        $tableName = $this->resolveTableNameAlias($tableNameOrAlias);
807
-        if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
808
-            if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
809
-                // Select all entries for the current language
810
-                $additionalWhereClause = $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getLanguageUid()) . ',-1)';
811
-                // If any language is set -> get those entries which are not translated yet
812
-                // They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
813
-                if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
814
-                    && $querySettings->getLanguageUid() > 0
815
-                ) {
816
-                    $additionalWhereClause .= ' OR (' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
817
-                        ' AND ' . $tableNameOrAlias . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
818
-                        ' FROM ' . $tableName .
819
-                        ' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
820
-                        ' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
821
-
822
-                    // Add delete clause to ensure all entries are loaded
823
-                    if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
824
-                        $additionalWhereClause .= ' AND ' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
825
-                    }
826
-                    $additionalWhereClause .= '))';
827
-                }
828
-                $statementParts['additionalWhereClause'][$tableNameOrAlias][] = '(' . $additionalWhereClause . ')';
829
-            }
830
-        }
831
-    }
832
-
833
-    /**
834
-     * Transforms orderings into SQL.
835
-     *
836
-     * @param array $orderings An array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
837
-     * @param SourceInterface $source The source
838
-     * @param array &$statementParts The query parts
839
-     * @return void
840
-     * @throws Exception\UnsupportedOrderException
841
-     */
842
-    protected function parseOrderings(array $orderings, SourceInterface $source, array &$statementParts)
843
-    {
844
-        foreach ($orderings as $fieldNameAndPath => $order) {
845
-            switch ($order) {
846
-                case QueryInterface::ORDER_ASCENDING:
847
-                    $order = 'ASC';
848
-                    break;
849
-                case QueryInterface::ORDER_DESCENDING:
850
-                    $order = 'DESC';
851
-                    break;
852
-                default:
853
-                    throw new UnsupportedOrderException('Unsupported order encountered.', 1456845126);
854
-            }
855
-
856
-            $tableName = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->query->getType());
857
-            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $tableName);
858
-            $statementParts['orderings'][] = sprintf('%s.%s %s', $tableName, $fieldName, $order);
859
-        }
860
-    }
861
-
862
-    /**
863
-     * Transforms limit and offset into SQL
864
-     *
865
-     * @param int $limit
866
-     * @param int $offset
867
-     * @param array &$statementParts
868
-     * @return void
869
-     */
870
-    protected function parseLimitAndOffset($limit, $offset, array &$statementParts)
871
-    {
872
-        if ($limit !== null && $offset !== null) {
873
-            $statementParts['limit'] = intval($offset) . ', ' . intval($limit);
874
-        } elseif ($limit !== null) {
875
-            $statementParts['limit'] = intval($limit);
876
-        }
877
-    }
878
-
879
-    /**
880
-     * @param array $rows
881
-     * @return array
882
-     */
883
-    protected function getContentObjects(array $rows): array
884
-    {
885
-        $contentObjects = [];
886
-        foreach ($rows as $row) {
887
-
888
-            // Get language uid from querySettings.
889
-            // Ensure the backend handling is not broken (fallback to Get parameter 'L' if needed)
890
-            $overlaidRow = $this->doLanguageAndWorkspaceOverlay(
891
-                $row,
892
-                $this->query->getTypo3QuerySettings()
893
-            );
894
-
895
-            $contentObjects[] = GeneralUtility::makeInstance(
896
-                Content::class,
897
-                $this->query->getType(),
898
-                $overlaidRow
899
-            );
900
-        }
901
-
902
-        return $contentObjects;
903
-    }
904
-
905
-    /**
906
-     * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
907
-     * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
908
-     *
909
-     * @param array $row
910
-     * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
911
-     * @return array
912
-     */
913
-    protected function doLanguageAndWorkspaceOverlay(array $row, $querySettings)
914
-    {
915
-        $tableName = $this->getTableName();
916
-
917
-        $pageRepository = $this->getPageRepository();
918
-        if (is_object($GLOBALS['TSFE'])) {
919
-            $languageMode = $GLOBALS['TSFE']->sys_language_mode;
920
-            #if ($this->isBackendUserLogged() && $this->getBackendUser()->workspace !== 0) {
921
-            #    $pageRepository->versioningWorkspaceId = $this->getBackendUser()->workspace;
922
-            #}
923
-        } else {
924
-            $languageMode = '';
925
-            $workspaceUid = $this->getBackendUser()->workspace;
926
-            #$pageRepository->versioningWorkspaceId = $workspaceUid;
927
-            #if ($this->getBackendUser()->workspace !== 0) {
928
-            #    $pageRepository->versioningPreview = 1;
929
-            #}
930
-        }
931
-
932
-        // If current row is a translation select its parent
933
-        if (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
934
-            && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
935
-        ) {
936
-            if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
937
-                && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
938
-            ) {
939
-                $queryBuilder = $this->getQueryBuilder();
940
-                $row = $queryBuilder
941
-                    ->select($tableName . '.*')
942
-                    ->from($tableName)
943
-                    ->andWhere(
944
-                        $tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
945
-                        $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = 0'
946
-                    )
947
-                    ->execute()
948
-                    ->fetch();
949
-            }
950
-        }
951
-
952
-        // Retrieve the original uid; Used for Workspaces!
953
-        if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()) {
954
-            $pageRepository->versionOL($tableName, $row, true, true);
955
-        } else {
956
-            \TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($tableName, $row);
957
-        }
958
-        if ($pageRepository->versioningPreview && isset($row['_ORIG_uid'])) {
959
-            $row['uid'] = $row['_ORIG_uid'];
960
-        }
961
-
962
-        // Special case for table "pages"
963
-        if ($tableName == 'pages') {
964
-            $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
965
-        } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
966
-            && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
967
-        ) {
968
-            if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], array(-1, 0))) {
969
-                $overlayMode = $languageMode === 'strict' ? 'hideNonTranslated' : '';
970
-                $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
971
-            }
972
-        }
973
-
974
-        return $row;
975
-    }
976
-
977
-    /**
978
-     * Return a resolved table name given a possible table name alias.
979
-     *
980
-     * @param string $tableNameOrAlias
981
-     * @return string
982
-     */
983
-    protected function resolveTableNameAlias($tableNameOrAlias)
984
-    {
985
-        $resolvedTableName = $tableNameOrAlias;
986
-        if (!empty($this->tableNameAliases['aliases'][$tableNameOrAlias])) {
987
-            $resolvedTableName = $this->tableNameAliases['aliases'][$tableNameOrAlias];
988
-        }
989
-        return $resolvedTableName;
990
-    }
991
-
992
-    /**
993
-     * Generate a unique table name alias for the given table name.
994
-     *
995
-     * @param string $tableName
996
-     * @return string
997
-     */
998
-    protected function generateAlias($tableName)
999
-    {
1000
-
1001
-        if (!isset($this->tableNameAliases['aliasIncrement'][$tableName])) {
1002
-            $this->tableNameAliases['aliasIncrement'][$tableName] = 0;
1003
-        }
1004
-
1005
-        $numberOfAliases = $this->tableNameAliases['aliasIncrement'][$tableName];
1006
-        $tableNameAlias = $tableName . $numberOfAliases;
1007
-
1008
-        $this->tableNameAliases['aliasIncrement'][$tableName]++;
1009
-        $this->tableNameAliases['aliases'][$tableNameAlias] = $tableName;
1010
-
1011
-        return $tableNameAlias;
1012
-    }
1013
-
1014
-    /**
1015
-     * Replace the table names by its table name alias within the given statement.
1016
-     *
1017
-     * @param string $tableName
1018
-     * @param string $tableNameAlias
1019
-     * @param string $statement
1020
-     * @return string
1021
-     */
1022
-    protected function replaceTableNameByAlias($tableName, $tableNameAlias, $statement)
1023
-    {
1024
-        if ($statement && $tableName !== $tableNameAlias) {
1025
-            $statement = str_replace($tableName, $tableNameAlias, $statement);
1026
-        }
1027
-        return $statement;
1028
-    }
1029
-
1030
-    /**
1031
-     * Returns an instance of the current Backend User.
1032
-     *
1033
-     * @return BackendUserAuthentication
1034
-     */
1035
-    protected function getBackendUser()
1036
-    {
1037
-        return $GLOBALS['BE_USER'];
1038
-    }
1039
-
1040
-    /**
1041
-     * Tell whether a Backend User is logged in.
1042
-     *
1043
-     * @return bool
1044
-     */
1045
-    protected function isBackendUserLogged()
1046
-    {
1047
-        return is_object($GLOBALS['BE_USER']);
1048
-    }
1049
-
1050
-    /**
1051
-     * @return PageRepository|object
1052
-     */
1053
-    protected function getPageRepository()
1054
-    {
1055
-        if (!$this->pageRepository instanceof PageRepository) {
1056
-            if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() && is_object($GLOBALS['TSFE'])) {
1057
-                $this->pageRepository = $GLOBALS['TSFE']->sys_page;
1058
-            } else {
1059
-                $this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
1060
-            }
1061
-        }
1062
-
1063
-        return $this->pageRepository;
1064
-    }
1065
-
1066
-    /**
1067
-     * @return FieldPathResolver|object
1068
-     */
1069
-    protected function getFieldPathResolver()
1070
-    {
1071
-        return GeneralUtility::makeInstance(FieldPathResolver::class);
1072
-    }
1073
-
1074
-    /**
1075
-     * @return object|Connection
1076
-     */
1077
-    protected function getConnection(): Connection
1078
-    {
1079
-        /** @var ConnectionPool $connectionPool */
1080
-        return GeneralUtility::makeInstance(ConnectionPool::class)
1081
-            ->getConnectionForTable($this->getTableName());
1082
-    }
1083
-
1084
-    /**
1085
-     * @return object|QueryBuilder
1086
-     */
1087
-    protected function getQueryBuilder(): QueryBuilder
1088
-    {
1089
-        /** @var ConnectionPool $connectionPool */
1090
-        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1091
-        return $connectionPool->getQueryBuilderForTable($this->getTableName());
1092
-    }
1093
-
1094
-    /**
1095
-     * @return string
1096
-     */
1097
-    public function getTableName(): string
1098
-    {
1099
-        return $this->query->getSource()->getNodeTypeName(); // getSelectorName()
1100
-    }
54
+	const OPERATOR_EQUAL_TO_NULL = 'operatorEqualToNull';
55
+	const OPERATOR_NOT_EQUAL_TO_NULL = 'operatorNotEqualToNull';
56
+
57
+	/**
58
+	 * The TYPO3 page repository. Used for language and workspace overlay
59
+	 *
60
+	 * @var PageRepository
61
+	 */
62
+	protected $pageRepository;
63
+
64
+	/**
65
+	 * @var Query
66
+	 */
67
+	protected $query;
68
+
69
+	/**
70
+	 * Store some info related to table name and its aliases.
71
+	 *
72
+	 * @var array
73
+	 */
74
+	protected $tableNameAliases = array(
75
+		'aliases' => [],
76
+		'aliasIncrement' => [],
77
+	);
78
+
79
+	/**
80
+	 * Use to store the current foreign table name alias.
81
+	 *
82
+	 * @var string
83
+	 */
84
+	protected $currentChildTableNameAlias = '';
85
+
86
+	/**
87
+	 * @param Query $query
88
+	 */
89
+	public function __construct(Query $query)
90
+	{
91
+		$this->query = $query;
92
+	}
93
+
94
+	/**
95
+	 * @param $parameters
96
+	 * @return array
97
+	 */
98
+	protected static function getTypes($parameters)
99
+	{
100
+		$types = [];
101
+		foreach ($parameters as $parameter) {
102
+			if (is_array($parameter)) {
103
+				if (MathUtility::canBeInterpretedAsInteger($parameter[0])) {
104
+					$types[] = \Doctrine\DBAL\Connection::PARAM_INT_ARRAY;
105
+				} else {
106
+					$types[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
107
+				}
108
+			} else {
109
+				if (MathUtility::canBeInterpretedAsInteger($parameter)) {
110
+					$types[] = ParameterType::INTEGER;
111
+				} else {
112
+					$types[] = ParameterType::STRING;
113
+				}
114
+			}
115
+		}
116
+		return $types;
117
+	}
118
+
119
+	/**
120
+	 * Returns the result of the query
121
+	 */
122
+	public function fetchResult()
123
+	{
124
+		$parameters = [];
125
+		$statementParts = $this->parseQuery($parameters);
126
+		$statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
127
+		$sql = $this->buildQuery($statementParts);
128
+		//print $sql; exit();
129
+
130
+		$rows = $this->getConnection()
131
+			->executeQuery($sql, $parameters, self::getTypes($parameters))
132
+			->fetchAll();
133
+
134
+		return $this->getContentObjects($rows);
135
+	}
136
+
137
+	/**
138
+	 * Returns the number of tuples matching the query.
139
+	 *
140
+	 * @return int The number of matching tuples
141
+	 */
142
+	public function countResult()
143
+	{
144
+		$parameters = [];
145
+		$statementParts = $this->parseQuery($parameters);
146
+		$statementParts = $this->processStatementStructureForRecursiveMMRelation($statementParts);
147
+		$types = self::getTypes($parameters);
148
+
149
+		// if limit is set, we need to count the rows "manually" as COUNT(*) ignores LIMIT constraints
150
+		if (!empty($statementParts['limit'])) {
151
+			$sql = $this->buildQuery($statementParts);
152
+
153
+			$count = $this
154
+				->getConnection()
155
+				->executeQuery($sql, $parameters, $types)
156
+				->rowCount();
157
+		} else {
158
+			$statementParts['fields'] = array('COUNT(*)');
159
+			// having orderings without grouping is not compatible with non-MySQL DBMS
160
+			$statementParts['orderings'] = [];
161
+			if (isset($statementParts['keywords']['distinct'])) {
162
+				unset($statementParts['keywords']['distinct']);
163
+				$distinctField = $this->query->getDistinct() ? $this->query->getDistinct() : 'uid';
164
+				$statementParts['fields'] = array('COUNT(DISTINCT ' . $statementParts['mainTable'] . '.' . $distinctField . ')');
165
+			}
166
+
167
+			$sql = $this->buildQuery($statementParts);
168
+			$count = $this
169
+				->getConnection()
170
+				->executeQuery($sql, $parameters, $types)
171
+				->fetchColumn(0);
172
+		}
173
+		return (int)$count;
174
+	}
175
+
176
+	/**
177
+	 * Parses the query and returns the SQL statement parts.
178
+	 *
179
+	 * @param array &$parameters
180
+	 * @return array
181
+	 */
182
+	public function parseQuery(array &$parameters)
183
+	{
184
+		$statementParts = [];
185
+		$statementParts['keywords'] = [];
186
+		$statementParts['tables'] = [];
187
+		$statementParts['unions'] = [];
188
+		$statementParts['fields'] = [];
189
+		$statementParts['where'] = [];
190
+		$statementParts['additionalWhereClause'] = [];
191
+		$statementParts['orderings'] = [];
192
+		$statementParts['limit'] = [];
193
+		$query = $this->query;
194
+		$source = $query->getSource();
195
+		$this->parseSource($source, $statementParts);
196
+		$this->parseConstraint($query->getConstraint(), $source, $statementParts, $parameters);
197
+		$this->parseOrderings($query->getOrderings(), $source, $statementParts);
198
+		$this->parseLimitAndOffset($query->getLimit(), $query->getOffset(), $statementParts);
199
+		$tableNames = array_unique(array_keys($statementParts['tables'] + $statementParts['unions']));
200
+		foreach ($tableNames as $tableNameOrAlias) {
201
+			if (is_string($tableNameOrAlias) && strlen($tableNameOrAlias) > 0) {
202
+				$this->addAdditionalWhereClause($query->getTypo3QuerySettings(), $tableNameOrAlias, $statementParts);
203
+			}
204
+		}
205
+
206
+		return $statementParts;
207
+	}
208
+
209
+	/**
210
+	 * Fiddle with the statement structure to handle recursive MM relations.
211
+	 * For the recursive MM query to work, we must invert some values.
212
+	 * Let see if that is the best way of doing that...
213
+	 *
214
+	 * @param array $statementParts
215
+	 * @return array
216
+	 */
217
+	public function processStatementStructureForRecursiveMMRelation(array $statementParts)
218
+	{
219
+
220
+		if ($this->hasRecursiveMMRelation()) {
221
+			$tableName = $this->query->getType();
222
+
223
+			// In order the MM query to work for a recursive MM query, we must invert some values.
224
+			// tx_domain_model_foo0 (the alias) <--> tx_domain_model_foo (the origin table name)
225
+			$values = [];
226
+			foreach ($statementParts['fields'] as $key => $value) {
227
+				$values[$key] = str_replace($tableName, $tableName . '0', $value);
228
+			}
229
+			$statementParts['fields'] = $values;
230
+
231
+			// Same comment as above.
232
+			$values = [];
233
+			foreach ($statementParts['where'] as $key => $value) {
234
+				$values[$key] = str_replace($tableName . '0', $tableName, $value);
235
+			}
236
+			$statementParts['where'] = $values;
237
+
238
+			// We must be more restrictive by transforming the "left" union by "inner"
239
+			$values = [];
240
+			foreach ($statementParts['unions'] as $key => $value) {
241
+				$values[$key] = str_replace('LEFT JOIN', 'INNER JOIN', $value);
242
+			}
243
+			$statementParts['unions'] = $values;
244
+		}
245
+
246
+		return $statementParts;
247
+	}
248
+
249
+	/**
250
+	 * Tell whether there is a recursive MM relation.
251
+	 *
252
+	 * @return bool
253
+	 */
254
+	public function hasRecursiveMMRelation()
255
+	{
256
+		return isset($this->tableNameAliases['aliasIncrement'][$this->query->getType()]);
257
+
258
+	}
259
+
260
+	/**
261
+	 * Returns the statement, ready to be executed.
262
+	 *
263
+	 * @param array $statementParts The SQL statement parts
264
+	 * @return string The SQL statement
265
+	 */
266
+	public function buildQuery(array $statementParts)
267
+	{
268
+
269
+		// Add more statement to the UNION part.
270
+		if (!empty($statementParts['unions'])) {
271
+			foreach ($statementParts['unions'] as $tableName => $unionPart) {
272
+				if (!empty($statementParts['additionalWhereClause'][$tableName])) {
273
+					$statementParts['unions'][$tableName] .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$tableName]);
274
+				}
275
+			}
276
+		}
277
+
278
+		$statement = 'SELECT ' . implode(' ', $statementParts['keywords']) . ' ' . implode(',', $statementParts['fields']) . ' FROM ' . implode(' ', $statementParts['tables']) . ' ' . implode(' ', $statementParts['unions']);
279
+		if (!empty($statementParts['where'])) {
280
+			$statement .= ' WHERE ' . implode('', $statementParts['where']);
281
+			if (!empty($statementParts['additionalWhereClause'][$this->query->getType()])) {
282
+				$statement .= ' AND ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
283
+			}
284
+		} elseif (!empty($statementParts['additionalWhereClause'])) {
285
+			$statement .= ' WHERE ' . implode(' AND ', $statementParts['additionalWhereClause'][$this->query->getType()]);
286
+		}
287
+		if (!empty($statementParts['orderings'])) {
288
+			$statement .= ' ORDER BY ' . implode(', ', $statementParts['orderings']);
289
+		}
290
+		if (!empty($statementParts['limit'])) {
291
+			$statement .= ' LIMIT ' . $statementParts['limit'];
292
+		}
293
+
294
+		return $statement;
295
+	}
296
+
297
+	/**
298
+	 * Transforms a Query Source into SQL and parameter arrays
299
+	 *
300
+	 * @param SourceInterface $source The source
301
+	 * @param array &$sql
302
+	 * @return void
303
+	 */
304
+	protected function parseSource(SourceInterface $source, array &$sql)
305
+	{
306
+		$tableName = $this->getTableName();
307
+		$sql['fields'][$tableName] = $tableName . '.*';
308
+		if ($this->query->getDistinct()) {
309
+			$sql['fields'][$tableName] = $tableName . '.' . $this->query->getDistinct();
310
+			$sql['keywords']['distinct'] = 'DISTINCT';
311
+		}
312
+		$sql['tables'][$tableName] = $tableName;
313
+		$sql['mainTable'] = $tableName;
314
+	}
315
+
316
+	/**
317
+	 * Transforms a constraint into SQL and parameter arrays
318
+	 *
319
+	 * @param ConstraintInterface $constraint The constraint
320
+	 * @param SourceInterface $source The source
321
+	 * @param array &$statementParts The query parts
322
+	 * @param array &$parameters The parameters that will replace the markers
323
+	 * @return void
324
+	 */
325
+	protected function parseConstraint(ConstraintInterface $constraint = null, SourceInterface $source, array &$statementParts, array &$parameters)
326
+	{
327
+		if ($constraint instanceof AndInterface) {
328
+			$statementParts['where'][] = '(';
329
+			$this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
330
+			$statementParts['where'][] = ' AND ';
331
+			$this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
332
+			$statementParts['where'][] = ')';
333
+		} elseif ($constraint instanceof OrInterface) {
334
+			$statementParts['where'][] = '(';
335
+			$this->parseConstraint($constraint->getConstraint1(), $source, $statementParts, $parameters);
336
+			$statementParts['where'][] = ' OR ';
337
+			$this->parseConstraint($constraint->getConstraint2(), $source, $statementParts, $parameters);
338
+			$statementParts['where'][] = ')';
339
+		} elseif ($constraint instanceof NotInterface) {
340
+			$statementParts['where'][] = 'NOT (';
341
+			$this->parseConstraint($constraint->getConstraint(), $source, $statementParts, $parameters);
342
+			$statementParts['where'][] = ')';
343
+		} elseif ($constraint instanceof ComparisonInterface) {
344
+			$this->parseComparison($constraint, $source, $statementParts, $parameters);
345
+		}
346
+	}
347
+
348
+	/**
349
+	 * Parse a Comparison into SQL and parameter arrays.
350
+	 *
351
+	 * @param ComparisonInterface $comparison The comparison to parse
352
+	 * @param SourceInterface $source The source
353
+	 * @param array &$statementParts SQL query parts to add to
354
+	 * @param array &$parameters Parameters to bind to the SQL
355
+	 * @return void
356
+	 * @throws Exception\RepositoryException
357
+	 */
358
+	protected function parseComparison(ComparisonInterface $comparison, SourceInterface $source, array &$statementParts, array &$parameters)
359
+	{
360
+		$operand1 = $comparison->getOperand1();
361
+		$operator = $comparison->getOperator();
362
+		$operand2 = $comparison->getOperand2();
363
+		if ($operator === QueryInterface::OPERATOR_IN) {
364
+			$items = [];
365
+			$hasValue = false;
366
+			foreach ($operand2 as $value) {
367
+				$value = $this->getPlainValue($value);
368
+				if ($value !== null) {
369
+					$items[] = $value;
370
+					$hasValue = true;
371
+				}
372
+			}
373
+			if ($hasValue === false) {
374
+				$statementParts['where'][] = '1<>1';
375
+			} else {
376
+				$this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters, null);
377
+				$parameters[] = $items;
378
+			}
379
+		} elseif ($operator === QueryInterface::OPERATOR_CONTAINS) {
380
+			if ($operand2 === null) {
381
+				$statementParts['where'][] = '1<>1';
382
+			} else {
383
+				throw new \Exception('Not implemented! Contact extension author.', 1412931227);
384
+				# @todo re-implement me if necessary.
385
+				#$tableName = $this->query->getType();
386
+				#$propertyName = $operand1->getPropertyName();
387
+				#while (strpos($propertyName, '.') !== false) {
388
+				#	$this->addUnionStatement($tableName, $propertyName, $statementParts);
389
+				#}
390
+				#$columnName = $propertyName;
391
+				#$columnMap = $propertyName;
392
+				#$typeOfRelation = $columnMap instanceof ColumnMap ? $columnMap->getTypeOfRelation() : null;
393
+				#if ($typeOfRelation === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
394
+				#	$relationTableName = $columnMap->getRelationTableName();
395
+				#	$statementParts['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=?)';
396
+				#	$parameters[] = intval($this->getPlainValue($operand2));
397
+				#} elseif ($typeOfRelation === ColumnMap::RELATION_HAS_MANY) {
398
+				#	$parentKeyFieldName = $columnMap->getParentKeyFieldName();
399
+				#	if (isset($parentKeyFieldName)) {
400
+				#		$childTableName = $columnMap->getChildTableName();
401
+				#		$statementParts['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=?)';
402
+				#		$parameters[] = intval($this->getPlainValue($operand2));
403
+				#	} else {
404
+				#		$statementParts['where'][] = 'FIND_IN_SET(?,' . $tableName . '.' . $columnName . ')';
405
+				#		$parameters[] = intval($this->getPlainValue($operand2));
406
+				#	}
407
+				#} else {
408
+				#	throw new Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
409
+				#}
410
+			}
411
+		} else {
412
+			if ($operand2 === null) {
413
+				if ($operator === QueryInterface::OPERATOR_EQUAL_TO) {
414
+					$operator = self::OPERATOR_EQUAL_TO_NULL;
415
+				} elseif ($operator === QueryInterface::OPERATOR_NOT_EQUAL_TO) {
416
+					$operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
417
+				}
418
+			}
419
+			$this->parseDynamicOperand($operand1, $operator, $source, $statementParts, $parameters);
420
+			$parameters[] = $this->getPlainValue($operand2);
421
+		}
422
+	}
423
+
424
+	/**
425
+	 * Returns a plain value, i.e. objects are flattened if possible.
426
+	 *
427
+	 * @param mixed $input
428
+	 * @return mixed
429
+	 * @throws UnexpectedTypeException
430
+	 */
431
+	protected function getPlainValue($input)
432
+	{
433
+		if (is_array($input)) {
434
+			throw new UnexpectedTypeException('An array could not be converted to a plain value.', 1274799932);
435
+		}
436
+		if ($input instanceof \DateTime) {
437
+			return $input->format('U');
438
+		} elseif (is_object($input)) {
439
+			if ($input instanceof LazyLoadingProxy) {
440
+				$realInput = $input->_loadRealInstance();
441
+			} else {
442
+				$realInput = $input;
443
+			}
444
+			if ($realInput instanceof DomainObjectInterface) {
445
+				return $realInput->getUid();
446
+			} else {
447
+				throw new UnexpectedTypeException('An object of class "' . get_class($realInput) . '" could not be converted to a plain value.', 1274799934);
448
+			}
449
+		} elseif (is_bool($input)) {
450
+			return $input === true ? 1 : 0;
451
+		} else {
452
+			return $input;
453
+		}
454
+	}
455
+
456
+	/**
457
+	 * Parse a DynamicOperand into SQL and parameter arrays.
458
+	 *
459
+	 * @param DynamicOperandInterface $operand
460
+	 * @param string $operator One of the JCR_OPERATOR_* constants
461
+	 * @param SourceInterface $source The source
462
+	 * @param array &$statementParts The query parts
463
+	 * @param array &$parameters The parameters that will replace the markers
464
+	 * @param string $valueFunction an optional SQL function to apply to the operand value
465
+	 * @return void
466
+	 */
467
+	protected function parseDynamicOperand(DynamicOperandInterface $operand, $operator, SourceInterface $source, array &$statementParts, array &$parameters, $valueFunction = null)
468
+	{
469
+		if ($operand instanceof LowerCaseInterface) {
470
+			$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'LOWER');
471
+		} elseif ($operand instanceof UpperCaseInterface) {
472
+			$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $statementParts, $parameters, 'UPPER');
473
+		} elseif ($operand instanceof PropertyValueInterface) {
474
+			$propertyName = $operand->getPropertyName();
475
+
476
+			// Reset value.
477
+			$this->currentChildTableNameAlias = '';
478
+
479
+			if ($source instanceof SelectorInterface) {
480
+				$tableName = $this->query->getType();
481
+				while (strpos($propertyName, '.') !== false) {
482
+					$this->addUnionStatement($tableName, $propertyName, $statementParts);
483
+				}
484
+			} elseif ($source instanceof JoinInterface) {
485
+				$tableName = $source->getJoinCondition()->getSelector1Name();
486
+			}
487
+
488
+			$columnName = $propertyName;
489
+			$resolvedOperator = $this->resolveOperator($operator);
490
+			$constraintSQL = '';
491
+
492
+			$marker = $operator === QueryInterface::OPERATOR_IN
493
+				? '(?)'
494
+				: '?';
495
+
496
+			if ($valueFunction === null) {
497
+				$constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $resolvedOperator . ' ' . $marker;
498
+			} else {
499
+				$constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $resolvedOperator . ' ' . $marker;
500
+			}
501
+
502
+			if (isset($tableName) && !empty($this->currentChildTableNameAlias)) {
503
+				$constraintSQL = $this->replaceTableNameByAlias($tableName, $this->currentChildTableNameAlias, $constraintSQL);
504
+			}
505
+			$statementParts['where'][] = $constraintSQL;
506
+		}
507
+	}
508
+
509
+	/**
510
+	 * @param string &$tableName
511
+	 * @param string &$propertyPath
512
+	 * @param array &$statementParts
513
+	 */
514
+	protected function addUnionStatement(&$tableName, &$propertyPath, array &$statementParts)
515
+	{
516
+
517
+		$table = Tca::table($tableName);
518
+
519
+		$explodedPropertyPath = explode('.', $propertyPath, 2);
520
+		$fieldName = $explodedPropertyPath[0];
521
+
522
+		// Field of type "group" are special because property path must contain the table name
523
+		// to determine the relation type. Example for sys_category, property path will look like "items.sys_file"
524
+		$parts = explode('.', $propertyPath, 3);
525
+		if ($table->field($fieldName)->isGroup() && count($parts) > 2) {
526
+			$explodedPropertyPath[0] = $parts[0] . '.' . $parts[1];
527
+			$explodedPropertyPath[1] = $parts[2];
528
+			$fieldName = $explodedPropertyPath[0];
529
+		}
530
+
531
+		$parentKeyFieldName = $table->field($fieldName)->getForeignField();
532
+		$childTableName = $table->field($fieldName)->getForeignTable();
533
+
534
+		if ($childTableName === null) {
535
+			throw new InvalidRelationConfigurationException('The relation information for property "' . $fieldName . '" of class "' . $tableName . '" is missing.', 1353170925);
536
+		}
537
+
538
+		if ($table->field($fieldName)->hasOne()) { // includes relation "one-to-one" and "many-to-one"
539
+			// sometimes the opposite relation is not defined. We don't want to force this config for backward compatibility reasons.
540
+			// $parentKeyFieldName === null does the trick somehow. Before condition was if (isset($parentKeyFieldName))
541
+			if ($table->field($fieldName)->hasRelationManyToOne() || $parentKeyFieldName === null) {
542
+				$statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.' . $fieldName . '=' . $childTableName . '.uid';
543
+			} else {
544
+				$statementParts['unions'][$childTableName] = 'LEFT JOIN ' . $childTableName . ' ON ' . $tableName . '.uid=' . $childTableName . '.' . $parentKeyFieldName;
545
+			}
546
+		} elseif ($table->field($fieldName)->hasRelationManyToMany()) {
547
+			$relationTableName = $table->field($fieldName)->getManyToManyTable();
548
+
549
+			$parentKeyFieldName = $table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
550
+			$childKeyFieldName = !$table->field($fieldName)->isOppositeRelation() ? 'uid_foreign' : 'uid_local';
551
+
552
+			// MM table e.g sys_category_record_mm
553
+			$relationTableNameAlias = $this->generateAlias($relationTableName);
554
+			$join = sprintf(
555
+				'LEFT JOIN %s AS %s ON %s.uid=%s.%s', $relationTableName,
556
+				$relationTableNameAlias,
557
+				$tableName,
558
+				$relationTableNameAlias,
559
+				$parentKeyFieldName
560
+			);
561
+			$statementParts['unions'][$relationTableNameAlias] = $join;
562
+
563
+			// Foreign table e.g sys_category
564
+			$childTableNameAlias = $this->generateAlias($childTableName);
565
+			$this->currentChildTableNameAlias = $childTableNameAlias;
566
+			$join = sprintf(
567
+				'LEFT JOIN %s AS %s ON %s.%s=%s.uid',
568
+				$childTableName,
569
+				$childTableNameAlias,
570
+				$relationTableNameAlias,
571
+				$childKeyFieldName,
572
+				$childTableNameAlias
573
+			);
574
+			$statementParts['unions'][$childTableNameAlias] = $join;
575
+
576
+			// Find a possible table name for a MM condition.
577
+			$tableNameCondition = $table->field($fieldName)->getAdditionalTableNameCondition();
578
+			if ($tableNameCondition) {
579
+
580
+				// If we can find a source file name,  we can then retrieve more MM conditions from the TCA such as a field name.
581
+				$sourceFileName = $this->query->getSourceFieldName();
582
+				if (empty($sourceFileName)) {
583
+					$additionalMMConditions = array(
584
+						'tablenames' => $tableNameCondition,
585
+					);
586
+				} else {
587
+					$additionalMMConditions = Tca::table($tableNameCondition)->field($sourceFileName)->getAdditionalMMCondition();
588
+				}
589
+
590
+				foreach ($additionalMMConditions as $additionalFieldName => $additionalMMCondition) {
591
+					$additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
592
+					$statementParts['unions'][$relationTableNameAlias] .= $additionalJoin;
593
+
594
+					$additionalJoin = sprintf(' AND %s.%s = "%s"', $relationTableNameAlias, $additionalFieldName, $additionalMMCondition);
595
+					$statementParts['unions'][$childTableNameAlias] .= $additionalJoin;
596
+				}
597
+			}
598
+
599
+		} elseif ($table->field($fieldName)->hasMany()) { // includes relations "many-to-one" and "csv" relations
600
+			$childTableNameAlias = $this->generateAlias($childTableName);
601
+			$this->currentChildTableNameAlias = $childTableNameAlias;
602
+
603
+			if (isset($parentKeyFieldName)) {
604
+				$join = sprintf(
605
+					'LEFT JOIN %s AS %s ON %s.uid=%s.%s',
606
+					$childTableName,
607
+					$childTableNameAlias,
608
+					$tableName,
609
+					$childTableNameAlias,
610
+					$parentKeyFieldName
611
+				);
612
+				$statementParts['unions'][$childTableNameAlias] = $join;
613
+			} else {
614
+				$join = sprintf(
615
+					'LEFT JOIN %s AS %s ON (FIND_IN_SET(%s.uid, %s.%s))',
616
+					$childTableName,
617
+					$childTableNameAlias,
618
+					$childTableNameAlias,
619
+					$tableName,
620
+					$fieldName
621
+				);
622
+				$statementParts['unions'][$childTableNameAlias] = $join;
623
+			}
624
+		} else {
625
+			throw new Exception('Could not determine type of relation.', 1252502725);
626
+		}
627
+
628
+		$statementParts['keywords']['distinct'] = 'DISTINCT';
629
+		$propertyPath = $explodedPropertyPath[1];
630
+		$tableName = $childTableName;
631
+	}
632
+
633
+	/**
634
+	 * Returns the SQL operator for the given JCR operator type.
635
+	 *
636
+	 * @param string $operator One of the JCR_OPERATOR_* constants
637
+	 * @return string an SQL operator
638
+	 * @throws Exception
639
+	 */
640
+	protected function resolveOperator($operator)
641
+	{
642
+		switch ($operator) {
643
+			case self::OPERATOR_EQUAL_TO_NULL:
644
+				$operator = 'IS';
645
+				break;
646
+			case self::OPERATOR_NOT_EQUAL_TO_NULL:
647
+				$operator = 'IS NOT';
648
+				break;
649
+			case QueryInterface::OPERATOR_IN:
650
+				$operator = 'IN';
651
+				break;
652
+			case QueryInterface::OPERATOR_EQUAL_TO:
653
+				$operator = '=';
654
+				break;
655
+			case QueryInterface::OPERATOR_NOT_EQUAL_TO:
656
+				$operator = '!=';
657
+				break;
658
+			case QueryInterface::OPERATOR_LESS_THAN:
659
+				$operator = '<';
660
+				break;
661
+			case QueryInterface::OPERATOR_LESS_THAN_OR_EQUAL_TO:
662
+				$operator = '<=';
663
+				break;
664
+			case QueryInterface::OPERATOR_GREATER_THAN:
665
+				$operator = '>';
666
+				break;
667
+			case QueryInterface::OPERATOR_GREATER_THAN_OR_EQUAL_TO:
668
+				$operator = '>=';
669
+				break;
670
+			case QueryInterface::OPERATOR_LIKE:
671
+				$operator = 'LIKE';
672
+				break;
673
+			default:
674
+				throw new Exception('Unsupported operator encountered.', 1242816073);
675
+		}
676
+		return $operator;
677
+	}
678
+
679
+	/**
680
+	 * Adds additional WHERE statements according to the query settings.
681
+	 *
682
+	 * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
683
+	 * @param string $tableNameOrAlias The table name to add the additional where clause for
684
+	 * @param array &$statementParts
685
+	 * @return void
686
+	 */
687
+	protected function addAdditionalWhereClause(QuerySettingsInterface $querySettings, $tableNameOrAlias, &$statementParts)
688
+	{
689
+		$this->addVisibilityConstraintStatement($querySettings, $tableNameOrAlias, $statementParts);
690
+		if ($querySettings->getRespectSysLanguage()) {
691
+			$this->addSysLanguageStatement($tableNameOrAlias, $statementParts, $querySettings);
692
+		}
693
+	}
694
+
695
+	/**
696
+	 * Adds enableFields and deletedClause to the query if necessary
697
+	 *
698
+	 * @param QuerySettingsInterface $querySettings
699
+	 * @param string $tableNameOrAlias The database table name
700
+	 * @param array &$statementParts The query parts
701
+	 * @return void
702
+	 */
703
+	protected function addVisibilityConstraintStatement(QuerySettingsInterface $querySettings, $tableNameOrAlias, array &$statementParts)
704
+	{
705
+		$statement = '';
706
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
707
+		if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
708
+			$ignoreEnableFields = $querySettings->getIgnoreEnableFields();
709
+			$enableFieldsToBeIgnored = $querySettings->getEnableFieldsToBeIgnored();
710
+			$includeDeleted = $querySettings->getIncludeDeleted();
711
+			if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()) {
712
+				$statement .= $this->getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted);
713
+			} else {
714
+				// 'BE' case
715
+				$statement .= $this->getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted);
716
+			}
717
+
718
+			// Remove the prefixing "AND" if any.
719
+			if (!empty($statement)) {
720
+				$statement = strtolower(substr($statement, 1, 3)) === 'and' ? substr($statement, 5) : $statement;
721
+				$statementParts['additionalWhereClause'][$tableNameOrAlias][] = $statement;
722
+			}
723
+		}
724
+	}
725
+
726
+	/**
727
+	 * Returns constraint statement for frontend context
728
+	 *
729
+	 * @param string $tableNameOrAlias
730
+	 * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
731
+	 * @param array $enableFieldsToBeIgnored If $ignoreEnableFields is true, this array specifies enable fields to be ignored. If it is null or an empty array (default) all enable fields are ignored.
732
+	 * @param boolean $includeDeleted A flag indicating whether deleted records should be included
733
+	 * @return string
734
+	 * @throws Exception\InconsistentQuerySettingsException
735
+	 */
736
+	protected function getFrontendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $enableFieldsToBeIgnored, $includeDeleted)
737
+	{
738
+		$statement = '';
739
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
740
+		if ($ignoreEnableFields && !$includeDeleted) {
741
+			if (count($enableFieldsToBeIgnored)) {
742
+				// array_combine() is necessary because of the way \TYPO3\CMS\Frontend\Page\PageRepository::enableFields() is implemented
743
+				$statement .= $this->getPageRepository()->enableFields($tableName, -1, array_combine($enableFieldsToBeIgnored, $enableFieldsToBeIgnored));
744
+			} else {
745
+				$statement .= $this->getPageRepository()->deleteClause($tableName);
746
+			}
747
+		} elseif (!$ignoreEnableFields && !$includeDeleted) {
748
+			$statement .= $this->getPageRepository()->enableFields($tableName);
749
+		} elseif (!$ignoreEnableFields && $includeDeleted) {
750
+			throw new InconsistentQuerySettingsException('Query setting "ignoreEnableFields=false" can not be used together with "includeDeleted=true" in frontend context.', 1327678173);
751
+		}
752
+		return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
753
+	}
754
+
755
+	/**
756
+	 * Returns constraint statement for backend context
757
+	 *
758
+	 * @param string $tableNameOrAlias
759
+	 * @param boolean $ignoreEnableFields A flag indicating whether the enable fields should be ignored
760
+	 * @param boolean $includeDeleted A flag indicating whether deleted records should be included
761
+	 * @return string
762
+	 */
763
+	protected function getBackendConstraintStatement($tableNameOrAlias, $ignoreEnableFields, $includeDeleted)
764
+	{
765
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
766
+		$statement = '';
767
+		if (!$ignoreEnableFields) {
768
+			$statement .= BackendUtility::BEenableFields($tableName);
769
+		}
770
+
771
+		// If the table is found to have "workspace" support, add the corresponding fields in the statement.
772
+		if (Tca::table($tableName)->hasWorkspaceSupport()) {
773
+			if ($this->getBackendUser()->workspace === 0) {
774
+				$statement .= ' AND ' . $tableName . '.t3ver_state<=' . new VersionState(VersionState::DEFAULT_STATE);
775
+			} else {
776
+				// Show only records of live and of the current workspace
777
+				// In case we are in a Versioning preview
778
+				$statement .= ' AND (' .
779
+					$tableName . '.t3ver_wsid=0 OR ' .
780
+					$tableName . '.t3ver_wsid=' . (int)$this->getBackendUser()->workspace .
781
+					')';
782
+			}
783
+
784
+			// Check if this segment make sense here or whether it should be in the "if" part when we have workspace = 0
785
+			$statement .= ' AND ' . $tableName . '.pid<>-1';
786
+		}
787
+
788
+		if (!$includeDeleted) {
789
+			$statement .= BackendUtility::deleteClause($tableName);
790
+		}
791
+
792
+		return $this->replaceTableNameByAlias($tableName, $tableNameOrAlias, $statement);
793
+	}
794
+
795
+	/**
796
+	 * Builds the language field statement
797
+	 *
798
+	 * @param string $tableNameOrAlias The database table name
799
+	 * @param array &$statementParts The query parts
800
+	 * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
801
+	 * @return void
802
+	 * @throws Exception
803
+	 */
804
+	protected function addSysLanguageStatement($tableNameOrAlias, array &$statementParts, $querySettings)
805
+	{
806
+		$tableName = $this->resolveTableNameAlias($tableNameOrAlias);
807
+		if (is_array($GLOBALS['TCA'][$tableName]['ctrl'])) {
808
+			if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])) {
809
+				// Select all entries for the current language
810
+				$additionalWhereClause = $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' IN (' . intval($querySettings->getLanguageUid()) . ',-1)';
811
+				// If any language is set -> get those entries which are not translated yet
812
+				// They will be removed by t3lib_page::getRecordOverlay if not matching overlay mode
813
+				if (isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
814
+					&& $querySettings->getLanguageUid() > 0
815
+				) {
816
+					$additionalWhereClause .= ' OR (' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '=0' .
817
+						' AND ' . $tableNameOrAlias . '.uid NOT IN (SELECT ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] .
818
+						' FROM ' . $tableName .
819
+						' WHERE ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'] . '>0' .
820
+						' AND ' . $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . '>0';
821
+
822
+					// Add delete clause to ensure all entries are loaded
823
+					if (isset($GLOBALS['TCA'][$tableName]['ctrl']['delete'])) {
824
+						$additionalWhereClause .= ' AND ' . $tableNameOrAlias . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['delete'] . '=0';
825
+					}
826
+					$additionalWhereClause .= '))';
827
+				}
828
+				$statementParts['additionalWhereClause'][$tableNameOrAlias][] = '(' . $additionalWhereClause . ')';
829
+			}
830
+		}
831
+	}
832
+
833
+	/**
834
+	 * Transforms orderings into SQL.
835
+	 *
836
+	 * @param array $orderings An array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
837
+	 * @param SourceInterface $source The source
838
+	 * @param array &$statementParts The query parts
839
+	 * @return void
840
+	 * @throws Exception\UnsupportedOrderException
841
+	 */
842
+	protected function parseOrderings(array $orderings, SourceInterface $source, array &$statementParts)
843
+	{
844
+		foreach ($orderings as $fieldNameAndPath => $order) {
845
+			switch ($order) {
846
+				case QueryInterface::ORDER_ASCENDING:
847
+					$order = 'ASC';
848
+					break;
849
+				case QueryInterface::ORDER_DESCENDING:
850
+					$order = 'DESC';
851
+					break;
852
+				default:
853
+					throw new UnsupportedOrderException('Unsupported order encountered.', 1456845126);
854
+			}
855
+
856
+			$tableName = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->query->getType());
857
+			$fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $tableName);
858
+			$statementParts['orderings'][] = sprintf('%s.%s %s', $tableName, $fieldName, $order);
859
+		}
860
+	}
861
+
862
+	/**
863
+	 * Transforms limit and offset into SQL
864
+	 *
865
+	 * @param int $limit
866
+	 * @param int $offset
867
+	 * @param array &$statementParts
868
+	 * @return void
869
+	 */
870
+	protected function parseLimitAndOffset($limit, $offset, array &$statementParts)
871
+	{
872
+		if ($limit !== null && $offset !== null) {
873
+			$statementParts['limit'] = intval($offset) . ', ' . intval($limit);
874
+		} elseif ($limit !== null) {
875
+			$statementParts['limit'] = intval($limit);
876
+		}
877
+	}
878
+
879
+	/**
880
+	 * @param array $rows
881
+	 * @return array
882
+	 */
883
+	protected function getContentObjects(array $rows): array
884
+	{
885
+		$contentObjects = [];
886
+		foreach ($rows as $row) {
887
+
888
+			// Get language uid from querySettings.
889
+			// Ensure the backend handling is not broken (fallback to Get parameter 'L' if needed)
890
+			$overlaidRow = $this->doLanguageAndWorkspaceOverlay(
891
+				$row,
892
+				$this->query->getTypo3QuerySettings()
893
+			);
894
+
895
+			$contentObjects[] = GeneralUtility::makeInstance(
896
+				Content::class,
897
+				$this->query->getType(),
898
+				$overlaidRow
899
+			);
900
+		}
901
+
902
+		return $contentObjects;
903
+	}
904
+
905
+	/**
906
+	 * Performs workspace and language overlay on the given row array. The language and workspace id is automatically
907
+	 * detected (depending on FE or BE context). You can also explicitly set the language/workspace id.
908
+	 *
909
+	 * @param array $row
910
+	 * @param QuerySettingsInterface $querySettings The TYPO3 CMS specific query settings
911
+	 * @return array
912
+	 */
913
+	protected function doLanguageAndWorkspaceOverlay(array $row, $querySettings)
914
+	{
915
+		$tableName = $this->getTableName();
916
+
917
+		$pageRepository = $this->getPageRepository();
918
+		if (is_object($GLOBALS['TSFE'])) {
919
+			$languageMode = $GLOBALS['TSFE']->sys_language_mode;
920
+			#if ($this->isBackendUserLogged() && $this->getBackendUser()->workspace !== 0) {
921
+			#    $pageRepository->versioningWorkspaceId = $this->getBackendUser()->workspace;
922
+			#}
923
+		} else {
924
+			$languageMode = '';
925
+			$workspaceUid = $this->getBackendUser()->workspace;
926
+			#$pageRepository->versioningWorkspaceId = $workspaceUid;
927
+			#if ($this->getBackendUser()->workspace !== 0) {
928
+			#    $pageRepository->versioningPreview = 1;
929
+			#}
930
+		}
931
+
932
+		// If current row is a translation select its parent
933
+		if (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
934
+			&& isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
935
+		) {
936
+			if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
937
+				&& $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
938
+			) {
939
+				$queryBuilder = $this->getQueryBuilder();
940
+				$row = $queryBuilder
941
+					->select($tableName . '.*')
942
+					->from($tableName)
943
+					->andWhere(
944
+						$tableName . '.uid=' . (int)$row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
945
+						$tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] . ' = 0'
946
+					)
947
+					->execute()
948
+					->fetch();
949
+			}
950
+		}
951
+
952
+		// Retrieve the original uid; Used for Workspaces!
953
+		if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()) {
954
+			$pageRepository->versionOL($tableName, $row, true, true);
955
+		} else {
956
+			\TYPO3\CMS\Backend\Utility\BackendUtility::workspaceOL($tableName, $row);
957
+		}
958
+		if ($pageRepository->versioningPreview && isset($row['_ORIG_uid'])) {
959
+			$row['uid'] = $row['_ORIG_uid'];
960
+		}
961
+
962
+		// Special case for table "pages"
963
+		if ($tableName == 'pages') {
964
+			$row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
965
+		} elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
966
+			&& $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
967
+		) {
968
+			if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], array(-1, 0))) {
969
+				$overlayMode = $languageMode === 'strict' ? 'hideNonTranslated' : '';
970
+				$row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
971
+			}
972
+		}
973
+
974
+		return $row;
975
+	}
976
+
977
+	/**
978
+	 * Return a resolved table name given a possible table name alias.
979
+	 *
980
+	 * @param string $tableNameOrAlias
981
+	 * @return string
982
+	 */
983
+	protected function resolveTableNameAlias($tableNameOrAlias)
984
+	{
985
+		$resolvedTableName = $tableNameOrAlias;
986
+		if (!empty($this->tableNameAliases['aliases'][$tableNameOrAlias])) {
987
+			$resolvedTableName = $this->tableNameAliases['aliases'][$tableNameOrAlias];
988
+		}
989
+		return $resolvedTableName;
990
+	}
991
+
992
+	/**
993
+	 * Generate a unique table name alias for the given table name.
994
+	 *
995
+	 * @param string $tableName
996
+	 * @return string
997
+	 */
998
+	protected function generateAlias($tableName)
999
+	{
1000
+
1001
+		if (!isset($this->tableNameAliases['aliasIncrement'][$tableName])) {
1002
+			$this->tableNameAliases['aliasIncrement'][$tableName] = 0;
1003
+		}
1004
+
1005
+		$numberOfAliases = $this->tableNameAliases['aliasIncrement'][$tableName];
1006
+		$tableNameAlias = $tableName . $numberOfAliases;
1007
+
1008
+		$this->tableNameAliases['aliasIncrement'][$tableName]++;
1009
+		$this->tableNameAliases['aliases'][$tableNameAlias] = $tableName;
1010
+
1011
+		return $tableNameAlias;
1012
+	}
1013
+
1014
+	/**
1015
+	 * Replace the table names by its table name alias within the given statement.
1016
+	 *
1017
+	 * @param string $tableName
1018
+	 * @param string $tableNameAlias
1019
+	 * @param string $statement
1020
+	 * @return string
1021
+	 */
1022
+	protected function replaceTableNameByAlias($tableName, $tableNameAlias, $statement)
1023
+	{
1024
+		if ($statement && $tableName !== $tableNameAlias) {
1025
+			$statement = str_replace($tableName, $tableNameAlias, $statement);
1026
+		}
1027
+		return $statement;
1028
+	}
1029
+
1030
+	/**
1031
+	 * Returns an instance of the current Backend User.
1032
+	 *
1033
+	 * @return BackendUserAuthentication
1034
+	 */
1035
+	protected function getBackendUser()
1036
+	{
1037
+		return $GLOBALS['BE_USER'];
1038
+	}
1039
+
1040
+	/**
1041
+	 * Tell whether a Backend User is logged in.
1042
+	 *
1043
+	 * @return bool
1044
+	 */
1045
+	protected function isBackendUserLogged()
1046
+	{
1047
+		return is_object($GLOBALS['BE_USER']);
1048
+	}
1049
+
1050
+	/**
1051
+	 * @return PageRepository|object
1052
+	 */
1053
+	protected function getPageRepository()
1054
+	{
1055
+		if (!$this->pageRepository instanceof PageRepository) {
1056
+			if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() && is_object($GLOBALS['TSFE'])) {
1057
+				$this->pageRepository = $GLOBALS['TSFE']->sys_page;
1058
+			} else {
1059
+				$this->pageRepository = GeneralUtility::makeInstance(PageRepository::class);
1060
+			}
1061
+		}
1062
+
1063
+		return $this->pageRepository;
1064
+	}
1065
+
1066
+	/**
1067
+	 * @return FieldPathResolver|object
1068
+	 */
1069
+	protected function getFieldPathResolver()
1070
+	{
1071
+		return GeneralUtility::makeInstance(FieldPathResolver::class);
1072
+	}
1073
+
1074
+	/**
1075
+	 * @return object|Connection
1076
+	 */
1077
+	protected function getConnection(): Connection
1078
+	{
1079
+		/** @var ConnectionPool $connectionPool */
1080
+		return GeneralUtility::makeInstance(ConnectionPool::class)
1081
+			->getConnectionForTable($this->getTableName());
1082
+	}
1083
+
1084
+	/**
1085
+	 * @return object|QueryBuilder
1086
+	 */
1087
+	protected function getQueryBuilder(): QueryBuilder
1088
+	{
1089
+		/** @var ConnectionPool $connectionPool */
1090
+		$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
1091
+		return $connectionPool->getQueryBuilderForTable($this->getTableName());
1092
+	}
1093
+
1094
+	/**
1095
+	 * @return string
1096
+	 */
1097
+	public function getTableName(): string
1098
+	{
1099
+		return $this->query->getSource()->getNodeTypeName(); // getSelectorName()
1100
+	}
1101 1101
 
1102 1102
 }
Please login to merge, or discard this patch.