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