2526ee2a213926be5d506bebc8ea3edbf3945946
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Service / GridDataService.php
1 <?php
2 namespace TYPO3\CMS\Workspaces\Service;
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 Psr\Log\LoggerAwareInterface;
18 use Psr\Log\LoggerAwareTrait;
19 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Cache\CacheManager;
22 use TYPO3\CMS\Core\Imaging\Icon;
23 use TYPO3\CMS\Core\Imaging\IconFactory;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Versioning\VersionState;
26 use TYPO3\CMS\Extbase\Object\ObjectManager;
27 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
28 use TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord;
29 use TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder;
30
31 /**
32 * Grid data service
33 */
34 class GridDataService implements LoggerAwareInterface
35 {
36 use LoggerAwareTrait;
37
38 const SIGNAL_GenerateDataArray_BeforeCaching = 'generateDataArray.beforeCaching';
39 const SIGNAL_GenerateDataArray_PostProcesss = 'generateDataArray.postProcess';
40 const SIGNAL_GetDataArray_PostProcesss = 'getDataArray.postProcess';
41 const SIGNAL_SortDataArray_PostProcesss = 'sortDataArray.postProcess';
42
43 const GridColumn_Collection = 'Workspaces_Collection';
44 const GridColumn_CollectionLevel = 'Workspaces_CollectionLevel';
45 const GridColumn_CollectionParent = 'Workspaces_CollectionParent';
46 const GridColumn_CollectionCurrent = 'Workspaces_CollectionCurrent';
47 const GridColumn_CollectionChildren = 'Workspaces_CollectionChildren';
48
49 /**
50 * Id of the current active workspace.
51 *
52 * @var int
53 */
54 protected $currentWorkspace;
55
56 /**
57 * Version record information (filtered, sorted and limited)
58 *
59 * @var array
60 */
61 protected $dataArray = [];
62
63 /**
64 * Name of the field used for sorting.
65 *
66 * @var string
67 */
68 protected $sort = '';
69
70 /**
71 * Direction used for sorting (ASC, DESC).
72 *
73 * @var string
74 */
75 protected $sortDir = '';
76
77 /**
78 * @var \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
79 */
80 protected $workspacesCache;
81
82 /**
83 * @var array
84 */
85 protected $systemLanguages;
86
87 /**
88 * @var IntegrityService
89 */
90 protected $integrityService;
91
92 /**
93 * Generates grid list array from given versions.
94 *
95 * @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
96 * @param \stdClass $parameter Parameters as submitted by JavaScript component
97 * @param int $currentWorkspace The current workspace
98 * @return array Version record information (filtered, sorted and limited)
99 * @throws \InvalidArgumentException
100 */
101 public function generateGridListFromVersions($versions, $parameter, $currentWorkspace)
102 {
103 // Read the given parameters from grid. If the parameter is not set use default values.
104 $filterTxt = $parameter->filterTxt ?? '';
105 $start = isset($parameter->start) ? (int)$parameter->start : 0;
106 $limit = isset($parameter->limit) ? (int)$parameter->limit : 30;
107 $this->sort = $parameter->sort ?? 't3ver_oid';
108 $this->sortDir = $parameter->dir ?? 'ASC';
109 if (is_int($currentWorkspace)) {
110 $this->currentWorkspace = $currentWorkspace;
111 } else {
112 throw new \InvalidArgumentException('No such workspace defined', 1476048304);
113 }
114 $data = [];
115 $data['data'] = [];
116 $this->generateDataArray($versions, $filterTxt);
117 $data['total'] = count($this->dataArray);
118 $data['data'] = $this->getDataArray($start, $limit);
119 return $data;
120 }
121
122 /**
123 * Generates grid list array from given versions.
124 *
125 * @param array $versions All available version records
126 * @param string $filterTxt Text to be used to filter record result
127 */
128 protected function generateDataArray(array $versions, $filterTxt)
129 {
130 $workspaceAccess = $GLOBALS['BE_USER']->checkWorkspace($GLOBALS['BE_USER']->workspace);
131 $swapStage = $workspaceAccess['publish_access'] & 1 ? StagesService::STAGE_PUBLISH_ID : 0;
132 $swapAccess = $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace) && $GLOBALS['BE_USER']->workspaceSwapAccess();
133 $this->initializeWorkspacesCachingFramework();
134 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
135 // check for dataArray in cache
136 if ($this->getDataArrayFromCache($versions, $filterTxt) === false) {
137 $stagesObj = GeneralUtility::makeInstance(StagesService::class);
138 $defaultGridColumns = [
139 self::GridColumn_Collection => 0,
140 self::GridColumn_CollectionLevel => 0,
141 self::GridColumn_CollectionParent => '',
142 self::GridColumn_CollectionCurrent => '',
143 self::GridColumn_CollectionChildren => 0,
144 ];
145 foreach ($versions as $table => $records) {
146 $hiddenField = $this->getTcaEnableColumnsFieldName($table, 'disabled');
147 $isRecordTypeAllowedToModify = $GLOBALS['BE_USER']->check('tables_modify', $table);
148
149 foreach ($records as $record) {
150 $origRecord = BackendUtility::getRecord($table, $record['t3ver_oid']);
151 $versionRecord = BackendUtility::getRecord($table, $record['uid']);
152 $combinedRecord = CombinedRecord::createFromArrays($table, $origRecord, $versionRecord);
153 $this->getIntegrityService()->checkElement($combinedRecord);
154
155 if ($hiddenField !== null) {
156 $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord[$hiddenField], $versionRecord[$hiddenField]);
157 } else {
158 $recordState = $this->workspaceState($versionRecord['t3ver_state']);
159 }
160
161 $isDeletedPage = $table === 'pages' && $recordState === 'deleted';
162 $pageId = $table === 'pages' ? $record['uid'] : $record['pid'];
163 $viewUrl = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, $record['uid'], $origRecord, $versionRecord);
164 $versionArray = $defaultGridColumns;
165 $versionArray['table'] = $table;
166 $versionArray['id'] = $table . ':' . $record['uid'];
167 $versionArray['uid'] = $record['uid'];
168 $versionArray['label_Workspace'] = htmlspecialchars(BackendUtility::getRecordTitle($table, $versionRecord));
169 $versionArray['label_Live'] = htmlspecialchars(BackendUtility::getRecordTitle($table, $origRecord));
170 $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle($versionRecord['t3ver_stage']));
171 $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
172 $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
173 $versionArray['value_nextStage'] = (int)$tempStage['uid'];
174 $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
175 $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle($tempStage['uid']));
176 $versionArray['value_prevStage'] = (int)$tempStage['uid'];
177 $versionArray['path_Live'] = htmlspecialchars(BackendUtility::getRecordPath($record['livepid'], '', 999));
178 $versionArray['path_Workspace'] = htmlspecialchars(BackendUtility::getRecordPath($record['wspid'], '', 999));
179 $versionArray['workspace_Title'] = htmlspecialchars(WorkspaceService::getWorkspaceTitle($versionRecord['t3ver_wsid']));
180 $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
181 $versionArray['workspace_Formated_Tstamp'] = BackendUtility::datetime($versionRecord['tstamp']);
182 $versionArray['t3ver_wsid'] = $versionRecord['t3ver_wsid'];
183 $versionArray['t3ver_oid'] = $record['t3ver_oid'];
184 $versionArray['livepid'] = $record['livepid'];
185 $versionArray['stage'] = $versionRecord['t3ver_stage'];
186 $versionArray['icon_Live'] = $iconFactory->getIconForRecord($table, $origRecord, Icon::SIZE_SMALL)->render();
187 $versionArray['icon_Workspace'] = $iconFactory->getIconForRecord($table, $versionRecord, Icon::SIZE_SMALL)->render();
188 $languageValue = $this->getLanguageValue($table, $versionRecord);
189 $versionArray['languageValue'] = $languageValue;
190 $versionArray['language'] = [
191 'icon' => $iconFactory->getIcon($this->getSystemLanguageValue($languageValue, $pageId, 'flagIcon'), Icon::SIZE_SMALL)->render()
192 ];
193 $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
194 $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
195 if ($swapAccess && $swapStage != 0 && $versionRecord['t3ver_stage'] == $swapStage) {
196 $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
197 } elseif ($swapAccess && $swapStage == 0) {
198 $versionArray['allowedAction_swap'] = $isRecordTypeAllowedToModify;
199 } else {
200 $versionArray['allowedAction_swap'] = false;
201 }
202 $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
203 // preview and editing of a deleted page won't work ;)
204 $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
205 $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
206 $versionArray['allowedAction_editVersionedPage'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
207 $versionArray['state_Workspace'] = $recordState;
208
209 $versionArray = array_merge(
210 $versionArray,
211 $this->getAdditionalColumnService()->getData($combinedRecord)
212 );
213
214 if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
215 $versionIdentifier = $versionArray['id'];
216 $this->dataArray[$versionIdentifier] = $versionArray;
217 }
218 }
219 }
220 // Suggested slot method:
221 // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, array $versions)
222 list($this->dataArray, $versions) = $this->emitSignal(self::SIGNAL_GenerateDataArray_BeforeCaching, $this->dataArray, $versions);
223 // Enrich elements after everything has been processed:
224 foreach ($this->dataArray as &$element) {
225 $identifier = $element['table'] . ':' . $element['t3ver_oid'];
226 $element['integrity'] = [
227 'status' => $this->getIntegrityService()->getStatusRepresentation($identifier),
228 'messages' => htmlspecialchars($this->getIntegrityService()->getIssueMessages($identifier, true))
229 ];
230 }
231 $this->setDataArrayIntoCache($versions, $filterTxt);
232 }
233 // Suggested slot method:
234 // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, array $versions)
235 list($this->dataArray) = $this->emitSignal(self::SIGNAL_GenerateDataArray_PostProcesss, $this->dataArray, $versions);
236 $this->sortDataArray();
237 $this->resolveDataArrayDependencies();
238 }
239
240 /**
241 * Resolves dependencies of nested structures
242 * and sort data elements considering these dependencies.
243 */
244 protected function resolveDataArrayDependencies()
245 {
246 $collectionService = $this->getDependencyCollectionService();
247 $dependencyResolver = $collectionService->getDependencyResolver();
248
249 foreach ($this->dataArray as $dataElement) {
250 $dependencyResolver->addElement($dataElement['table'], $dataElement['uid']);
251 }
252
253 $this->dataArray = $collectionService->process($this->dataArray);
254 }
255
256 /**
257 * Gets the data array by considering the page to be shown in the grid view.
258 *
259 * @param int $start
260 * @param int $limit
261 * @return array
262 */
263 protected function getDataArray($start, $limit)
264 {
265 $dataArrayPart = [];
266 $dataArrayCount = count($this->dataArray);
267 $end = ($start + $limit < $dataArrayCount ? $start + $limit : $dataArrayCount);
268
269 // Ensure that there are numerical indexes
270 $this->dataArray = array_values($this->dataArray);
271 for ($i = $start; $i < $end; $i++) {
272 $dataArrayPart[] = $this->dataArray[$i];
273 }
274
275 // Ensure that collections are not cut for the pagination
276 if (!empty($this->dataArray[$i][self::GridColumn_Collection])) {
277 $collectionIdentifier = $this->dataArray[$i][self::GridColumn_Collection];
278 for ($i = $i + 1; $i < $dataArrayCount && $collectionIdentifier === $this->dataArray[$i][self::GridColumn_Collection]; $i++) {
279 $dataArrayPart[] = $this->dataArray[$i];
280 }
281 }
282
283 // Suggested slot method:
284 // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, $start, $limit, array $dataArrayPart)
285 list($this->dataArray, $start, $limit, $dataArrayPart) = $this->emitSignal(self::SIGNAL_GetDataArray_PostProcesss, $this->dataArray, $start, $limit, $dataArrayPart);
286 return $dataArrayPart;
287 }
288
289 /**
290 * Initializes the workspace cache
291 */
292 protected function initializeWorkspacesCachingFramework()
293 {
294 $this->workspacesCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('workspaces_cache');
295 }
296
297 /**
298 * Puts the generated dataArray into the workspace cache.
299 *
300 * @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
301 * @param string $filterTxt The given filter text from the grid.
302 */
303 protected function setDataArrayIntoCache(array $versions, $filterTxt)
304 {
305 $hash = $this->calculateHash($versions, $filterTxt);
306 $this->workspacesCache->set($hash, $this->dataArray, [(string)$this->currentWorkspace, 'user_' . $GLOBALS['BE_USER']->user['uid']]);
307 }
308
309 /**
310 * Checks if a cache entry is given for given versions and filter text and tries to load the data array from cache.
311 *
312 * @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
313 * @param string $filterTxt The given filter text from the grid.
314 * @return bool TRUE if cache entry was successfully fetched from cache and content put to $this->dataArray
315 */
316 protected function getDataArrayFromCache(array $versions, $filterTxt)
317 {
318 $cacheEntry = false;
319 $hash = $this->calculateHash($versions, $filterTxt);
320 $content = $this->workspacesCache->get($hash);
321 if ($content !== false) {
322 $this->dataArray = $content;
323 $cacheEntry = true;
324 }
325 return $cacheEntry;
326 }
327
328 /**
329 * 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.
330 *
331 * @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
332 * @param string $filterTxt The given filter text from the grid.
333 * @return string
334 */
335 protected function calculateHash(array $versions, $filterTxt)
336 {
337 $hashArray = [
338 $GLOBALS['BE_USER']->workspace,
339 $GLOBALS['BE_USER']->user['uid'],
340 $versions,
341 $filterTxt,
342 $this->sort,
343 $this->sortDir,
344 $this->currentWorkspace
345 ];
346 $hash = md5(serialize($hashArray));
347 return $hash;
348 }
349
350 /**
351 * Performs sorting on the data array accordant to the
352 * selected column in the grid view to be used for sorting.
353 */
354 protected function sortDataArray()
355 {
356 if (is_array($this->dataArray)) {
357 switch ($this->sort) {
358 case 'uid':
359 case 'change':
360 case 'workspace_Tstamp':
361 case 't3ver_oid':
362 case 'liveid':
363 case 'livepid':
364 case 'languageValue':
365 uasort($this->dataArray, [$this, 'intSort']);
366 break;
367 case 'label_Workspace':
368 case 'label_Live':
369 case 'label_Stage':
370 case 'workspace_Title':
371 case 'path_Live':
372 // case 'path_Workspace': This is the first sorting attribute
373 uasort($this->dataArray, [$this, 'stringSort']);
374 break;
375 default:
376 // Do nothing
377 }
378 } else {
379 $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.');
380 }
381 // Suggested slot method:
382 // methodName(\TYPO3\CMS\Workspaces\Service\GridDataService $gridData, array $dataArray, $sortColumn, $sortDirection)
383 list($this->dataArray, $this->sort, $this->sortDir) = $this->emitSignal(self::SIGNAL_SortDataArray_PostProcesss, $this->dataArray, $this->sort, $this->sortDir);
384 }
385
386 /**
387 * Implements individual sorting for columns based on integer comparison.
388 *
389 * @param array $a First value
390 * @param array $b Second value
391 * @return int
392 */
393 protected function intSort(array $a, array $b)
394 {
395 if (!$this->isSortable($a, $b)) {
396 return 0;
397 }
398 // First sort by using the page-path in current workspace
399 $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
400 if ($path_cmp < 0) {
401 return $path_cmp;
402 }
403 if ($path_cmp == 0) {
404 if ($a[$this->sort] == $b[$this->sort]) {
405 return 0;
406 }
407 if ($this->sortDir === 'ASC') {
408 return $a[$this->sort] < $b[$this->sort] ? -1 : 1;
409 }
410 if ($this->sortDir === 'DESC') {
411 return $a[$this->sort] > $b[$this->sort] ? -1 : 1;
412 }
413 } elseif ($path_cmp > 0) {
414 return $path_cmp;
415 }
416 return 0;
417 }
418
419 /**
420 * Implements individual sorting for columns based on string comparison.
421 *
422 * @param string $a First value
423 * @param string $b Second value
424 * @return int
425 */
426 protected function stringSort($a, $b)
427 {
428 if (!$this->isSortable($a, $b)) {
429 return 0;
430 }
431 $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
432 if ($path_cmp < 0) {
433 return $path_cmp;
434 }
435 if ($path_cmp == 0) {
436 if ($a[$this->sort] == $b[$this->sort]) {
437 return 0;
438 }
439 if ($this->sortDir === 'ASC') {
440 return strcasecmp($a[$this->sort], $b[$this->sort]);
441 }
442 if ($this->sortDir === 'DESC') {
443 return strcasecmp($a[$this->sort], $b[$this->sort]) * -1;
444 }
445 } elseif ($path_cmp > 0) {
446 return $path_cmp;
447 }
448 return 0;
449 }
450
451 /**
452 * Determines whether dataArray elements are sortable.
453 * Only elements on the first level (0) or below the same
454 * parent element are directly sortable.
455 *
456 * @param array $a
457 * @param array $b
458 * @return bool
459 */
460 protected function isSortable(array $a, array $b)
461 {
462 return
463 $a[self::GridColumn_CollectionLevel] === 0 && $b[self::GridColumn_CollectionLevel] === 0
464 || $a[self::GridColumn_CollectionParent] === $b[self::GridColumn_CollectionParent]
465 ;
466 }
467
468 /**
469 * Determines whether the text used to filter the results is part of
470 * a column that is visible in the grid view.
471 *
472 * @param string $filterText
473 * @param array $versionArray
474 * @return bool
475 */
476 protected function isFilterTextInVisibleColumns($filterText, array $versionArray)
477 {
478 if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'])) {
479 $visibleColumns = $GLOBALS['BE_USER']->uc['moduleData']['Workspaces'][$GLOBALS['BE_USER']->workspace]['columns'];
480 } else {
481 $visibleColumns = [
482 'workspace_Formated_Tstamp' => ['hidden' => 0],
483 'change' => ['hidden' => 0],
484 'path_Workspace' => ['hidden' => 0],
485 'path_Live' => ['hidden' => 0],
486 'label_Live' => ['hidden' => 0],
487 'label_Stage' => ['hidden' => 0],
488 'label_Workspace' => ['hidden' => 0],
489 ];
490 }
491 foreach ($visibleColumns as $column => $value) {
492 if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
493 if ($value['hidden'] == 0) {
494 switch ($column) {
495 case 'workspace_Tstamp':
496 if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== false) {
497 return true;
498 }
499 break;
500 case 'change':
501 if (stripos((string)$versionArray[$column], str_replace('%', '', $filterText)) !== false) {
502 return true;
503 }
504 break;
505 default:
506 if (stripos((string)$versionArray[$column], $filterText) !== false) {
507 return true;
508 }
509 }
510 }
511 }
512 }
513 return false;
514 }
515
516 /**
517 * Gets the state of a given state value.
518 *
519 * @param int $stateId stateId of offline record
520 * @param bool $hiddenOnline hidden status of online record
521 * @param bool $hiddenOffline hidden status of offline record
522 * @return string
523 */
524 protected function workspaceState($stateId, $hiddenOnline = false, $hiddenOffline = false)
525 {
526 $hiddenState = null;
527 if ($hiddenOnline == 0 && $hiddenOffline == 1) {
528 $hiddenState = 'hidden';
529 } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
530 $hiddenState = 'unhidden';
531 }
532 switch ($stateId) {
533 case VersionState::NEW_PLACEHOLDER_VERSION:
534 $state = 'new';
535 break;
536 case VersionState::DELETE_PLACEHOLDER:
537 $state = 'deleted';
538 break;
539 case VersionState::MOVE_POINTER:
540 $state = 'moved';
541 break;
542 default:
543 $state = ($hiddenState ?: 'modified');
544 }
545 return $state;
546 }
547
548 /**
549 * Gets the field name of the enable-columns as defined in $TCA.
550 *
551 * @param string $table Name of the table
552 * @param string $type Type to be fetches (e.g. 'disabled', 'starttime', 'endtime', 'fe_group)
553 * @return string|null The accordant field name or NULL if not defined
554 */
555 protected function getTcaEnableColumnsFieldName($table, $type)
556 {
557 $fieldName = null;
558
559 if (!empty($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type])) {
560 $fieldName = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type];
561 }
562
563 return $fieldName;
564 }
565
566 /**
567 * Gets the used language value (sys_language.uid) of
568 * a given database record.
569 *
570 * @param string $table Name of the table
571 * @param array $record Database record
572 * @return int
573 */
574 protected function getLanguageValue($table, array $record)
575 {
576 $languageValue = 0;
577 if (BackendUtility::isTableLocalizable($table)) {
578 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
579 if (!empty($record[$languageField])) {
580 $languageValue = $record[$languageField];
581 }
582 }
583 return $languageValue;
584 }
585
586 /**
587 * Gets a named value of the available sys_language elements.
588 *
589 * @param int $id sys_language uid
590 * @param int $pageId page id of a site
591 * @param string $key Name of the value to be fetched (e.g. title)
592 * @return string|null
593 * @see getSystemLanguages
594 */
595 protected function getSystemLanguageValue($id, $pageId, $key)
596 {
597 $value = null;
598 $systemLanguages = $this->getSystemLanguages($pageId);
599 if (!empty($systemLanguages[$id][$key])) {
600 $value = $systemLanguages[$id][$key];
601 }
602 return $value;
603 }
604
605 /**
606 * Gets all available system languages.
607 *
608 * @param int $pageId
609 * @return array
610 */
611 public function getSystemLanguages(int $pageId)
612 {
613 if (!isset($this->systemLanguages)) {
614 $translateTools = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
615 $this->systemLanguages = $translateTools->getSystemLanguages($pageId);
616 }
617 return $this->systemLanguages;
618 }
619
620 /**
621 * Gets an instance of the integrity service.
622 *
623 * @return IntegrityService
624 */
625 protected function getIntegrityService()
626 {
627 if (!isset($this->integrityService)) {
628 $this->integrityService = GeneralUtility::makeInstance(IntegrityService::class);
629 }
630 return $this->integrityService;
631 }
632
633 /**
634 * Emits a signal to be handled by any registered slots.
635 *
636 * @param string $signalName Name of the signal
637 * @param array|mixed[] $arguments
638 * @return array
639 */
640 protected function emitSignal($signalName, ...$arguments)
641 {
642 // Arguments are always ($this, [method argument], [method argument], ...)
643 $signalArguments = $arguments;
644 array_unshift($signalArguments, $this);
645 $slotReturn = $this->getSignalSlotDispatcher()->dispatch(GridDataService::class, $signalName, $signalArguments);
646 return array_slice($slotReturn, 1);
647 }
648
649 /**
650 * @return Dependency\CollectionService
651 */
652 protected function getDependencyCollectionService()
653 {
654 return GeneralUtility::makeInstance(Dependency\CollectionService::class);
655 }
656
657 /**
658 * @return AdditionalColumnService
659 */
660 protected function getAdditionalColumnService()
661 {
662 return $this->getObjectManager()->get(AdditionalColumnService::class);
663 }
664
665 /**
666 * @return Dispatcher
667 */
668 protected function getSignalSlotDispatcher()
669 {
670 return $this->getObjectManager()->get(Dispatcher::class);
671 }
672
673 /**
674 * @return ObjectManager
675 */
676 protected function getObjectManager()
677 {
678 return GeneralUtility::makeInstance(ObjectManager::class);
679 }
680 }