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