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