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