Completed
Push — master ( f62711...a1bda7 )
by Fabien
03:34
created

GridService::getHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
namespace Fab\Vidi\Tca;
3
4
/**
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use Fab\Vidi\Grid\ColumnInterface;
18
use Fab\Vidi\Grid\ColumnRendererInterface;
19
use Fab\Vidi\Grid\GenericColumn;
20
use Fab\Vidi\Module\ConfigurablePart;
21
use Fab\Vidi\Module\ModulePreferences;
22
use TYPO3\CMS\Core\Utility\GeneralUtility;
23
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
24
use Fab\Vidi\Exception\InvalidKeyInArrayException;
25
use Fab\Vidi\Facet\StandardFacet;
26
use Fab\Vidi\Facet\FacetInterface;
27
28
/**
29
 * A class to handle TCA grid configuration
30
 */
31
class GridService extends AbstractTca
32
{
33
34
    /**
35
     * @var array
36
     */
37
    protected $tca;
38
39
    /**
40
     * @var string
41
     */
42
    protected $tableName;
43
44
    /**
45
     * All fields available in the Grid.
46
     *
47
     * @var array
48
     */
49
    protected $fields;
50
51
    /**
52
     * All fields regardless whether they have been excluded or not.
53
     *
54
     * @var array
55
     */
56
    protected $allFields;
57
58
    /**
59
     * @var array
60
     */
61
    protected $instances;
62
63
    /**
64
     * @var array
65
     */
66
    protected $facets;
67
68
    /**
69
     * __construct
70
     *
71
     * @throws InvalidKeyInArrayException
72
     * @param string $tableName
73
     * @return \Fab\Vidi\Tca\GridService
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
74
     */
75
    public function __construct($tableName)
0 ignored issues
show
Coding Style introduced by
__construct uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
76
    {
77
78
        $this->tableName = $tableName;
79
80
        if (empty($GLOBALS['TCA'][$this->tableName])) {
81
            throw new InvalidKeyInArrayException('No TCA existence for table name: ' . $this->tableName, 1356945108);
82
        }
83
84
        $this->tca = $GLOBALS['TCA'][$this->tableName]['grid'];
85
    }
86
87
    /**
88
     * Returns an array containing column names.
89
     *
90
     * @return array
91
     */
92
    public function getFieldNames()
93
    {
94
        $fields = $this->getFields();
95
        return array_keys($fields);
96
    }
97
98
    /**
99
     * Returns an array containing column names.
100
     *
101
     * @return array
102
     */
103
    public function getAllFieldNames()
104
    {
105
        $allFields = $this->getAllFields();
106
        return array_keys($allFields);
107
    }
108
109
    /**
110
     * Get the label key.
111
     *
112
     * @param string $fieldNameAndPath
113
     * @return string
114
     * @throws \Fab\Vidi\Exception\InvalidKeyInArrayException
115
     */
116
    public function getLabelKey($fieldNameAndPath)
117
    {
118
119
        $field = $this->getField($fieldNameAndPath);
120
121
        // First option is to get the label from the Grid TCA.
122
        $rawLabel = '';
123
        if (isset($field['label'])) {
124
            $rawLabel = $field['label'];
125
        }
126
127
        // Second option is to fetch the label from the Column Renderer object.
128 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...
129
            $renderers = $this->getRenderers($fieldNameAndPath);
130
            /** @var $renderer ColumnRendererInterface */
131
            foreach ($renderers as $renderer) {
132
                if (isset($renderer['label'])) {
133
                    $rawLabel = $renderer['label'];
134
                    break;
135
                }
136
            }
137
        }
138
        return $rawLabel;
139
    }
140
141
    /**
142
     * Get the translation of a label given a column name.
143
     *
144
     * @param string $fieldNameAndPath
145
     * @return string
146
     */
147
    public function getLabel($fieldNameAndPath)
148
    {
149
        $label = '';
150
        if ($this->hasLabel($fieldNameAndPath)) {
151
            $labelKey = $this->getLabelKey($fieldNameAndPath);
152
            $label = LocalizationUtility::translate($labelKey, '');
153
            if (is_null($label)) {
154
                $label = $labelKey;
155
            }
156
        } else {
157
158
            // Important to notice the label can contains a path, e.g. metadata.categories and must be resolved.
159
            $dataType = $this->getFieldPathResolver()->getDataType($fieldNameAndPath, $this->tableName);
160
            $fieldName = $this->getFieldPathResolver()->stripFieldPath($fieldNameAndPath, $this->tableName);
161
            $table = Tca::table($dataType);
162
163
            if ($table->hasField($fieldName) && $table->field($fieldName)->hasLabel()) {
164
                $label = $table->field($fieldName)->getLabel();
165
            }
166
        }
167
168
        return $label;
169
    }
170
171
    /**
172
     * Returns the field name given its position.
173
     *
174
     * @param string $position the position of the field in the grid
175
     * @throws InvalidKeyInArrayException
176
     * @return int
177
     */
178
    public function getFieldNameByPosition($position)
179
    {
180
        $fields = array_keys($this->getFields());
181
        if (empty($fields[$position])) {
182
            throw new InvalidKeyInArrayException('No field exist for position: ' . $position, 1356945119);
183
        }
184
185
        return $fields[$position];
186
    }
187
188
    /**
189
     * Returns a field name.
190
     *
191
     * @param string $fieldName
192
     * @return array
193
     * @throws InvalidKeyInArrayException
194
     */
195
    public function getField($fieldName)
196
    {
197
        $fields = $this->getFields();
198
        return $fields[$fieldName];
199
    }
200
201
    /**
202
     * Returns an array containing column names for the Grid.
203
     *
204
     * @return array
205
     * @throws \Exception
206
     */
207
    public function getFields()
208
    {
209
        // Cache this operation since it can take some time.
210
        if (is_null($this->fields)) {
211
212
            // Fetch all available fields first.
213
            $fields = $this->getAllFields();
214
215
            if ($this->isBackendMode()) {
216
217
                // Then remove the not allowed.
218
                $fields = $this->filterByIncludedFields($fields);
219
                $fields = $this->filterByBackendUser($fields);
220
                $fields = $this->filterByExcludedFields($fields);
221
            }
222
223
            $this->fields = $fields;
224
        }
225
226
        return $this->fields;
227
    }
228
229
    /**
230
     * Remove fields according to Grid configuration.
231
     *
232
     * @param $fields
233
     * @return array
234
     */
235
    protected function filterByIncludedFields($fields)
236
    {
237
238
        $filteredFields = $fields;
239
        $includedFields = $this->getIncludedFields();
240
        if (count($includedFields) > 0) {
241
            $filteredFields = [];
242
            foreach ($fields as $fieldNameAndPath => $configuration) {
243
                if (in_array($fieldNameAndPath, $includedFields, true) || !Tca::table($this->tableName)->hasField($fieldNameAndPath)) {
244
                    $filteredFields[$fieldNameAndPath] = $configuration;
245
                }
246
            }
247
        }
248
        return $filteredFields;
249
    }
250
251
    /**
252
     * Remove fields according to BE User permission.
253
     *
254
     * @param $fields
255
     * @return array
256
     * @throws \Exception
257
     */
258 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...
259
    {
260
        if (!$this->getBackendUser()->isAdmin()) {
261
            foreach ($fields as $fieldName => $field) {
262
                if (Tca::table($this->tableName)->hasField($fieldName) && !Tca::table($this->tableName)->field($fieldName)->hasAccess()) {
263
                    unset($fields[$fieldName]);
264
                }
265
            }
266
        }
267
        return $fields;
268
    }
269
270
    /**
271
     * Remove fields according to Grid configuration.
272
     *
273
     * @param $fields
274
     * @return array
275
     */
276
    protected function filterByExcludedFields($fields)
277
    {
278
279
        // Unset excluded fields.
280
        foreach ($this->getExcludedFields() as $excludedField) {
281
            if (isset($fields[$excludedField])) {
282
                unset($fields[$excludedField]);
283
            }
284
        }
285
286
        return $fields;
287
    }
288
289
    /**
290
     * Returns an array containing column names for the Grid.
291
     *
292
     * @return array
293
     */
294
    public function getAllFields()
295
    {
296
297
        // Cache this operation since it can take some time.
298
        if (is_null($this->allFields)) {
299
300
            $fields = is_array($this->tca['columns']) ? $this->tca['columns'] : array();
301
            $gridFieldNames = array_keys($fields);
302
303
            // Fetch all fields of the TCA and merge it back to the fields configured for Grid.
304
            $tableFieldNames = Tca::table($this->tableName)->getFields();
305
306
            // Just remove system fields from the Grid.
307
            foreach ($tableFieldNames as $key => $fieldName) {
308
                if (in_array($fieldName, Tca::getSystemFields())) {
309
                    unset($tableFieldNames[$key]);
310
                }
311
            }
312
313
            $additionalFields = array_diff($tableFieldNames, $gridFieldNames);
314
315
            if (!empty($additionalFields)) {
316
317
                // Pop out last element of the key
318
                // Idea is to place new un-configured columns in between. By default, they will be hidden.
319
                end($fields);
320
                $lastColumnKey = key($fields);
321
                $lastColumn = array_pop($fields);
322
323
                // Feed up the grid fields with un configured elements
324
                foreach ($additionalFields as $additionalField) {
325
                    $fields[$additionalField] = array(
326
                        'visible' => FALSE
327
                    );
328
329
                    // Try to guess the format of the field.
330
                    $fieldType = Tca::table($this->tableName)->field($additionalField)->getType();
331
                    if ($fieldType === FieldType::DATE) {
332
                        $fields[$additionalField]['format'] = 'Fab\Vidi\Formatter\Date';
333
                    } elseif ($fieldType === FieldType::DATETIME) {
334
                        $fields[$additionalField]['format'] = 'Fab\Vidi\Formatter\Datetime';
335
                    }
336
                }
337
                $fields[$lastColumnKey] = $lastColumn;
338
            }
339
340
            $this->allFields = $fields;
341
        }
342
343
        return $this->allFields;
344
    }
345
346
    /**
347
     * Tell whether the field exists in the grid or not.
348
     *
349
     * @param string $fieldName
350
     * @return bool
351
     */
352
    public function hasField($fieldName)
353
    {
354
        $fields = $this->getFields();
355
        return isset($fields[$fieldName]);
356
    }
357
358
    /**
359
     * Tell whether the facet exists in the grid or not.
360
     *
361
     * @param string $facetName
362
     * @return bool
363
     */
364
    public function hasFacet($facetName)
365
    {
366
        $facets = $this->getFacets();
367
        return isset($facets[$facetName]);
368
    }
369
370
    /**
371
     * Returns an array containing facets fields.
372
     *
373
     * @return FacetInterface[]
374
     */
375
    public function getFacets()
376
    {
377
        if (is_null($this->facets)) {
378
            $this->facets = array();
379
380
            if (is_array($this->tca['facets'])) {
381
                foreach ($this->tca['facets'] as $facetNameOrObject) {
382
                    if ($facetNameOrObject instanceof FacetInterface) {
383
                        $this->facets[$facetNameOrObject->getName()] = $facetNameOrObject;
384
                    } else {
385
                        $this->facets[$facetNameOrObject] = $this->instantiateStandardFacet($facetNameOrObject);
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)
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)
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)
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)
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)
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)
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)
529
    {
530
        $field = $this->getField($fieldName);
531
        $renderers = array();
532
        if (!empty($field['renderer'])) {
533
            $renderers = $this->convertRendererToArray($field['renderer']);
534
        } elseif (!empty($field['renderers']) && is_array($field['renderers'])) {
535
            foreach ($field['renderers'] as $renderer) {
536
                $rendererNameAndConfiguration = $this->convertRendererToArray($renderer);
537
                $renderers = array_merge($renderers, $rendererNameAndConfiguration);
538
            }
539
        }
540
541
        return $renderers;
542
    }
543
544
    /**
545
     * @param string|GenericColumn $renderer
546
     * @return array
547
     */
548
    public function convertRendererToArray($renderer)
549
    {
550
        $result = array();
551
        if (is_string($renderer)) {
552
            $result[$renderer] = array();
553
        } elseif ($renderer instanceof ColumnInterface || $renderer instanceof ColumnRendererInterface) {
554
            /** @var GenericColumn $renderer */
555
            $result[get_class($renderer)] = $renderer->getConfiguration();
0 ignored issues
show
Deprecated Code introduced by
The method Fab\Vidi\Grid\GenericColumn::getConfiguration() has been deprecated with message: will be removed in Vidi 2.0 + 2.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
556
        }
557
        return $result;
558
    }
559
560
    /**
561
     * Returns the class names applied to a cell
562
     *
563
     * @param string $fieldName
564
     * @return bool
565
     */
566
    public function getClass($fieldName)
567
    {
568
        $field = $this->getField($fieldName);
569
        return isset($field['class']) ? $field['class'] : '';
570
    }
571
572
    /**
573
     * Returns whether the column has a label.
574
     *
575
     * @param string $fieldNameAndPath
576
     * @return bool
577
     */
578
    public function hasLabel($fieldNameAndPath)
579
    {
580
        $field = $this->getField($fieldNameAndPath);
581
582
        $hasLabel = empty($field['label']) ? FALSE : TRUE;
583
584 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...
585
            $renderers = $this->getRenderers($fieldNameAndPath);
586
            /** @var $renderer ColumnRendererInterface */
587
            foreach ($renderers as $renderer) {
588
                if (isset($renderer['label'])) {
589
                    $hasLabel = TRUE;
590
                    break;
591
                }
592
            }
593
        }
594
        return $hasLabel;
595
    }
596
597
    /**
598
     * @return array
599
     */
600
    public function getTca()
601
    {
602
        return $this->tca;
603
    }
604
605
    /**
606
     * @return array
607
     */
608
    public function getIncludedFields()
609
    {
610
        return empty($this->tca['included_fields']) ? [] : GeneralUtility::trimExplode(',', $this->tca['included_fields'], true);
611
    }
612
613
    /**
614
     * Return excluded fields from configuration + preferences.
615
     *
616
     * @return array
617
     */
618
    public function getExcludedFields()
619
    {
620
        $configurationFields = $this->getExcludedFieldsFromConfiguration();
621
        $preferencesFields = $this->getExcludedFieldsFromPreferences();
622
623
        return array_merge($configurationFields, $preferencesFields);
624
    }
625
626
    /**
627
     * Fetch excluded fields from configuration.
628
     *
629
     * @return array
630
     */
631
    protected function getExcludedFieldsFromConfiguration()
632
    {
633
        $excludedFields = array();
634
        if (!empty($this->tca['excluded_fields'])) {
635
            $excludedFields = GeneralUtility::trimExplode(',', $this->tca['excluded_fields'], TRUE);
636
        } elseif (!empty($this->tca['export']['excluded_fields'])) { // only for export for legacy reason.
637
            $excludedFields = GeneralUtility::trimExplode(',', $this->tca['export']['excluded_fields'], TRUE);
638
        }
639
        return $excludedFields;
640
641
    }
642
643
    /**
644
     * Fetch excluded fields from preferences.
645
     *
646
     * @return array
647
     */
648
    protected function getExcludedFieldsFromPreferences()
649
    {
650
        $excludedFields = $this->getModulePreferences()->get(ConfigurablePart::EXCLUDED_FIELDS, $this->tableName);
651
        return is_array($excludedFields) ? $excludedFields : array();
652
    }
653
654
    /**
655
     * @return array
656
     */
657
    public function areFilesIncludedInExport()
658
    {
659
        $isIncluded = TRUE;
660
661
        if (isset($this->tca['export']['include_files'])) {
662
            $isIncluded = $this->tca['export']['include_files'];
663
        }
664
        return $isIncluded;
665
    }
666
667
    /**
668
     * Returns a "facet" service instance.
669
     *
670
     * @param string|FacetInterface $facetName
671
     * @return StandardFacet
672
     */
673
    protected function instantiateStandardFacet($facetName)
674
    {
675
        $label = $this->getLabel($facetName);
0 ignored issues
show
Bug introduced by
It seems like $facetName defined by parameter $facetName on line 673 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...
676
677
        /** @var StandardFacet $facetName */
678
        $facet = GeneralUtility::makeInstance('Fab\Vidi\Facet\StandardFacet', $facetName, $label);
679
680
        if (!$facet instanceof StandardFacet) {
681
            throw new \RuntimeException('I could not instantiate a facet for facet name "' . (string)$facet . '""', 1445856345);
682
        }
683
        return $facet;
684
    }
685
686
    /**
687
     * Returns a "facet" service instance.
688
     *
689
     * @param string|FacetInterface $facetName
690
     * @return FacetInterface
691
     */
692
    public function facet($facetName = '')
693
    {
694
        $facets = $this->getFacets();
695
        return $facets[$facetName];
696
    }
697
698
    /**
699
     * @return \Fab\Vidi\Resolver\FieldPathResolver
700
     */
701
    protected function getFieldPathResolver()
702
    {
703
        return GeneralUtility::makeInstance('Fab\Vidi\Resolver\FieldPathResolver');
704
    }
705
706
    /**
707
     * @return ModulePreferences
708
     */
709
    protected function getModulePreferences()
710
    {
711
        return GeneralUtility::makeInstance('Fab\Vidi\Module\ModulePreferences');
712
    }
713
714
}
715