‪TYPO3CMS  ‪main
GridDataService.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Psr\Http\Message\ServerRequestInterface;
22 use Psr\Log\LoggerAwareInterface;
23 use Psr\Log\LoggerAwareTrait;
26 use TYPO3\CMS\Backend\Utility\BackendUtility;
31 use TYPO3\CMS\Core\Imaging\IconSize;
43 
47 class ‪GridDataService implements LoggerAwareInterface
48 {
49  use LoggerAwareTrait;
50 
51  public const ‪GridColumn_Collection = 'Workspaces_Collection';
52  public const ‪GridColumn_CollectionLevel = 'Workspaces_CollectionLevel';
53  public const ‪GridColumn_CollectionParent = 'Workspaces_CollectionParent';
54  public const ‪GridColumn_CollectionCurrent = 'Workspaces_CollectionCurrent';
55  public const ‪GridColumn_CollectionChildren = 'Workspaces_CollectionChildren';
56 
60  protected int ‪$currentWorkspace = 0;
61 
65  protected array ‪$dataArray = [];
66 
70  protected string ‪$sort = '';
71 
75  protected string ‪$sortDir = '';
76 
79 
80  public function ‪__construct(
81  private readonly EventDispatcherInterface $eventDispatcher,
82  private readonly ‪WorkspaceService $workspaceService,
83  private readonly ‪ModuleProvider $moduleProvider,
84  private readonly ‪WorkspacePublishGate $workspacePublishGate,
85  ) {}
86 
95  public function ‪generateGridListFromVersions(array $versions, \stdClass $parameter, int ‪$currentWorkspace, ServerRequestInterface $request): array
96  {
97  // Read the given parameters from grid. If the parameter is not set use default values.
98  $filterTxt = $parameter->filterTxt ?? '';
99  $start = isset($parameter->start) ? (int)$parameter->start : 0;
100  $limit = isset($parameter->limit) ? (int)$parameter->limit : 30;
101  $this->sort = $parameter->sort ?? 't3ver_oid';
102  $this->sortDir = $parameter->dir ?? 'ASC';
103  $this->currentWorkspace = ‪$currentWorkspace;
104  $this->generateDataArray($versions, $filterTxt, $request);
105  return [
106  // Only count parent records for pagination
107  'total' => count(array_filter($this->dataArray, static function ($element) {
108  return (int)($element[self::GridColumn_CollectionLevel] ?? 0) === 0;
109  })),
110  'data' => $this->‪getDataArray($start, $limit),
111  ];
112  }
113 
120  protected function ‪generateDataArray(array $versions, string $filterTxt, ServerRequestInterface $request): void
121  {
122  $backendUser = $this->‪getBackendUser();
123  $workspaceAccess = $backendUser->checkWorkspace($backendUser->workspace);
125 
126  $isAllowedToPublish = $this->workspacePublishGate->isGranted($backendUser, $backendUser->workspace);
128  $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
129  // check for dataArray in cache
130  if ($this->‪getDataArrayFromCache($versions, $filterTxt) === false) {
131  $stagesObj = GeneralUtility::makeInstance(StagesService::class);
132  $defaultGridColumns = [
133  self::GridColumn_Collection => 0,
134  self::GridColumn_CollectionLevel => 0,
135  self::GridColumn_CollectionParent => '',
136  self::GridColumn_CollectionCurrent => '',
137  self::GridColumn_CollectionChildren => 0,
138  ];
139  foreach ($versions as $table => $records) {
140  $table = (string)$table;
141  $hiddenField = $this->‪getTcaEnableColumnsFieldName($table, 'disabled');
142  $isRecordTypeAllowedToModify = $backendUser->check('tables_modify', $table);
143 
144  foreach ($records as ‪$record) {
145  $origRecord = (array)BackendUtility::getRecord($table, ‪$record['t3ver_oid']);
146  $versionRecord = (array)BackendUtility::getRecord($table, ‪$record['uid']);
147  $combinedRecord = ‪CombinedRecord::createFromArrays($table, $origRecord, $versionRecord);
148  $hasDiff = $this->‪versionIsModified($combinedRecord, $request);
149  $this->‪getIntegrityService()->checkElement($combinedRecord);
150 
151  if ($hiddenField !== null) {
152  $recordState = $this->‪workspaceState($versionRecord['t3ver_state'], (bool)$origRecord[$hiddenField], (bool)$versionRecord[$hiddenField], $hasDiff);
153  } else {
154  $recordState = $this->‪workspaceState($versionRecord['t3ver_state'], $hasDiff);
155  }
156 
157  $isDeletedPage = $table === 'pages' && $recordState === 'deleted';
158  $pageId = (int)(‪$record['pid'] ?? null);
159  if ($table === 'pages') {
160  // The page ID for a translated page is considered here
161  $pageId = (int)(!empty(‪$record['l10n_parent']) ? ‪$record['l10n_parent'] : (‪$record['t3ver_oid'] ?: ‪$record['uid']));
162  }
163  $viewUrl = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, (int)‪$record['uid'], $origRecord, $versionRecord);
164  $workspaceRecordLabel = BackendUtility::getRecordTitle($table, $versionRecord);
165  $liveRecordLabel = BackendUtility::getRecordTitle($table, $origRecord);
166  $iconLive = $iconFactory->getIconForRecord($table, $origRecord, IconSize::SMALL);
167  $iconWorkspace = $iconFactory->getIconForRecord($table, $versionRecord, IconSize::SMALL);
168  [$pathWorkspaceCropped, $pathWorkspace] = BackendUtility::getRecordPath((int)‪$record['wspid'], '', 15, 1000);
169  $calculatedT3verOid = ‪$record['t3ver_oid'];
170  if (VersionState::tryFrom(‪$record['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER) {
171  // If we're dealing with a 'new' record, this one has no t3ver_oid. On publish, there is no
172  // live counterpart, but the publish methods later need a live uid to publish to. We thus
173  // use the uid as t3ver_oid here to be transparent on javascript side.
174  $calculatedT3verOid = ‪$record['uid'];
175  }
176 
177  $versionArray = [];
178  $versionArray['table'] = $table;
179  $versionArray['id'] = $table . ':' . ‪$record['uid'];
180  $versionArray['uid'] = ‪$record['uid'];
181  $versionArray = array_merge($versionArray, $defaultGridColumns);
182  $versionArray['label_Workspace'] = htmlspecialchars($workspaceRecordLabel);
183  $versionArray['label_Workspace_crop'] = htmlspecialchars(‪GeneralUtility::fixed_lgd_cs($workspaceRecordLabel, (int)$backendUser->uc['titleLen']));
184  $versionArray['label_Live'] = htmlspecialchars($liveRecordLabel);
185  $versionArray['label_Live_crop'] = htmlspecialchars(‪GeneralUtility::fixed_lgd_cs($liveRecordLabel, (int)$backendUser->uc['titleLen']));
186  $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle((int)$versionRecord['t3ver_stage']));
187  $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
188  $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle((int)$tempStage['uid']));
189  $versionArray['value_nextStage'] = (int)$tempStage['uid'];
190  $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
191  $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle((int)($tempStage['uid'] ?? 0)));
192  $versionArray['value_prevStage'] = (int)($tempStage['uid'] ?? 0);
193  $versionArray['path_Live'] = htmlspecialchars(BackendUtility::getRecordPath(‪$record['livepid'], '', 999));
194  $versionArray['path_Workspace'] = htmlspecialchars($pathWorkspace);
195  $versionArray['path_Workspace_crop'] = htmlspecialchars($pathWorkspaceCropped);
196  $versionArray['workspace_Title'] = htmlspecialchars($this->workspaceService->getWorkspaceTitle((int)$versionRecord['t3ver_wsid']));
197  $versionArray['workspace_Tstamp'] = 0;
198  $versionArray['lastChangedFormatted'] = '';
199  if (array_key_exists('tstamp', $versionRecord)) {
200  // @todo: Avoid hard coded access to 'tstamp' and use table TCA 'ctrl' 'tstamp' value instead, if set.
201  $versionArray['workspace_Tstamp'] = (int)$versionRecord['tstamp'];
202  $versionArray['lastChangedFormatted'] = BackendUtility::datetime((int)$versionRecord['tstamp']);
203  }
204  $versionArray['t3ver_wsid'] = $versionRecord['t3ver_wsid'];
205  $versionArray['t3ver_oid'] = $calculatedT3verOid;
206  $versionArray['livepid'] = ‪$record['livepid'];
207  $versionArray['stage'] = $versionRecord['t3ver_stage'];
208  $versionArray['icon_Live'] = $iconLive->getIdentifier();
209  $versionArray['icon_Live_Overlay'] = $iconLive->getOverlayIcon()?->getIdentifier() ?? '';
210  $versionArray['icon_Workspace'] = $iconWorkspace->getIdentifier();
211  $versionArray['icon_Workspace_Overlay'] = $iconWorkspace->getOverlayIcon()?->getIdentifier() ?? '';
212  $languageValue = $this->‪getLanguageValue($table, $versionRecord);
213  $versionArray['languageValue'] = $languageValue;
214  $versionArray['language'] = [
215  'icon' => $iconFactory->getIcon($this->‪getSystemLanguageValue($languageValue, $pageId, 'flagIcon'), IconSize::SMALL)->getIdentifier(),
216  ];
217  $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
218  $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
219  if ($isAllowedToPublish && $swapStage !== ‪StagesService::STAGE_EDIT_ID && (int)$versionRecord['t3ver_stage'] === $swapStage) {
220  $versionArray['allowedAction_publish'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
221  } elseif ($isAllowedToPublish && $swapStage === ‪StagesService::STAGE_EDIT_ID) {
222  $versionArray['allowedAction_publish'] = $isRecordTypeAllowedToModify;
223  } else {
224  $versionArray['allowedAction_publish'] = false;
225  }
226  $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
227  // preview and editing of a deleted page won't work ;)
228  $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
229  $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
230  $versionArray['allowedAction_versionPageOpen'] = $this->‪isPageModuleAllowed() && !$isDeletedPage;
231  $versionArray['state_Workspace'] = $recordState;
232  $versionArray['hasChanges'] = $recordState !== 'unchanged';
233  // Allows to be overridden by PSR-14 event to dynamically modify the expand / collapse state
234  $versionArray['expanded'] = false;
235 
236  if ($filterTxt == '' || $this->‪isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
237  $versionIdentifier = $versionArray['id'];
238  $this->dataArray[$versionIdentifier] = $versionArray;
239  }
240  }
241  }
242 
243  // Trigger a PSR-14 event
244  $event = new ‪AfterCompiledCacheableDataForWorkspaceEvent($this, $this->dataArray, $versions);
245  $this->eventDispatcher->dispatch($event);
246  $this->dataArray = $event->getData();
247  $versions = $event->getVersions();
248  // Enrich elements after everything has been processed:
249  foreach ($this->dataArray as &$element) {
250  ‪$identifier = $element['table'] . ':' . $element['t3ver_oid'];
251  $messages = $this->‪getIntegrityService()->getIssueMessages(‪$identifier);
252  $element['integrity'] = [
253  'status' => $this->‪getIntegrityService()->getStatusRepresentation(‪$identifier),
254  'messages' => htmlspecialchars(implode('<br>', $messages)),
255  ];
256  }
257  $this->‪setDataArrayIntoCache($versions, $filterTxt);
258  }
259 
260  // Trigger a PSR-14 event
261  $event = new ‪AfterDataGeneratedForWorkspaceEvent($this, $this->dataArray, $versions);
262  $this->eventDispatcher->dispatch($event);
263  $this->dataArray = $event->getData();
264  $this->‪sortDataArray();
266  }
267 
268  protected function ‪versionIsModified(‪CombinedRecord $combinedRecord, ServerRequestInterface $request): bool
269  {
270  $remoteServer = GeneralUtility::makeInstance(RemoteServer::class);
271 
272  $params = new \stdClass();
273  $params->stage = (int)$combinedRecord->‪getVersionRecord()->getRow()['t3ver_stage'];
274  $params->t3ver_oid = $combinedRecord->‪getLiveRecord()->getUid();
275  $params->table = $combinedRecord->‪getLiveRecord()->getTable();
276  $params->uid = $combinedRecord->‪getVersionRecord()->getUid();
277 
278  $result = $remoteServer->getRowDetails($params, $request);
279  return !empty($result['data'][0]['diff']);
280  }
281 
286  protected function ‪resolveDataArrayDependencies(): void
287  {
288  $collectionService = $this->‪getDependencyCollectionService();
289  $dependencyResolver = $collectionService->getDependencyResolver();
290 
291  foreach ($this->dataArray as $dataElement) {
292  $dependencyResolver->addElement($dataElement['table'], $dataElement['uid']);
293  }
294 
295  $this->dataArray = $collectionService->process($this->dataArray);
296  }
297 
301  protected function ‪getDataArray(int $start, int $limit): array
302  {
303  $dataArrayCount = count($this->dataArray);
304  $start = $this->‪calculateStartWithCollections($start);
305  $end = ($start + $limit < $dataArrayCount ? $start + $limit : $dataArrayCount);
306 
307  // Ensure that there are numerical indexes
308  $this->dataArray = array_values($this->dataArray);
309  // Fill the data array part
310  $dataArrayPart = $this->‪fillDataArrayPart($start, $end);
311 
312  // Trigger a PSR-14 event
313  $event = new ‪GetVersionedDataEvent($this, $this->dataArray, $start, $limit, $dataArrayPart);
314  $this->eventDispatcher->dispatch($event);
315  $this->dataArray = $event->getData();
316  return $event->getDataArrayPart();
317  }
318 
322  protected function ‪initializeWorkspacesCachingFramework(): void
323  {
324  $this->workspacesCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('workspaces_cache');
325  }
326 
333  protected function ‪setDataArrayIntoCache(array $versions, string $filterTxt): void
334  {
335  $hash = $this->‪calculateHash($versions, $filterTxt);
336  $this->workspacesCache->set(
337  $hash,
338  $this->dataArray,
339  [
340  (string)$this->currentWorkspace,
341  'user_' . $this->‪getBackendUser()->user['uid'],
342  ]
343  );
344  }
345 
353  protected function ‪getDataArrayFromCache(array $versions, string $filterTxt): bool
354  {
355  $cacheEntry = false;
356  $hash = $this->‪calculateHash($versions, $filterTxt);
357  $content = $this->workspacesCache->get($hash);
358  if (is_array($content)) {
359  $this->dataArray = $content;
360  $cacheEntry = true;
361  }
362  return $cacheEntry;
363  }
364 
371  protected function ‪calculateHash(array $versions, string $filterTxt): string
372  {
373  $backendUser = $this->‪getBackendUser();
374  $hashArray = [
375  $backendUser->workspace,
376  $backendUser->user['uid'],
377  $versions,
378  $filterTxt,
382  ];
383  $hash = md5(serialize($hashArray));
384  return $hash;
385  }
386 
391  protected function ‪sortDataArray(): void
392  {
393  switch ($this->sort) {
394  case 'uid':
395  case 'change':
396  case 'workspace_Tstamp':
397  case 't3ver_oid':
398  case 'liveid':
399  case 'livepid':
400  case 'languageValue':
401  uasort($this->dataArray, [$this, 'intSort']);
402  break;
403  case 'label_Workspace':
404  case 'label_Live':
405  case 'label_Stage':
406  case 'workspace_Title':
407  case 'path_Live':
408  // case 'path_Workspace': This is the first sorting attribute
409  uasort($this->dataArray, [$this, 'stringSort']);
410  break;
411  default:
412  // Do nothing
413  }
414 
415  // Trigger an event for extensibility
416  $event = new ‪SortVersionedDataEvent($this, $this->dataArray, $this->sort, $this->sortDir);
417  $this->eventDispatcher->dispatch($event);
418  $this->dataArray = $event->getData();
419  $this->sort = $event->getSortColumn();
420  $this->sortDir = $event->getSortDirection();
421  }
422 
426  protected function ‪intSort(array $a, array $b): int
427  {
428  if (!$this->‪isSortable($a, $b)) {
429  return 0;
430  }
431 
432  // First sort by using the page-path in current workspace
433  $pathSortingResult = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
434  if ($pathSortingResult !== 0) {
435  return $pathSortingResult;
436  }
437 
438  if ($a[$this->sort] == $b[$this->sort]) {
439  $sortingResult = 0;
440  } elseif ($this->sortDir === 'ASC') {
441  $sortingResult = $a[‪$this->sort] < $b[‪$this->sort] ? -1 : 1;
442  } elseif ($this->sortDir === 'DESC') {
443  $sortingResult = $a[‪$this->sort] > $b[‪$this->sort] ? -1 : 1;
444  } else {
445  $sortingResult = 0;
446  }
447 
448  return $sortingResult;
449  }
450 
454  protected function ‪stringSort(array $a, array $b): int
455  {
456  if (!$this->‪isSortable($a, $b)) {
457  return 0;
458  }
459 
460  // First sort by using the page-path in current workspace
461  $pathSortingResult = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
462  if ($pathSortingResult !== 0) {
463  return $pathSortingResult;
464  }
465 
466  if ($a[$this->sort] == $b[$this->sort]) {
467  $sortingResult = 0;
468  } elseif ($this->sortDir === 'ASC') {
469  $sortingResult = strcasecmp($a[$this->sort], $b[$this->sort]);
470  } elseif ($this->sortDir === 'DESC') {
471  $sortingResult = strcasecmp($a[$this->sort], $b[$this->sort]) * -1;
472  } else {
473  $sortingResult = 0;
474  }
475 
476  return $sortingResult;
477  }
478 
484  protected function ‪isSortable(array $a, array $b): bool
485  {
486  return
489  ;
490  }
491 
496  protected function ‪isPageModuleAllowed(): bool
497  {
498  return $this->moduleProvider->accessGranted('web_layout', $this->‪getBackendUser());
499  }
500 
505  protected function ‪isFilterTextInVisibleColumns(string $filterText, array $versionArray): bool
506  {
507  $backendUser = $this->‪getBackendUser();
508  if (is_array($backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'] ?? false)) {
509  $visibleColumns = $backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'];
510  } else {
511  $visibleColumns = [
512  'workspace_Formated_Tstamp' => ['hidden' => 0],
513  'change' => ['hidden' => 0],
514  'path_Workspace' => ['hidden' => 0],
515  'path_Live' => ['hidden' => 0],
516  'label_Live' => ['hidden' => 0],
517  'label_Stage' => ['hidden' => 0],
518  'label_Workspace' => ['hidden' => 0],
519  ];
520  }
521  foreach ($visibleColumns as $column => $value) {
522  if (isset($value['hidden']) && isset($versionArray[$column])) {
523  if ($value['hidden'] == 0) {
524  switch ($column) {
525  case 'workspace_Tstamp':
526  if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== false) {
527  return true;
528  }
529  break;
530  case 'change':
531  if (stripos((string)$versionArray[$column], str_replace('%', '', $filterText)) !== false) {
532  return true;
533  }
534  break;
535  default:
536  if (stripos((string)$versionArray[$column], $filterText) !== false) {
537  return true;
538  }
539  }
540  }
541  }
542  }
543  return false;
544  }
545 
554  protected function ‪workspaceState(int $stateId, bool $hiddenOnline = false, bool $hiddenOffline = false, bool $hasDiff = true): string
555  {
556  $hiddenState = null;
557  if (!$hiddenOnline && $hiddenOffline) {
558  $hiddenState = 'hidden';
559  } elseif ($hiddenOnline && !$hiddenOffline) {
560  $hiddenState = 'unhidden';
561  }
562  switch (VersionState::tryFrom($stateId)) {
563  case VersionState::NEW_PLACEHOLDER:
564  $state = 'new';
565  break;
566  case VersionState::DELETE_PLACEHOLDER:
567  $state = 'deleted';
568  break;
569  case VersionState::MOVE_POINTER:
570  $state = 'moved';
571  break;
572  default:
573  if (!$hasDiff) {
574  $state = 'unchanged';
575  } else {
576  $state = ($hiddenState ?: 'modified');
577  }
578  }
579 
580  return $state;
581  }
582 
590  protected function ‪getTcaEnableColumnsFieldName(string $table, string $type): ?string
591  {
592  $fieldName = null;
593 
594  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type])) {
595  $fieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type];
596  }
597 
598  return $fieldName;
599  }
600 
608  protected function ‪getLanguageValue(string $table, array ‪$record): int
609  {
610  $languageValue = 0;
611  if (BackendUtility::isTableLocalizable($table)) {
612  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
613  if (!empty(‪$record[$languageField])) {
614  $languageValue = (int)‪$record[$languageField];
615  }
616  }
617  return $languageValue;
618  }
619 
629  protected function ‪getSystemLanguageValue(int $id, int $pageId, string $key): ?string
630  {
631  $value = null;
632  $systemLanguages = $this->‪getSystemLanguages($pageId);
633  if (!empty($systemLanguages[$id][$key])) {
634  $value = $systemLanguages[$id][$key];
635  }
636  return $value;
637  }
638 
642  protected function ‪calculateStartWithCollections(int $start): int
643  {
644  // The recordsCount is the real record items count, while the
645  // parentRecordsCount only takes the parent records into account
646  $recordsCount = $parentRecordsCount = 0;
647  while ($parentRecordsCount < $start) {
648  // As soon as no more item exists in the dataArray, the loop needs to end
649  // prematurely to prevent invalid items which would may led to some unexpected
650  // behaviour. Note: This usually should never happen since these records must
651  // exists => As they were responsible for increasing the start value. However to
652  // prevent errors in case multiple different users manipulate the records count
653  // somehow simultaneously, we apply this check to be save.
654  if (!isset($this->dataArray[$recordsCount])) {
655  break;
656  }
657  // Loop over the dataArray until we found enough parent records
658  $item = $this->dataArray[$recordsCount];
659  if (($item[self::GridColumn_CollectionLevel] ?? 0) === 0) {
660  // In case the current parent record is the last one ($start is reached),
661  // ensure its collection children are counted as well.
662  if (($parentRecordsCount + 1) === $start && (int)($item[self::GridColumn_Collection] ?? 0) !== 0) {
663  // By not providing the third parameter, we only count the collection children recursively
664  $this->‪addCollectionChildrenRecursive($item, $recordsCount);
665  }
666  // Only increase the parent records count in case $item is a parent record
667  $parentRecordsCount++;
668  }
669  // Always increase the record items count
670  $recordsCount++;
671  }
672 
673  return $recordsCount;
674  }
675 
681  private function ‪fillDataArrayPart(int $start, int $end): array
682  {
683  // Initialize empty data array part
684  $dataArrayPart = [];
685  // The recordsCount is the real record items count, while the
686  // parentRecordsCount only takes the parent records into account.
687  $itemsCount = $parentRecordsCount = $start;
688  while ($parentRecordsCount < $end) {
689  // As soon as no more item exists in the dataArray, the loop needs to end
690  // prematurely to prevent invalid items which would trigger JavaScript errors.
691  if (!isset($this->dataArray[$itemsCount])) {
692  break;
693  }
694  // Loop over the dataArray until we found enough parent records
695  $item = $this->dataArray[$itemsCount];
696  // Add the item to the $dataArrayPart
697  $dataArrayPart[] = $item;
698  if (($item[self::GridColumn_CollectionLevel] ?? 0) === 0) {
699  // In case the current parent record is the last one ($end is reached),
700  // ensure its collection children are added as well.
701  if (($parentRecordsCount + 1) === $end && (int)($item[self::GridColumn_Collection] ?? 0) !== 0) {
702  // Add collection children recursively
703  $this->‪addCollectionChildrenRecursive($item, $itemsCount, $dataArrayPart);
704  }
705  // Only increase the parent records count in case $item is a parent record
706  $parentRecordsCount++;
707  }
708  // Always increase the record items count
709  $itemsCount++;
710  }
711  return $dataArrayPart;
712  }
713 
717  protected function ‪addCollectionChildrenRecursive(array $item, int &$recordsCount, array &$dataArrayPart = []): void
718  {
719  $collectionParent = (string)$item[self::GridColumn_CollectionCurrent];
720  foreach ($this->dataArray as $element) {
721  if ((string)($element[‪self::GridColumn_CollectionParent] ?? '') === $collectionParent) {
722  // Increase the "real" record items count
723  $recordsCount++;
724  // Fetch the children from the dataArray using the current record items
725  // count. This is possible since the dataArray is already sorted.
726  $child = $this->dataArray[$recordsCount];
727  // In case $dataArrayPart is not given, just count the item
728  if ($dataArrayPart !== []) {
729  // Add the children
730  $dataArrayPart[] = $child;
731  }
732  // In case the $child is also a collection, add it's children as well (recursively)
733  if ((int)($child[‪self::GridColumn_Collection] ?? 0) !== 0) {
734  $this->‪addCollectionChildrenRecursive($child, $recordsCount, $dataArrayPart);
735  }
736  }
737  }
738  }
739 
743  protected function ‪getSystemLanguages(int $pageId): array
744  {
745  return GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages($pageId);
746  }
747 
752  {
753  if (!isset($this->integrityService)) {
754  $this->integrityService = GeneralUtility::makeInstance(IntegrityService::class);
755  }
757  }
758 
760  {
761  return GeneralUtility::makeInstance(CollectionService::class);
762  }
763 
765  {
766  return ‪$GLOBALS['BE_USER'];
767  }
768 }
‪TYPO3\CMS\Workspaces\Service\GridDataService\sortDataArray
‪sortDataArray()
Definition: GridDataService.php:391
‪TYPO3\CMS\Workspaces\Service\GridDataService\isFilterTextInVisibleColumns
‪isFilterTextInVisibleColumns(string $filterText, array $versionArray)
Definition: GridDataService.php:505
‪TYPO3\CMS\Workspaces\Service\GridDataService\$currentWorkspace
‪int $currentWorkspace
Definition: GridDataService.php:60
‪TYPO3\CMS\Workspaces\Service\GridDataService\getDataArray
‪getDataArray(int $start, int $limit)
Definition: GridDataService.php:301
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixed_lgd_cs
‪static string fixed_lgd_cs(string $string, int $chars, string $appendString='...')
Definition: GeneralUtility.php:92
‪TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord\getVersionRecord
‪getVersionRecord()
Definition: CombinedRecord.php:103
‪TYPO3\CMS\Workspaces\Authorization\WorkspacePublishGate
Definition: WorkspacePublishGate.php:29
‪TYPO3\CMS\Workspaces\Service\GridDataService\stringSort
‪stringSort(array $a, array $b)
Definition: GridDataService.php:454
‪TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord
Definition: CombinedRecord.php:28
‪TYPO3\CMS\Workspaces\Event\SortVersionedDataEvent
Definition: SortVersionedDataEvent.php:26
‪TYPO3\CMS\Workspaces\Service\GridDataService\generateDataArray
‪generateDataArray(array $versions, string $filterTxt, ServerRequestInterface $request)
Definition: GridDataService.php:120
‪TYPO3\CMS\Workspaces\Service
‪TYPO3\CMS\Workspaces\Event\AfterDataGeneratedForWorkspaceEvent
Definition: AfterDataGeneratedForWorkspaceEvent.php:26
‪TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord\getLiveRecord
‪getLiveRecord()
Definition: CombinedRecord.php:87
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Workspaces\Service\GridDataService\__construct
‪__construct(private readonly EventDispatcherInterface $eventDispatcher, private readonly WorkspaceService $workspaceService, private readonly ModuleProvider $moduleProvider, private readonly WorkspacePublishGate $workspacePublishGate,)
Definition: GridDataService.php:80
‪TYPO3\CMS\Workspaces\Service\GridDataService\calculateHash
‪calculateHash(array $versions, string $filterTxt)
Definition: GridDataService.php:371
‪TYPO3\CMS\Workspaces\Service\GridDataService\initializeWorkspacesCachingFramework
‪initializeWorkspacesCachingFramework()
Definition: GridDataService.php:322
‪TYPO3\CMS\Workspaces\Service\GridDataService\$sortDir
‪string $sortDir
Definition: GridDataService.php:75
‪TYPO3\CMS\Workspaces\Service\GridDataService\calculateStartWithCollections
‪calculateStartWithCollections(int $start)
Definition: GridDataService.php:642
‪TYPO3\CMS\Workspaces\Service\GridDataService\getDataArrayFromCache
‪bool getDataArrayFromCache(array $versions, string $filterTxt)
Definition: GridDataService.php:353
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Backend\Module\ModuleProvider
Definition: ModuleProvider.php:29
‪TYPO3\CMS\Workspaces\Service\GridDataService\intSort
‪intSort(array $a, array $b)
Definition: GridDataService.php:426
‪TYPO3\CMS\Workspaces\Service\GridDataService\$sort
‪string $sort
Definition: GridDataService.php:70
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_ID
‪const STAGE_PUBLISH_ID
Definition: StagesService.php:38
‪TYPO3\CMS\Workspaces\Service\GridDataService\getSystemLanguages
‪getSystemLanguages(int $pageId)
Definition: GridDataService.php:743
‪TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord\createFromArrays
‪static createFromArrays(string $table, array $liveRow, array $versionRow)
Definition: CombinedRecord.php:54
‪TYPO3\CMS\Workspaces\Service\GridDataService\getSystemLanguageValue
‪string null getSystemLanguageValue(int $id, int $pageId, string $key)
Definition: GridDataService.php:629
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_CollectionCurrent
‪const GridColumn_CollectionCurrent
Definition: GridDataService.php:54
‪TYPO3\CMS\Workspaces\Service\GridDataService\getIntegrityService
‪getIntegrityService()
Definition: GridDataService.php:751
‪TYPO3\CMS\Workspaces\Controller\Remote\RemoteServer
Definition: RemoteServer.php:56
‪TYPO3\CMS\Workspaces\Service\GridDataService\fillDataArrayPart
‪fillDataArrayPart(int $start, int $end)
Definition: GridDataService.php:681
‪TYPO3\CMS\Workspaces\Service\GridDataService\isPageModuleAllowed
‪isPageModuleAllowed()
Definition: GridDataService.php:496
‪TYPO3\CMS\Workspaces\Service\Dependency\CollectionService
Definition: CollectionService.php:36
‪TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder
Definition: PreviewUriBuilder.php:47
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Workspaces\Service\IntegrityService
Definition: IntegrityService.php:29
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Workspaces\Service\GridDataService\addCollectionChildrenRecursive
‪addCollectionChildrenRecursive(array $item, int &$recordsCount, array &$dataArrayPart=[])
Definition: GridDataService.php:717
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\PUBLISH_ACCESS_ONLY_IN_PUBLISH_STAGE
‪const PUBLISH_ACCESS_ONLY_IN_PUBLISH_STAGE
Definition: WorkspaceService.php:45
‪TYPO3\CMS\Workspaces\Service\GridDataService\$workspacesCache
‪FrontendInterface $workspacesCache
Definition: GridDataService.php:77
‪TYPO3\CMS\Workspaces\Service\GridDataService\workspaceState
‪workspaceState(int $stateId, bool $hiddenOnline=false, bool $hiddenOffline=false, bool $hasDiff=true)
Definition: GridDataService.php:554
‪TYPO3\CMS\Workspaces\Service\WorkspaceService
Definition: WorkspaceService.php:38
‪TYPO3\CMS\Workspaces\Event\AfterCompiledCacheableDataForWorkspaceEvent
Definition: AfterCompiledCacheableDataForWorkspaceEvent.php:26
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_EDIT_ID
‪const STAGE_EDIT_ID
Definition: StagesService.php:39
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_CollectionLevel
‪const GridColumn_CollectionLevel
Definition: GridDataService.php:52
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider
Definition: TranslationConfigurationProvider.php:39
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Workspaces\Service\GridDataService\$dataArray
‪array $dataArray
Definition: GridDataService.php:65
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_Collection
‪const GridColumn_Collection
Definition: GridDataService.php:51
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_CollectionParent
‪const GridColumn_CollectionParent
Definition: GridDataService.php:53
‪TYPO3\CMS\Workspaces\Service\GridDataService\getBackendUser
‪getBackendUser()
Definition: GridDataService.php:764
‪TYPO3\CMS\Workspaces\Service\GridDataService\getDependencyCollectionService
‪getDependencyCollectionService()
Definition: GridDataService.php:759
‪TYPO3\CMS\Workspaces\Service\GridDataService\generateGridListFromVersions
‪array generateGridListFromVersions(array $versions, \stdClass $parameter, int $currentWorkspace, ServerRequestInterface $request)
Definition: GridDataService.php:95
‪TYPO3\CMS\Workspaces\Service\GridDataService\isSortable
‪isSortable(array $a, array $b)
Definition: GridDataService.php:484
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_CollectionChildren
‪const GridColumn_CollectionChildren
Definition: GridDataService.php:55
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Workspaces\Service\GridDataService\versionIsModified
‪versionIsModified(CombinedRecord $combinedRecord, ServerRequestInterface $request)
Definition: GridDataService.php:268
‪TYPO3\CMS\Workspaces\Service\GridDataService\getTcaEnableColumnsFieldName
‪string null getTcaEnableColumnsFieldName(string $table, string $type)
Definition: GridDataService.php:590
‪TYPO3\CMS\Workspaces\Event\GetVersionedDataEvent
Definition: GetVersionedDataEvent.php:28
‪TYPO3\CMS\Workspaces\Service\GridDataService\$integrityService
‪IntegrityService $integrityService
Definition: GridDataService.php:78
‪TYPO3\CMS\Workspaces\Service\GridDataService\setDataArrayIntoCache
‪setDataArrayIntoCache(array $versions, string $filterTxt)
Definition: GridDataService.php:333
‪TYPO3\CMS\Workspaces\Service\GridDataService\resolveDataArrayDependencies
‪resolveDataArrayDependencies()
Definition: GridDataService.php:286
‪TYPO3\CMS\Workspaces\Service\GridDataService\getLanguageValue
‪int getLanguageValue(string $table, array $record)
Definition: GridDataService.php:608
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Workspaces\Service\GridDataService
Definition: GridDataService.php:48