Completed
Push — master ( 7f2d14...7fc8f2 )
by Fabien
02:11
created

GridService::getFieldNameByPosition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
namespace Fab\Vidi\Tca;
3
4
/*
5
 * This file is part of the Fab/Vidi project under GPLv2 or later.
6
 *
7
 * For the full copyright and license information, please read the
8
 * LICENSE.md file that was distributed with this source code.
9
 */
10
11
use Fab\Vidi\Grid\ColumnRendererInterface;
12
use Fab\Vidi\Module\ConfigurablePart;
13
use Fab\Vidi\Module\ModulePreferences;
14
use TYPO3\CMS\Core\Utility\GeneralUtility;
15
use Fab\Vidi\Exception\InvalidKeyInArrayException;
16
use Fab\Vidi\Facet\StandardFacet;
17
use Fab\Vidi\Facet\FacetInterface;
18
19
/**
20
 * A class to handle TCA grid configuration
21
 */
22
class GridService extends AbstractTca
23
{
24
25
    /**
26
     * @var array
27
     */
28
    protected $tca;
29
30
    /**
31
     * @var string
32
     */
33
    protected $tableName;
34
35
    /**
36
     * All fields available in the Grid.
37
     *
38
     * @var array
39
     */
40
    protected $fields;
41
42
    /**
43
     * All fields regardless whether they have been excluded or not.
44
     *
45
     * @var array
46
     */
47
    protected $allFields;
48
49
    /**
50
     * @var array
51
     */
52
    protected $instances;
53
54
    /**
55
     * @var array
56
     */
57
    protected $facets;
58
59
    /**
60
     * __construct
61
     *
62
     * @throws InvalidKeyInArrayException
63
     * @param string $tableName
64
     */
65
    public function __construct($tableName)
66
    {
67
68
        $this->tableName = $tableName;
69
70 View Code Duplication
        if (empty($GLOBALS['TCA'][$this->tableName])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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()
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()
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
     * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
105
     */
106
    public function getLabelKey($fieldNameAndPath)
107
    {
108
109
        $field = $this->getField($fieldNameAndPath);
110
111
        // First option is to get the label from the Grid TCA.
112
        $rawLabel = '';
113
        if (isset($field['label'])) {
114
            $rawLabel = $field['label'];
115
        }
116
117
        // Second option is to fetch the label from the Column Renderer object.
118 View Code Duplication
        if (!$rawLabel && $this->hasRenderers($fieldNameAndPath)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
119
            $renderers = $this->getRenderers($fieldNameAndPath);
120
            /** @var $renderer ColumnRendererInterface */
121
            foreach ($renderers as $renderer) {
122
                if (isset($renderer['label'])) {
123
                    $rawLabel = $renderer['label'];
124
                    break;
125
                }
126
            }
127
        }
128
        return $rawLabel;
129
    }
130
131
    /**
132
     * Get the translation of a label given a column name.
133
     *
134
     * @param string $fieldNameAndPath
135
     * @return string
136
     */
137
    public function getLabel($fieldNameAndPath)
138
    {
139
        $label = '';
140
        if ($this->hasLabel($fieldNameAndPath)) {
141
            $labelKey = $this->getLabelKey($fieldNameAndPath);
142
            try {
143
                $label = $this->getLanguageService()->sL($labelKey);
144
            } catch (\InvalidArgumentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
145
            }
146
            if (empty($label)) {
147
                $label = $labelKey;
148
            }
149
        } else {
150
151
            // Important to notice the label can contains a path, e.g. metadata.categories and must be resolved.
152
            $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->tableName);
153
            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->tableName);
154
            $table = Tca::table($dataType);
155
156
            if ($table->hasField($fieldName) && $table->field($fieldName)->hasLabel()) {
157
                $label = $table->field($fieldName)->getLabel();
158
            }
159
        }
160
161
        return $label;
162
    }
163
164
    /**
165
     * Returns the field name given its position.
166
     *
167
     * @param string $position the position of the field in the grid
168
     * @throws InvalidKeyInArrayException
169
     * @return int
170
     */
171
    public function getFieldNameByPosition($position)
172
    {
173
        $fields = array_keys($this->getFields());
174
        if (empty($fields[$position])) {
175
            throw new InvalidKeyInArrayException('No field exist for position: ' . $position, 1356945119);
176
        }
177
178
        return $fields[$position];
179
    }
180
181
    /**
182
     * Returns a field name.
183
     *
184
     * @param string $fieldName
185
     * @return array
186
     * @throws InvalidKeyInArrayException
187
     */
188
    public function getField($fieldName)
189
    {
190
        $fields = $this->getFields();
191
        return $fields[$fieldName];
192
    }
193
194
    /**
195
     * Returns an array containing column names for the Grid.
196
     *
197
     * @return array
198
     * @throws \Exception
199
     */
200
    public function getFields()
201
    {
202
        // Cache this operation since it can take some time.
203
        if (is_null($this->fields)) {
204
205
            // Fetch all available fields first.
206
            $fields = $this->getAllFields();
207
208
            if ($this->isBackendMode()) {
209
210
                // Then remove the not allowed.
211
                $fields = $this->filterByIncludedFields($fields);
212
                $fields = $this->filterByBackendUser($fields);
213
                $fields = $this->filterByExcludedFields($fields);
214
            }
215
216
            $this->fields = $fields;
217
        }
218
219
        return $this->fields;
220
    }
221
222
    /**
223
     * Remove fields according to Grid configuration.
224
     *
225
     * @param $fields
226
     * @return array
227
     */
228
    protected function filterByIncludedFields($fields)
229
    {
230
231
        $filteredFields = $fields;
232
        $includedFields = $this->getIncludedFields();
233
        if (count($includedFields) > 0) {
234
            $filteredFields = [];
235
            foreach ($fields as $fieldNameAndPath => $configuration) {
236
                if (in_array($fieldNameAndPath, $includedFields, true) || !Tca::table($this->tableName)->hasField($fieldNameAndPath)) {
237
                    $filteredFields[$fieldNameAndPath] = $configuration;
238
                }
239
            }
240
        }
241
        return $filteredFields;
242
    }
243
244
    /**
245
     * Remove fields according to BE User permission.
246
     *
247
     * @param $fields
248
     * @return array
249
     * @throws \Exception
250
     */
251 View Code Duplication
    protected function filterByBackendUser($fields)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
252
    {
253
        if (!$this->getBackendUser()->isAdmin()) {
254
            foreach ($fields as $fieldName => $field) {
255
                if (Tca::table($this->tableName)->hasField($fieldName) && !Tca::table($this->tableName)->field($fieldName)->hasAccess()) {
256
                    unset($fields[$fieldName]);
257
                }
258
            }
259
        }
260
        return $fields;
261
    }
262
263
    /**
264
     * Remove fields according to Grid configuration.
265
     *
266
     * @param $fields
267
     * @return array
268
     */
269
    protected function filterByExcludedFields($fields)
270
    {
271
272
        // Unset excluded fields.
273
        foreach ($this->getExcludedFields() as $excludedField) {
274
            if (isset($fields[$excludedField])) {
275
                unset($fields[$excludedField]);
276
            }
277
        }
278
279
        return $fields;
280
    }
281
282
    /**
283
     * Returns an array containing column names for the Grid.
284
     *
285
     * @return array
286
     */
287
    public function getAllFields()
288
    {
289
290
        // Cache this operation since it can take some time.
291
        if (is_null($this->allFields)) {
292
293
            $fields = is_array($this->tca['columns']) ? $this->tca['columns'] : [];
294
            $gridFieldNames = array_keys($fields);
295
296
            // Fetch all fields of the TCA and merge it back to the fields configured for Grid.
297
            $tableFieldNames = Tca::table($this->tableName)->getFields();
298
299
            // Just remove system fields from the Grid.
300
            foreach ($tableFieldNames as $key => $fieldName) {
301
                if (in_array($fieldName, Tca::getSystemFields())) {
302
                    unset($tableFieldNames[$key]);
303
                }
304
            }
305
306
            $additionalFields = array_diff($tableFieldNames, $gridFieldNames);
307
308
            if (!empty($additionalFields)) {
309
310
                // Pop out last element of the key
311
                // Idea is to place new un-configured columns in between. By default, they will be hidden.
312
                end($fields);
313
                $lastColumnKey = key($fields);
314
                $lastColumn = array_pop($fields);
315
316
                // Feed up the grid fields with un configured elements
317
                foreach ($additionalFields as $additionalField) {
318
                    $fields[$additionalField] = array(
319
                        'visible' => false
320
                    );
321
322
                    // Try to guess the format of the field.
323
                    $fieldType = Tca::table($this->tableName)->field($additionalField)->getType();
324
                    if ($fieldType === FieldType::DATE) {
325
                        $fields[$additionalField]['format'] = 'Fab\Vidi\Formatter\Date';
326
                    } elseif ($fieldType === FieldType::DATETIME) {
327
                        $fields[$additionalField]['format'] = 'Fab\Vidi\Formatter\Datetime';
328
                    }
329
                }
330
                $fields[$lastColumnKey] = $lastColumn;
331
            }
332
333
            $this->allFields = $fields;
334
        }
335
336
        return $this->allFields;
337
    }
338
339
    /**
340
     * Tell whether the field exists in the grid or not.
341
     *
342
     * @param string $fieldName
343
     * @return bool
344
     */
345
    public function hasField($fieldName)
346
    {
347
        $fields = $this->getFields();
348
        return isset($fields[$fieldName]);
349
    }
350
351
    /**
352
     * Tell whether the facet exists in the grid or not.
353
     *
354
     * @param string $facetName
355
     * @return bool
356
     */
357
    public function hasFacet($facetName)
358
    {
359
        $facets = $this->getFacets();
360
        return isset($facets[$facetName]);
361
    }
362
363
    /**
364
     * Returns an array containing facets fields.
365
     *
366
     * @return FacetInterface[]
367
     */
368
    public function getFacets()
369
    {
370
        if ($this->facets === null) {
371
            $this->facets = [];
372
373
            if (is_array($this->tca['facets'])) {
374
                foreach ($this->tca['facets'] as $key => $facetNameOrArray) {
375
                    if (is_array($facetNameOrArray)) {
376
377
                        $name = isset($facetNameOrArray['name'])
378
                            ? $facetNameOrArray['name']
379
                            : '';
380
                        $label = isset($facetNameOrArray['label'])
381
                            ? $this->getLanguageService()->sL($facetNameOrArray['label'])
382
                            : '';
383
384
                        $suggestions = isset($facetNameOrArray['suggestions'])
385
                            ? $facetNameOrArray['suggestions']
386
                            : [];
387
388
                        /** @var FacetInterface $facetObject */
389
                        $facetObject = GeneralUtility::makeInstance($key, $name, $label, $suggestions);
390
                        $this->facets[$facetObject->getName()] = $facetObject;
391
                    } else {
392
                        $this->facets[$facetNameOrArray] = $this->instantiateStandardFacet($facetNameOrArray);
393
                    }
394
                }
395
            }
396
        }
397
        return $this->facets;
398
    }
399
400
    /**
401
     * Returns the "sortable" value of the column.
402
     *
403
     * @param string $fieldName
404
     * @return int|string
405
     */
406
    public function isSortable($fieldName)
407
    {
408
        $defaultValue = true;
409
        $hasSortableField = Tca::table($this->tableName)->hasSortableField();
410
        if ($hasSortableField) {
411
            $isSortable = false;
412
        } else {
413
            $isSortable = $this->get($fieldName, 'sortable', $defaultValue);
414
        }
415
        return $isSortable;
416
    }
417
418
    /**
419
     * Returns the "canBeHidden" value of the column.
420
     *
421
     * @param string $fieldName
422
     * @return bool
423
     */
424
    public function canBeHidden($fieldName)
425
    {
426
        $defaultValue = true;
427
        return $this->get($fieldName, 'canBeHidden', $defaultValue);
428
    }
429
430
    /**
431
     * Returns the "width" value of the column.
432
     *
433
     * @param string $fieldName
434
     * @return int|string
435
     */
436
    public function getWidth($fieldName)
437
    {
438
        $defaultValue = 'auto';
439
        return $this->get($fieldName, 'width', $defaultValue);
440
    }
441
442
    /**
443
     * Returns the "visible" value of the column.
444
     *
445
     * @param string $fieldName
446
     * @return bool
447
     */
448
    public function isVisible($fieldName)
449
    {
450
        $defaultValue = true;
451
        return $this->get($fieldName, 'visible', $defaultValue);
452
    }
453
454
    /**
455
     * Returns the "editable" value of the column.
456
     *
457
     * @param string $columnName
458
     * @return bool
459
     */
460
    public function isEditable($columnName)
461
    {
462
        $defaultValue = false;
463
        return $this->get($columnName, 'editable', $defaultValue);
464
    }
465
466
    /**
467
     * Returns the "localized" value of the column.
468
     *
469
     * @param string $columnName
470
     * @return bool
471
     */
472
    public function isLocalized($columnName)
473
    {
474
        $defaultValue = true;
475
        return $this->get($columnName, 'localized', $defaultValue);
476
    }
477
478
    /**
479
     *
480
     * Returns the "html" value of the column.
481
     *
482
     * @param string $fieldName
483
     * @return string
484
     */
485
    public function getHeader($fieldName)
486
    {
487
        $defaultValue = '';
488
        return $this->get($fieldName, 'html', $defaultValue);
489
    }
490
491
    /**
492
     * Fetch a possible from a Grid Renderer. If no value is found, returns null
493
     *
494
     * @param string $fieldName
495
     * @param string $key
496
     * @param mixed $defaultValue
497
     * @return null|mixed
498
     */
499
    public function get($fieldName, $key, $defaultValue = null)
500
    {
501
        $value = $defaultValue;
502
503
        $field = $this->getField($fieldName);
504
        if (isset($field[$key])) {
505
            $value = $field[$key];
506
        } elseif ($this->hasRenderers($fieldName)) {
507
            $renderers = $this->getRenderers($fieldName);
508
            foreach ($renderers as $rendererConfiguration) {
509
                if (isset($rendererConfiguration[$key])) {
510
                    $value = $rendererConfiguration[$key];
511
                }
512
            }
513
        }
514
        return $value;
515
    }
516
517
    /**
518
     * Returns whether the column has a renderer.
519
     *
520
     * @param string $fieldName
521
     * @return bool
522
     */
523
    public function hasRenderers($fieldName)
524
    {
525
        $field = $this->getField($fieldName);
526
        return empty($field['renderer']) && empty($field['renderers']) ? false : true;
527
    }
528
529
    /**
530
     * Returns a renderer.
531
     *
532
     * @param string $fieldName
533
     * @return array
534
     */
535
    public function getRenderers($fieldName)
536
    {
537
        $field = $this->getField($fieldName);
538
        $renderers = [];
539
        if (!empty($field['renderer'])) {
540
            $renderers = $this->convertRendererToArray($field['renderer'], $field);
541
        } elseif (!empty($field['renderers']) && is_array($field['renderers'])) {
542
            foreach ($field['renderers'] as $renderer) {
543
                $rendererNameAndConfiguration = $this->convertRendererToArray($renderer, $field);
544
                $renderers = array_merge($renderers, $rendererNameAndConfiguration);
545
            }
546
        }
547
548
        return $renderers;
549
    }
550
551
    /**
552
     * @param string $renderer
553
     * @return array
554
     */
555
    protected function convertRendererToArray($renderer, array $field)
556
    {
557
        $result = [];
558
        if (is_string($renderer)) {
559
            $configuration = empty($field['rendererConfiguration'])
560
                ? []
561
                : $field['rendererConfiguration'];
562
563
            /** @var ColumnRendererInterface $rendererObject */
564
            $rendererObject = GeneralUtility::makeInstance($renderer);
565
566
            $result[$renderer] = array_merge($rendererObject->getConfiguration(), $configuration);
567
            // TODO: throw alert message because this is not compatible anymore as of TYPO3 8.7.7
568
        } elseif ($renderer instanceof ColumnRendererInterface) {
569
            /** @var ColumnRendererInterface $renderer */
570
            $result[get_class($renderer)] = $renderer->getConfiguration();
571
        }
572
        return $result;
573
    }
574
575
    /**
576
     * Returns the class names applied to a cell
577
     *
578
     * @param string $fieldName
579
     * @return bool
580
     */
581
    public function getClass($fieldName)
582
    {
583
        $field = $this->getField($fieldName);
584
        return isset($field['class']) ? $field['class'] : '';
585
    }
586
587
    /**
588
     * Returns whether the column has a label.
589
     *
590
     * @param string $fieldNameAndPath
591
     * @return bool
592
     */
593
    public function hasLabel($fieldNameAndPath)
594
    {
595
        $field = $this->getField($fieldNameAndPath);
596
597
        $hasLabel = empty($field['label']) ? false : true;
598
599 View Code Duplication
        if (!$hasLabel && $this->hasRenderers($fieldNameAndPath)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
600
            $renderers = $this->getRenderers($fieldNameAndPath);
601
            /** @var $renderer ColumnRendererInterface */
602
            foreach ($renderers as $renderer) {
603
                if (isset($renderer['label'])) {
604
                    $hasLabel = true;
605
                    break;
606
                }
607
            }
608
        }
609
        return $hasLabel;
610
    }
611
612
    /**
613
     * @return array
614
     */
615
    public function getTca()
616
    {
617
        return $this->tca;
618
    }
619
620
    /**
621
     * @return array
622
     */
623
    public function getIncludedFields()
624
    {
625
        return empty($this->tca['included_fields']) ? [] : GeneralUtility::trimExplode(',', $this->tca['included_fields'], true);
626
    }
627
628
    /**
629
     * Return excluded fields from configuration + preferences.
630
     *
631
     * @return array
632
     */
633
    public function getExcludedFields()
634
    {
635
        $configurationFields = $this->getExcludedFieldsFromConfiguration();
636
        $preferencesFields = $this->getExcludedFieldsFromPreferences();
637
638
        return array_merge($configurationFields, $preferencesFields);
639
    }
640
641
    /**
642
     * Fetch excluded fields from configuration.
643
     *
644
     * @return array
645
     */
646
    protected function getExcludedFieldsFromConfiguration()
647
    {
648
        $excludedFields = [];
649
        if (!empty($this->tca['excluded_fields'])) {
650
            $excludedFields = GeneralUtility::trimExplode(',', $this->tca['excluded_fields'], true);
651
        } elseif (!empty($this->tca['export']['excluded_fields'])) { // only for export for legacy reason.
652
            $excludedFields = GeneralUtility::trimExplode(',', $this->tca['export']['excluded_fields'], true);
653
        }
654
        return $excludedFields;
655
656
    }
657
658
    /**
659
     * Fetch excluded fields from preferences.
660
     *
661
     * @return array
662
     */
663
    protected function getExcludedFieldsFromPreferences()
664
    {
665
        $excludedFields = $this->getModulePreferences()->get(ConfigurablePart::EXCLUDED_FIELDS, $this->tableName);
666
        return is_array($excludedFields) ? $excludedFields : [];
667
    }
668
669
    /**
670
     * @return array
671
     */
672
    public function areFilesIncludedInExport()
673
    {
674
        $isIncluded = true;
675
676
        if (isset($this->tca['export']['include_files'])) {
677
            $isIncluded = $this->tca['export']['include_files'];
678
        }
679
        return $isIncluded;
680
    }
681
682
    /**
683
     * Returns a "facet" service instance.
684
     *
685
     * @param string|FacetInterface $facetName
686
     * @return StandardFacet
687
     */
688
    protected function instantiateStandardFacet($facetName)
689
    {
690
        $label = $this->getLabel($facetName);
0 ignored issues
show
Bug introduced by
It seems like $facetName defined by parameter $facetName on line 688 can also be of type object<Fab\Vidi\Facet\FacetInterface>; however, Fab\Vidi\Tca\GridService::getLabel() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
691
692
        /** @var StandardFacet $facetName */
693
        $facet = GeneralUtility::makeInstance(StandardFacet::class, $facetName, $label);
694
695
        if (!$facet instanceof StandardFacet) {
696
            throw new \RuntimeException('I could not instantiate a facet for facet name "' . (string)$facet . '""', 1445856345);
697
        }
698
        return $facet;
699
    }
700
701
    /**
702
     * Returns a "facet" service instance.
703
     *
704
     * @param string|FacetInterface $facetName
705
     * @return FacetInterface
706
     */
707
    public function facet($facetName = '')
708
    {
709
        $facets = $this->getFacets();
710
        return $facets[$facetName];
711
    }
712
713
    /**
714
     * @return \Fab\Vidi\Resolver\FieldPathResolver|object
715
     */
716
    protected function getFieldPathResolver()
717
    {
718
        return GeneralUtility::makeInstance(\Fab\Vidi\Resolver\FieldPathResolver::class);
719
    }
720
721
    /**
722
     * @return ModulePreferences|object
723
     */
724
    protected function getModulePreferences()
725
    {
726
        return GeneralUtility::makeInstance(ModulePreferences::class);
727
    }
728
729
    /**
730
     * @return \TYPO3\CMS\Lang\LanguageService|object
731
     * @throws \InvalidArgumentException
732
     */
733
    protected function getLanguageService()
734
    {
735
        return GeneralUtility::makeInstance(\TYPO3\CMS\Lang\LanguageService::class);
736
    }
737
738
}
739