Passed
Push — master ( 1e85f0...74899e )
by
unknown
14:10
created

GridDataService::generateDataArray()   F

Complexity

Conditions 23
Paths 4

Size

Total Lines 132
Code Lines 102

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 23
eloc 102
nc 4
nop 2
dl 0
loc 132
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Workspaces\Service;
17
18
use Psr\EventDispatcher\EventDispatcherInterface;
19
use Psr\Log\LoggerAwareInterface;
20
use Psr\Log\LoggerAwareTrait;
21
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
22
use TYPO3\CMS\Backend\Utility\BackendUtility;
23
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24
use TYPO3\CMS\Core\Cache\CacheManager;
25
use TYPO3\CMS\Core\Imaging\Icon;
26
use TYPO3\CMS\Core\Imaging\IconFactory;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
use TYPO3\CMS\Core\Versioning\VersionState;
29
use TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord;
30
use TYPO3\CMS\Workspaces\Event\AfterCompiledCacheableDataForWorkspaceEvent;
31
use TYPO3\CMS\Workspaces\Event\AfterDataGeneratedForWorkspaceEvent;
32
use TYPO3\CMS\Workspaces\Event\GetVersionedDataEvent;
33
use TYPO3\CMS\Workspaces\Event\SortVersionedDataEvent;
34
use TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder;
35
use TYPO3\CMS\Workspaces\Service\Dependency\CollectionService;
36
37
/**
38
 * Grid data service
39
 */
40
class GridDataService implements LoggerAwareInterface
41
{
42
    use LoggerAwareTrait;
43
44
    const GridColumn_Collection = 'Workspaces_Collection';
45
    const GridColumn_CollectionLevel = 'Workspaces_CollectionLevel';
46
    const GridColumn_CollectionParent = 'Workspaces_CollectionParent';
47
    const GridColumn_CollectionCurrent = 'Workspaces_CollectionCurrent';
48
    const GridColumn_CollectionChildren = 'Workspaces_CollectionChildren';
49
50
    /**
51
     * Id of the current active workspace.
52
     *
53
     * @var int
54
     */
55
    protected $currentWorkspace;
56
57
    /**
58
     * Version record information (filtered, sorted and limited)
59
     *
60
     * @var array
61
     */
62
    protected $dataArray = [];
63
64
    /**
65
     * Name of the field used for sorting.
66
     *
67
     * @var string
68
     */
69
    protected $sort = '';
70
71
    /**
72
     * Direction used for sorting (ASC, DESC).
73
     *
74
     * @var string
75
     */
76
    protected $sortDir = '';
77
78
    /**
79
     * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
80
     */
81
    protected $workspacesCache;
82
83
    /**
84
     * @var IntegrityService
85
     */
86
    protected $integrityService;
87
88
    /**
89
     * @var EventDispatcherInterface
90
     */
91
    protected $eventDispatcher;
92
93
    public function __construct(EventDispatcherInterface $eventDispatcher)
94
    {
95
        $this->eventDispatcher = $eventDispatcher;
96
    }
97
98
    /**
99
     * Generates grid list array from given versions.
100
     *
101
     * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
102
     * @param \stdClass $parameter Parameters as submitted by JavaScript component
103
     * @param int $currentWorkspace The current workspace
104
     * @return array Version record information (filtered, sorted and limited)
105
     * @throws \InvalidArgumentException
106
     */
107
    public function generateGridListFromVersions($versions, $parameter, $currentWorkspace)
108
    {
109
        // Read the given parameters from grid. If the parameter is not set use default values.
110
        $filterTxt = $parameter->filterTxt ?? '';
111
        $start = isset($parameter->start) ? (int)$parameter->start : 0;
112
        $limit = isset($parameter->limit) ? (int)$parameter->limit : 30;
113
        $this->sort = $parameter->sort ?? 't3ver_oid';
114
        $this->sortDir = $parameter->dir ?? 'ASC';
115
        if (is_int($currentWorkspace)) {
0 ignored issues
show
introduced by
The condition is_int($currentWorkspace) is always true.
Loading history...
116
            $this->currentWorkspace = $currentWorkspace;
117
        } else {
118
            throw new \InvalidArgumentException('No such workspace defined', 1476048304);
119
        }
120
        $data = [];
121
        $data['data'] = [];
122
        $this->generateDataArray($versions, $filterTxt);
123
        $data['total'] = count($this->dataArray);
124
        $data['data'] = $this->getDataArray($start, $limit);
125
        return $data;
126
    }
127
128
    /**
129
     * Generates grid list array from given versions.
130
     *
131
     * @param array $versions All available version records
132
     * @param string $filterTxt Text to be used to filter record result
133
     */
134
    protected function generateDataArray(array $versions, $filterTxt)
135
    {
136
        $backendUser = $this->getBackendUser();
137
        $workspaceAccess = $backendUser->checkWorkspace($backendUser->workspace);
138
        $swapStage = $workspaceAccess['publish_access'] & 1 ? StagesService::STAGE_PUBLISH_ID : 0;
139
        $swapAccess = $backendUser->workspacePublishAccess($backendUser->workspace);
140
        $this->initializeWorkspacesCachingFramework();
141
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
142
        // check for dataArray in cache
143
        if ($this->getDataArrayFromCache($versions, $filterTxt) === false) {
144
            $stagesObj = GeneralUtility::makeInstance(StagesService::class);
145
            $defaultGridColumns = [
146
                self::GridColumn_Collection => 0,
147
                self::GridColumn_CollectionLevel => 0,
148
                self::GridColumn_CollectionParent => '',
149
                self::GridColumn_CollectionCurrent => '',
150
                self::GridColumn_CollectionChildren => 0,
151
            ];
152
            foreach ($versions as $table => $records) {
153
                $table = (string)$table;
154
                $hiddenField = $this->getTcaEnableColumnsFieldName($table, 'disabled');
155
                $isRecordTypeAllowedToModify = $backendUser->check('tables_modify', $table);
156
157
                foreach ($records as $record) {
158
                    $origRecord = (array)BackendUtility::getRecord($table, $record['t3ver_oid']);
159
                    $versionRecord = (array)BackendUtility::getRecord($table, $record['uid']);
160
                    $combinedRecord = CombinedRecord::createFromArrays($table, $origRecord, $versionRecord);
161
                    $this->getIntegrityService()->checkElement($combinedRecord);
162
163
                    if ($hiddenField !== null) {
164
                        $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord[$hiddenField], $versionRecord[$hiddenField]);
165
                    } else {
166
                        $recordState = $this->workspaceState($versionRecord['t3ver_state']);
167
                    }
168
169
                    $isDeletedPage = $table === 'pages' && $recordState === 'deleted';
170
                    $pageId = $table === 'pages' ? $record['uid'] : $record['pid'];
171
                    $viewUrl = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, $record['uid'], $origRecord, $versionRecord);
172
                    $workspaceRecordLabel = BackendUtility::getRecordTitle($table, $versionRecord);
173
                    $liveRecordLabel = BackendUtility::getRecordTitle($table, $origRecord);
174
                    [$pathWorkspaceCropped, $pathWorkspace] = BackendUtility::getRecordPath((int)$record['wspid'], '', 15, 1000);
175
                    $calculatedT3verOid = $record['t3ver_oid'];
176
                    if ((int)$record['t3ver_state'] === VersionState::NEW_PLACEHOLDER) {
177
                        // If we're dealing with a 'new' record, this one has no t3ver_oid. On publish, there is no
178
                        // live counterpart, but the publish methods later need a live uid to publish to. We thus
179
                        // use the uid as t3ver_oid here to be transparent on javascript side.
180
                        $calculatedT3verOid = $record['uid'];
181
                    }
182
183
                    $versionArray = [];
184
                    $versionArray['table'] = $table;
185
                    $versionArray['id'] = $table . ':' . $record['uid'];
186
                    $versionArray['uid'] = $record['uid'];
187
                    $versionArray = array_merge($versionArray, $defaultGridColumns);
188
                    $versionArray['label_Workspace'] = htmlspecialchars($workspaceRecordLabel);
189
                    $versionArray['label_Workspace_crop'] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($workspaceRecordLabel, $backendUser->uc['titleLen']));
190
                    $versionArray['label_Live'] = htmlspecialchars($liveRecordLabel);
191
                    $versionArray['label_Live_crop'] = htmlspecialchars(GeneralUtility::fixed_lgd_cs($liveRecordLabel, $backendUser->uc['titleLen']));
192
                    $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
193
                    $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
194
                    $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
195
                    $versionArray['value_nextStage'] = (int)$tempStage['uid'];
196
                    $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
197
                    $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
198
                    $versionArray['value_prevStage'] = (int)$tempStage['uid'];
199
                    $versionArray['path_Live'] = htmlspecialchars(BackendUtility::getRecordPath($record['livepid'], '', 999));
0 ignored issues
show
Bug introduced by
It seems like TYPO3\CMS\Backend\Utilit...rd['livepid'], '', 999) can also be of type array<integer,string>; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

199
                    $versionArray['path_Live'] = htmlspecialchars(/** @scrutinizer ignore-type */ BackendUtility::getRecordPath($record['livepid'], '', 999));
Loading history...
200
                    $versionArray['path_Workspace'] = htmlspecialchars($pathWorkspace);
201
                    $versionArray['path_Workspace_crop'] = htmlspecialchars($pathWorkspaceCropped);
202
                    $versionArray['workspace_Title'] = htmlspecialchars(WorkspaceService::getWorkspaceTitle($versionRecord['t3ver_wsid']));
203
                    $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
204
                    $versionArray['workspace_Formated_Tstamp'] = BackendUtility::datetime($versionRecord['tstamp']);
205
                    $versionArray['t3ver_wsid'] = $versionRecord['t3ver_wsid'];
206
                    $versionArray['t3ver_oid'] = $calculatedT3verOid;
207
                    $versionArray['livepid'] = $record['livepid'];
208
                    $versionArray['stage'] = $versionRecord['t3ver_stage'];
209
                    $versionArray['icon_Live'] = $iconFactory->getIconForRecord($table, $origRecord, Icon::SIZE_SMALL)->render();
210
                    $versionArray['icon_Workspace'] = $iconFactory->getIconForRecord($table, $versionRecord, Icon::SIZE_SMALL)->render();
211
                    $languageValue = $this->getLanguageValue($table, $versionRecord);
212
                    $versionArray['languageValue'] = $languageValue;
213
                    $versionArray['language'] = [
214
                        'icon' => $iconFactory->getIcon($this->getSystemLanguageValue($languageValue, $pageId, 'flagIcon'), Icon::SIZE_SMALL)->render()
215
                    ];
216
                    $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
217
                    $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
218
                    if ($swapAccess && $swapStage != 0 && $versionRecord['t3ver_stage'] == $swapStage) {
219
                        $versionArray['allowedAction_publish'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
220
                    } elseif ($swapAccess && $swapStage == 0) {
221
                        $versionArray['allowedAction_publish'] = $isRecordTypeAllowedToModify;
222
                    } else {
223
                        $versionArray['allowedAction_publish'] = false;
224
                    }
225
                    $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
226
                    // preview and editing of a deleted page won't work ;)
227
                    $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
228
                    $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
229
                    $versionArray['allowedAction_editVersionedPage'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
230
                    $versionArray['state_Workspace'] = $recordState;
231
232
                    $versionArray = array_merge(
233
                        $versionArray,
234
                        $this->getAdditionalColumnService()->getData($combinedRecord)
235
                    );
236
237
                    if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
238
                        $versionIdentifier = $versionArray['id'];
239
                        $this->dataArray[$versionIdentifier] = $versionArray;
240
                    }
241
                }
242
            }
243
244
            // Trigger a PSR-14 event
245
            $event = new AfterCompiledCacheableDataForWorkspaceEvent($this, $this->dataArray, $versions);
246
            $this->eventDispatcher->dispatch($event);
247
            $this->dataArray = $event->getData();
248
            $versions = $event->getVersions();
249
            // Enrich elements after everything has been processed:
250
            foreach ($this->dataArray as &$element) {
251
                $identifier = $element['table'] . ':' . $element['t3ver_oid'];
252
                $element['integrity'] = [
253
                    'status' => $this->getIntegrityService()->getStatusRepresentation($identifier),
254
                    'messages' => htmlspecialchars((string)$this->getIntegrityService()->getIssueMessages($identifier, true))
255
                ];
256
            }
257
            $this->setDataArrayIntoCache($versions, $filterTxt);
258
        }
259
260
        // Trigger a PSR-14 event
261
        $event = new AfterDataGeneratedForWorkspaceEvent($this, $this->dataArray, $versions);
262
        $this->eventDispatcher->dispatch($event);
263
        $this->dataArray = $event->getData();
264
        $this->sortDataArray();
265
        $this->resolveDataArrayDependencies();
266
    }
267
268
    /**
269
     * Resolves dependencies of nested structures
270
     * and sort data elements considering these dependencies.
271
     */
272
    protected function resolveDataArrayDependencies()
273
    {
274
        $collectionService = $this->getDependencyCollectionService();
275
        $dependencyResolver = $collectionService->getDependencyResolver();
276
277
        foreach ($this->dataArray as $dataElement) {
278
            $dependencyResolver->addElement($dataElement['table'], $dataElement['uid']);
279
        }
280
281
        $this->dataArray = $collectionService->process($this->dataArray);
282
    }
283
284
    /**
285
     * Gets the data array by considering the page to be shown in the grid view.
286
     *
287
     * @param int $start
288
     * @param int $limit
289
     * @return array
290
     */
291
    protected function getDataArray($start, $limit)
292
    {
293
        $dataArrayPart = [];
294
        $dataArrayCount = count($this->dataArray);
295
        $end = ($start + $limit < $dataArrayCount ? $start + $limit : $dataArrayCount);
296
297
        // Ensure that there are numerical indexes
298
        $this->dataArray = array_values($this->dataArray);
299
        for ($i = $start; $i < $end; $i++) {
300
            $dataArrayPart[] = $this->dataArray[$i];
301
        }
302
303
        // Ensure that collections are not cut for the pagination
304
        if (!empty($this->dataArray[$i][self::GridColumn_Collection])) {
305
            $collectionIdentifier = $this->dataArray[$i][self::GridColumn_Collection];
306
            for ($i = $i + 1; $i < $dataArrayCount && $collectionIdentifier === $this->dataArray[$i][self::GridColumn_Collection]; $i++) {
307
                $dataArrayPart[] = $this->dataArray[$i];
308
            }
309
        }
310
311
        // Trigger a PSR-14 event
312
        $event = new GetVersionedDataEvent($this, $this->dataArray, $start, $limit, $dataArrayPart);
313
        $this->eventDispatcher->dispatch($event);
314
        $this->dataArray = $event->getData();
315
        return $event->getDataArrayPart();
316
    }
317
318
    /**
319
     * Initializes the workspace cache
320
     */
321
    protected function initializeWorkspacesCachingFramework()
322
    {
323
        $this->workspacesCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('workspaces_cache');
324
    }
325
326
    /**
327
     * Puts the generated dataArray into the workspace cache.
328
     *
329
     * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
330
     * @param string $filterTxt The given filter text from the grid.
331
     */
332
    protected function setDataArrayIntoCache(array $versions, $filterTxt)
333
    {
334
        $hash = $this->calculateHash($versions, $filterTxt);
335
        $this->workspacesCache->set(
336
            $hash,
337
            $this->dataArray,
338
            [
339
                (string)$this->currentWorkspace,
340
                'user_' . $this->getBackendUser()->user['uid']
341
            ]
342
        );
343
    }
344
345
    /**
346
     * Checks if a cache entry is given for given versions and filter text and tries to load the data array from cache.
347
     *
348
     * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
349
     * @param string $filterTxt The given filter text from the grid.
350
     * @return bool TRUE if cache entry was successfully fetched from cache and content put to $this->dataArray
351
     */
352
    protected function getDataArrayFromCache(array $versions, $filterTxt)
353
    {
354
        $cacheEntry = false;
355
        $hash = $this->calculateHash($versions, $filterTxt);
356
        $content = $this->workspacesCache->get($hash);
357
        if ($content !== false) {
358
            $this->dataArray = $content;
359
            $cacheEntry = true;
360
        }
361
        return $cacheEntry;
362
    }
363
364
    /**
365
     * Calculates the hash value of the used workspace, the user id, the versions array, the filter text, the sorting attribute, the workspace selected in grid and the sorting direction.
366
     *
367
     * @param array $versions All records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
368
     * @param string $filterTxt The given filter text from the grid.
369
     * @return string
370
     */
371
    protected function calculateHash(array $versions, $filterTxt)
372
    {
373
        $backendUser = $this->getBackendUser();
374
        $hashArray = [
375
            $backendUser->workspace,
376
            $backendUser->user['uid'],
377
            $versions,
378
            $filterTxt,
379
            $this->sort,
380
            $this->sortDir,
381
            $this->currentWorkspace
382
        ];
383
        $hash = md5(serialize($hashArray));
384
        return $hash;
385
    }
386
387
    /**
388
     * Performs sorting on the data array accordant to the
389
     * selected column in the grid view to be used for sorting.
390
     */
391
    protected function sortDataArray()
392
    {
393
        if (is_array($this->dataArray)) {
0 ignored issues
show
introduced by
The condition is_array($this->dataArray) is always true.
Loading history...
394
            switch ($this->sort) {
395
                case 'uid':
396
                case 'change':
397
                case 'workspace_Tstamp':
398
                case 't3ver_oid':
399
                case 'liveid':
400
                case 'livepid':
401
                case 'languageValue':
402
                    uasort($this->dataArray, [$this, 'intSort']);
403
                    break;
404
                case 'label_Workspace':
405
                case 'label_Live':
406
                case 'label_Stage':
407
                case 'workspace_Title':
408
                case 'path_Live':
409
                    // case 'path_Workspace': This is the first sorting attribute
410
                    uasort($this->dataArray, [$this, 'stringSort']);
411
                    break;
412
                default:
413
                    // Do nothing
414
            }
415
        } else {
416
            $this->logger->critical('Try to sort "' . $this->sort . '" in "\\TYPO3\\CMS\\Workspaces\\Service\\GridDataService::sortDataArray" but $this->dataArray is empty! This might be the bug #26422 which could not be reproduced yet.');
417
        }
418
        // Trigger an event for extensibility
419
        $event = new SortVersionedDataEvent($this, $this->dataArray, $this->sort, $this->sortDir);
420
        $this->eventDispatcher->dispatch($event);
421
        $this->dataArray = $event->getData();
422
        $this->sort = $event->getSortColumn();
423
        $this->sortDir = $event->getSortDirection();
424
    }
425
426
    /**
427
     * Implements individual sorting for columns based on integer comparison.
428
     *
429
     * @param array $a First value
430
     * @param array $b Second value
431
     * @return int
432
     */
433
    protected function intSort(array $a, array $b)
434
    {
435
        if (!$this->isSortable($a, $b)) {
436
            return 0;
437
        }
438
        // First sort by using the page-path in current workspace
439
        $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
440
        if ($path_cmp < 0) {
441
            return $path_cmp;
442
        }
443
        if ($path_cmp == 0) {
444
            if ($a[$this->sort] == $b[$this->sort]) {
445
                return 0;
446
            }
447
            if ($this->sortDir === 'ASC') {
448
                return $a[$this->sort] < $b[$this->sort] ? -1 : 1;
449
            }
450
            if ($this->sortDir === 'DESC') {
451
                return $a[$this->sort] > $b[$this->sort] ? -1 : 1;
452
            }
453
        } elseif ($path_cmp > 0) {
454
            return $path_cmp;
455
        }
456
        return 0;
457
    }
458
459
    /**
460
     * Implements individual sorting for columns based on string comparison.
461
     *
462
     * @param array $a First value
463
     * @param array $b Second value
464
     * @return int
465
     */
466
    protected function stringSort($a, $b)
467
    {
468
        if (!$this->isSortable($a, $b)) {
469
            return 0;
470
        }
471
        $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
472
        if ($path_cmp < 0) {
473
            return $path_cmp;
474
        }
475
        if ($path_cmp == 0) {
476
            if ($a[$this->sort] == $b[$this->sort]) {
477
                return 0;
478
            }
479
            if ($this->sortDir === 'ASC') {
480
                return strcasecmp($a[$this->sort], $b[$this->sort]);
481
            }
482
            if ($this->sortDir === 'DESC') {
483
                return strcasecmp($a[$this->sort], $b[$this->sort]) * -1;
484
            }
485
        } elseif ($path_cmp > 0) {
486
            return $path_cmp;
487
        }
488
        return 0;
489
    }
490
491
    /**
492
     * Determines whether dataArray elements are sortable.
493
     * Only elements on the first level (0) or below the same
494
     * parent element are directly sortable.
495
     *
496
     * @param array $a
497
     * @param array $b
498
     * @return bool
499
     */
500
    protected function isSortable(array $a, array $b)
501
    {
502
        return
503
            $a[self::GridColumn_CollectionLevel] === 0 && $b[self::GridColumn_CollectionLevel] === 0
504
            || $a[self::GridColumn_CollectionParent] === $b[self::GridColumn_CollectionParent]
505
        ;
506
    }
507
508
    /**
509
     * Determines whether the text used to filter the results is part of
510
     * a column that is visible in the grid view.
511
     *
512
     * @param string $filterText
513
     * @param array $versionArray
514
     * @return bool
515
     */
516
    protected function isFilterTextInVisibleColumns($filterText, array $versionArray)
517
    {
518
        $backendUser = $this->getBackendUser();
519
        if (is_array($backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'])) {
520
            $visibleColumns = $backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'];
521
        } else {
522
            $visibleColumns = [
523
                'workspace_Formated_Tstamp' => ['hidden' => 0],
524
                'change' => ['hidden' => 0],
525
                'path_Workspace' => ['hidden' => 0],
526
                'path_Live' => ['hidden' => 0],
527
                'label_Live' => ['hidden' => 0],
528
                'label_Stage' => ['hidden' => 0],
529
                'label_Workspace' => ['hidden' => 0],
530
            ];
531
        }
532
        foreach ($visibleColumns as $column => $value) {
533
            if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
534
                if ($value['hidden'] == 0) {
535
                    switch ($column) {
536
                        case 'workspace_Tstamp':
537
                            if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== false) {
538
                                return true;
539
                            }
540
                            break;
541
                        case 'change':
542
                            if (stripos((string)$versionArray[$column], str_replace('%', '', $filterText)) !== false) {
543
                                return true;
544
                            }
545
                            break;
546
                        default:
547
                            if (stripos((string)$versionArray[$column], $filterText) !== false) {
548
                                return true;
549
                            }
550
                    }
551
                }
552
            }
553
        }
554
        return false;
555
    }
556
557
    /**
558
     * Gets the state of a given state value.
559
     *
560
     * @param int $stateId stateId of offline record
561
     * @param bool $hiddenOnline hidden status of online record
562
     * @param bool $hiddenOffline hidden status of offline record
563
     * @return string
564
     */
565
    protected function workspaceState($stateId, $hiddenOnline = false, $hiddenOffline = false)
566
    {
567
        $hiddenState = null;
568
        if ($hiddenOnline == 0 && $hiddenOffline == 1) {
569
            $hiddenState = 'hidden';
570
        } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
571
            $hiddenState = 'unhidden';
572
        }
573
        switch ($stateId) {
574
            case VersionState::NEW_PLACEHOLDER:
575
                $state = 'new';
576
                break;
577
            case VersionState::DELETE_PLACEHOLDER:
578
                $state = 'deleted';
579
                break;
580
            case VersionState::MOVE_POINTER:
581
                $state = 'moved';
582
                break;
583
            default:
584
                $state = ($hiddenState ?: 'modified');
585
        }
586
        return $state;
587
    }
588
589
    /**
590
     * Gets the field name of the enable-columns as defined in $TCA.
591
     *
592
     * @param string $table Name of the table
593
     * @param string $type Type to be fetches (e.g. 'disabled', 'starttime', 'endtime', 'fe_group)
594
     * @return string|null The accordant field name or NULL if not defined
595
     */
596
    protected function getTcaEnableColumnsFieldName($table, $type)
597
    {
598
        $fieldName = null;
599
600
        if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type])) {
601
            $fieldName = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type];
602
        }
603
604
        return $fieldName;
605
    }
606
607
    /**
608
     * Gets the used language value (sys_language.uid) of
609
     * a given database record.
610
     *
611
     * @param string $table Name of the table
612
     * @param array $record Database record
613
     * @return int
614
     */
615
    protected function getLanguageValue($table, array $record)
616
    {
617
        $languageValue = 0;
618
        if (BackendUtility::isTableLocalizable($table)) {
619
            $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
620
            if (!empty($record[$languageField])) {
621
                $languageValue = $record[$languageField];
622
            }
623
        }
624
        return $languageValue;
625
    }
626
627
    /**
628
     * Gets a named value of the available sys_language elements.
629
     *
630
     * @param int $id sys_language uid
631
     * @param int $pageId page id of a site
632
     * @param string $key Name of the value to be fetched (e.g. title)
633
     * @return string|null
634
     * @see getSystemLanguages
635
     */
636
    protected function getSystemLanguageValue($id, $pageId, $key)
637
    {
638
        $value = null;
639
        $systemLanguages = $this->getSystemLanguages((int)$pageId);
640
        if (!empty($systemLanguages[$id][$key])) {
641
            $value = $systemLanguages[$id][$key];
642
        }
643
        return $value;
644
    }
645
646
    /**
647
     * Gets all available system languages.
648
     *
649
     * @param int $pageId
650
     * @return array
651
     */
652
    public function getSystemLanguages(int $pageId)
653
    {
654
        return GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages($pageId);
655
    }
656
657
    /**
658
     * Gets an instance of the integrity service.
659
     *
660
     * @return IntegrityService
661
     */
662
    protected function getIntegrityService()
663
    {
664
        if (!isset($this->integrityService)) {
665
            $this->integrityService = GeneralUtility::makeInstance(IntegrityService::class);
666
        }
667
        return $this->integrityService;
668
    }
669
670
    /**
671
     * @return Dependency\CollectionService
672
     */
673
    protected function getDependencyCollectionService()
674
    {
675
        return GeneralUtility::makeInstance(CollectionService::class);
676
    }
677
678
    /**
679
     * @return AdditionalColumnService
680
     */
681
    protected function getAdditionalColumnService()
682
    {
683
        return GeneralUtility::makeInstance(AdditionalColumnService::class);
684
    }
685
686
    /**
687
     * @return BackendUserAuthentication
688
     */
689
    protected function getBackendUser()
690
    {
691
        return $GLOBALS['BE_USER'];
692
    }
693
}
694