‪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 PharIo\Version\Version;
21 use Psr\EventDispatcher\EventDispatcherInterface;
22 use Psr\Http\Message\ServerRequestInterface;
23 use Psr\Log\LoggerAwareInterface;
24 use Psr\Log\LoggerAwareTrait;
27 use TYPO3\CMS\Backend\Utility\BackendUtility;
32 use TYPO3\CMS\Core\Imaging\IconSize;
44 
48 class ‪GridDataService implements LoggerAwareInterface
49 {
50  use LoggerAwareTrait;
51 
52  public const ‪GridColumn_Collection = 'Workspaces_Collection';
53  public const ‪GridColumn_CollectionLevel = 'Workspaces_CollectionLevel';
54  public const ‪GridColumn_CollectionParent = 'Workspaces_CollectionParent';
55  public const ‪GridColumn_CollectionCurrent = 'Workspaces_CollectionCurrent';
56  public const ‪GridColumn_CollectionChildren = 'Workspaces_CollectionChildren';
57 
61  protected int ‪$currentWorkspace = 0;
62 
66  protected array ‪$dataArray = [];
67 
71  protected string ‪$sort = '';
72 
76  protected string ‪$sortDir = '';
77 
80 
81  public function ‪__construct(
82  private readonly EventDispatcherInterface $eventDispatcher,
83  private readonly ‪WorkspaceService $workspaceService,
84  private readonly ‪ModuleProvider $moduleProvider,
85  private readonly ‪WorkspacePublishGate $workspacePublishGate,
86  ) {}
87 
96  public function ‪generateGridListFromVersions(array $versions, \stdClass $parameter, int ‪$currentWorkspace, ServerRequestInterface $request): array
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  $this->currentWorkspace = ‪$currentWorkspace;
105  $this->generateDataArray($versions, $filterTxt, $request);
106  return [
107  // Only count parent records for pagination
108  'total' => count(array_filter($this->dataArray, static function ($element) {
109  return (int)($element[self::GridColumn_CollectionLevel] ?? 0) === 0;
110  })),
111  'data' => $this->‪getDataArray($start, $limit),
112  ];
113  }
114 
121  protected function ‪generateDataArray(array $versions, string $filterTxt, ServerRequestInterface $request): void
122  {
123  $backendUser = $this->‪getBackendUser();
124  $workspaceAccess = $backendUser->checkWorkspace($backendUser->workspace);
126 
127  $isAllowedToPublish = $this->workspacePublishGate->isGranted($backendUser, $backendUser->workspace);
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  $table = (string)$table;
142  $hiddenField = $this->‪getTcaEnableColumnsFieldName($table, 'disabled');
143  $isRecordTypeAllowedToModify = $backendUser->check('tables_modify', $table);
144 
145  foreach ($records as ‪$record) {
146  $origRecord = (array)BackendUtility::getRecord($table, ‪$record['t3ver_oid']);
147  $versionRecord = (array)BackendUtility::getRecord($table, ‪$record['uid']);
148  $combinedRecord = ‪CombinedRecord::createFromArrays($table, $origRecord, $versionRecord);
149  $hasDiff = $this->‪versionIsModified($combinedRecord, $request);
150  $this->‪getIntegrityService()->checkElement($combinedRecord);
151 
152  if ($hiddenField !== null) {
153  $recordState = $this->‪workspaceState($versionRecord['t3ver_state'], (bool)$origRecord[$hiddenField], (bool)$versionRecord[$hiddenField], $hasDiff);
154  } else {
155  $recordState = $this->‪workspaceState($versionRecord['t3ver_state'], $hasDiff);
156  }
157 
158  $isDeletedPage = $table === 'pages' && $recordState === 'deleted';
159  $pageId = (int)(‪$record['pid'] ?? null);
160  if ($table === 'pages') {
161  // The page ID for a translated page is considered here
162  $pageId = (int)(!empty(‪$record['l10n_parent']) ? ‪$record['l10n_parent'] : (‪$record['t3ver_oid'] ?: ‪$record['uid']));
163  }
164  $viewUrl = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, (int)‪$record['uid'], $origRecord, $versionRecord);
165  $workspaceRecordLabel = BackendUtility::getRecordTitle($table, $versionRecord);
166  $liveRecordLabel = BackendUtility::getRecordTitle($table, $origRecord);
167  $iconLive = $iconFactory->getIconForRecord($table, $origRecord, IconSize::SMALL);
168  $iconWorkspace = $iconFactory->getIconForRecord($table, $versionRecord, IconSize::SMALL);
169  [$pathWorkspaceCropped, $pathWorkspace] = BackendUtility::getRecordPath((int)‪$record['wspid'], '', 15, 1000);
170  $calculatedT3verOid = ‪$record['t3ver_oid'];
171  if (VersionState::tryFrom(‪$record['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER) {
172  // If we're dealing with a 'new' record, this one has no t3ver_oid. On publish, there is no
173  // live counterpart, but the publish methods later need a live uid to publish to. We thus
174  // use the uid as t3ver_oid here to be transparent on javascript side.
175  $calculatedT3verOid = ‪$record['uid'];
176  }
177 
178  $versionArray = [];
179  $versionArray['table'] = $table;
180  $versionArray['id'] = $table . ':' . ‪$record['uid'];
181  $versionArray['uid'] = ‪$record['uid'];
182  $versionArray = array_merge($versionArray, $defaultGridColumns);
183  $versionArray['label_Workspace'] = htmlspecialchars($workspaceRecordLabel);
184  $versionArray['label_Workspace_crop'] = htmlspecialchars(‪GeneralUtility::fixed_lgd_cs($workspaceRecordLabel, (int)$backendUser->uc['titleLen']));
185  $versionArray['label_Live'] = htmlspecialchars($liveRecordLabel);
186  $versionArray['label_Live_crop'] = htmlspecialchars(‪GeneralUtility::fixed_lgd_cs($liveRecordLabel, (int)$backendUser->uc['titleLen']));
187  $versionArray['label_Stage'] = htmlspecialchars($stagesObj->getStageTitle((int)$versionRecord['t3ver_stage']));
188  $tempStage = $stagesObj->getNextStage($versionRecord['t3ver_stage']);
189  $versionArray['label_nextStage'] = htmlspecialchars($stagesObj->getStageTitle((int)$tempStage['uid']));
190  $versionArray['value_nextStage'] = (int)$tempStage['uid'];
191  $tempStage = $stagesObj->getPrevStage($versionRecord['t3ver_stage']);
192  $versionArray['label_prevStage'] = htmlspecialchars($stagesObj->getStageTitle((int)($tempStage['uid'] ?? 0)));
193  $versionArray['value_prevStage'] = (int)($tempStage['uid'] ?? 0);
194  $versionArray['path_Live'] = htmlspecialchars(BackendUtility::getRecordPath(‪$record['livepid'], '', 999));
195  $versionArray['path_Workspace'] = htmlspecialchars($pathWorkspace);
196  $versionArray['path_Workspace_crop'] = htmlspecialchars($pathWorkspaceCropped);
197  $versionArray['workspace_Title'] = htmlspecialchars($this->workspaceService->getWorkspaceTitle((int)$versionRecord['t3ver_wsid']));
198  $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
199  $versionArray['lastChangedFormatted'] = BackendUtility::datetime($versionRecord['tstamp']);
200  $versionArray['t3ver_wsid'] = $versionRecord['t3ver_wsid'];
201  $versionArray['t3ver_oid'] = $calculatedT3verOid;
202  $versionArray['livepid'] = ‪$record['livepid'];
203  $versionArray['stage'] = $versionRecord['t3ver_stage'];
204  $versionArray['icon_Live'] = $iconLive->getIdentifier();
205  $versionArray['icon_Live_Overlay'] = $iconLive->getOverlayIcon()?->getIdentifier() ?? '';
206  $versionArray['icon_Workspace'] = $iconWorkspace->getIdentifier();
207  $versionArray['icon_Workspace_Overlay'] = $iconWorkspace->getOverlayIcon()?->getIdentifier() ?? '';
208  $languageValue = $this->‪getLanguageValue($table, $versionRecord);
209  $versionArray['languageValue'] = $languageValue;
210  $versionArray['language'] = [
211  'icon' => $iconFactory->getIcon($this->‪getSystemLanguageValue($languageValue, $pageId, 'flagIcon'), IconSize::SMALL)->getIdentifier(),
212  ];
213  $versionArray['allowedAction_nextStage'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
214  $versionArray['allowedAction_prevStage'] = $isRecordTypeAllowedToModify && $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
215  if ($isAllowedToPublish && $swapStage !== ‪StagesService::STAGE_EDIT_ID && (int)$versionRecord['t3ver_stage'] === $swapStage) {
216  $versionArray['allowedAction_publish'] = $isRecordTypeAllowedToModify && $stagesObj->isNextStageAllowedForUser($swapStage);
217  } elseif ($isAllowedToPublish && $swapStage === ‪StagesService::STAGE_EDIT_ID) {
218  $versionArray['allowedAction_publish'] = $isRecordTypeAllowedToModify;
219  } else {
220  $versionArray['allowedAction_publish'] = false;
221  }
222  $versionArray['allowedAction_delete'] = $isRecordTypeAllowedToModify;
223  // preview and editing of a deleted page won't work ;)
224  $versionArray['allowedAction_view'] = !$isDeletedPage && $viewUrl;
225  $versionArray['allowedAction_edit'] = $isRecordTypeAllowedToModify && !$isDeletedPage;
226  $versionArray['allowedAction_versionPageOpen'] = $this->‪isPageModuleAllowed() && !$isDeletedPage;
227  $versionArray['state_Workspace'] = $recordState;
228  $versionArray['hasChanges'] = $recordState !== 'unchanged';
229  // Allows to be overridden by PSR-14 event to dynamically modify the expand / collapse state
230  $versionArray['expanded'] = false;
231 
232  if ($filterTxt == '' || $this->‪isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
233  $versionIdentifier = $versionArray['id'];
234  $this->dataArray[$versionIdentifier] = $versionArray;
235  }
236  }
237  }
238 
239  // Trigger a PSR-14 event
240  $event = new ‪AfterCompiledCacheableDataForWorkspaceEvent($this, $this->dataArray, $versions);
241  $this->eventDispatcher->dispatch($event);
242  $this->dataArray = $event->getData();
243  $versions = $event->getVersions();
244  // Enrich elements after everything has been processed:
245  foreach ($this->dataArray as &$element) {
246  ‪$identifier = $element['table'] . ':' . $element['t3ver_oid'];
247  $messages = $this->‪getIntegrityService()->getIssueMessages(‪$identifier);
248  $element['integrity'] = [
249  'status' => $this->‪getIntegrityService()->getStatusRepresentation(‪$identifier),
250  'messages' => htmlspecialchars(implode('<br>', $messages)),
251  ];
252  }
253  $this->‪setDataArrayIntoCache($versions, $filterTxt);
254  }
255 
256  // Trigger a PSR-14 event
257  $event = new ‪AfterDataGeneratedForWorkspaceEvent($this, $this->dataArray, $versions);
258  $this->eventDispatcher->dispatch($event);
259  $this->dataArray = $event->getData();
260  $this->‪sortDataArray();
262  }
263 
264  protected function ‪versionIsModified(‪CombinedRecord $combinedRecord, ServerRequestInterface $request): bool
265  {
266  $remoteServer = GeneralUtility::makeInstance(RemoteServer::class);
267 
268  $params = new \stdClass();
269  $params->stage = (int)$combinedRecord->‪getVersionRecord()->getRow()['t3ver_stage'];
270  $params->t3ver_oid = $combinedRecord->‪getLiveRecord()->getUid();
271  $params->table = $combinedRecord->‪getLiveRecord()->getTable();
272  $params->uid = $combinedRecord->‪getVersionRecord()->getUid();
273 
274  $result = $remoteServer->getRowDetails($params, $request);
275  return !empty($result['data'][0]['diff']);
276  }
277 
282  protected function ‪resolveDataArrayDependencies(): void
283  {
284  $collectionService = $this->‪getDependencyCollectionService();
285  $dependencyResolver = $collectionService->getDependencyResolver();
286 
287  foreach ($this->dataArray as $dataElement) {
288  $dependencyResolver->addElement($dataElement['table'], $dataElement['uid']);
289  }
290 
291  $this->dataArray = $collectionService->process($this->dataArray);
292  }
293 
297  protected function ‪getDataArray(int $start, int $limit): array
298  {
299  $dataArrayCount = count($this->dataArray);
300  $start = $this->‪calculateStartWithCollections($start);
301  $end = ($start + $limit < $dataArrayCount ? $start + $limit : $dataArrayCount);
302 
303  // Ensure that there are numerical indexes
304  $this->dataArray = array_values($this->dataArray);
305  // Fill the data array part
306  $dataArrayPart = $this->‪fillDataArrayPart($start, $end);
307 
308  // Trigger a PSR-14 event
309  $event = new ‪GetVersionedDataEvent($this, $this->dataArray, $start, $limit, $dataArrayPart);
310  $this->eventDispatcher->dispatch($event);
311  $this->dataArray = $event->getData();
312  return $event->getDataArrayPart();
313  }
314 
318  protected function ‪initializeWorkspacesCachingFramework(): void
319  {
320  $this->workspacesCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('workspaces_cache');
321  }
322 
329  protected function ‪setDataArrayIntoCache(array $versions, string $filterTxt): void
330  {
331  $hash = $this->‪calculateHash($versions, $filterTxt);
332  $this->workspacesCache->set(
333  $hash,
334  $this->dataArray,
335  [
336  (string)$this->currentWorkspace,
337  'user_' . $this->‪getBackendUser()->user['uid'],
338  ]
339  );
340  }
341 
349  protected function ‪getDataArrayFromCache(array $versions, string $filterTxt): bool
350  {
351  $cacheEntry = false;
352  $hash = $this->‪calculateHash($versions, $filterTxt);
353  $content = $this->workspacesCache->get($hash);
354  if (is_array($content)) {
355  $this->dataArray = $content;
356  $cacheEntry = true;
357  }
358  return $cacheEntry;
359  }
360 
367  protected function ‪calculateHash(array $versions, string $filterTxt): string
368  {
369  $backendUser = $this->‪getBackendUser();
370  $hashArray = [
371  $backendUser->workspace,
372  $backendUser->user['uid'],
373  $versions,
374  $filterTxt,
378  ];
379  $hash = md5(serialize($hashArray));
380  return $hash;
381  }
382 
387  protected function ‪sortDataArray(): void
388  {
389  switch ($this->sort) {
390  case 'uid':
391  case 'change':
392  case 'workspace_Tstamp':
393  case 't3ver_oid':
394  case 'liveid':
395  case 'livepid':
396  case 'languageValue':
397  uasort($this->dataArray, [$this, 'intSort']);
398  break;
399  case 'label_Workspace':
400  case 'label_Live':
401  case 'label_Stage':
402  case 'workspace_Title':
403  case 'path_Live':
404  // case 'path_Workspace': This is the first sorting attribute
405  uasort($this->dataArray, [$this, 'stringSort']);
406  break;
407  default:
408  // Do nothing
409  }
410 
411  // Trigger an event for extensibility
412  $event = new ‪SortVersionedDataEvent($this, $this->dataArray, $this->sort, $this->sortDir);
413  $this->eventDispatcher->dispatch($event);
414  $this->dataArray = $event->getData();
415  $this->sort = $event->getSortColumn();
416  $this->sortDir = $event->getSortDirection();
417  }
418 
422  protected function ‪intSort(array $a, array $b): int
423  {
424  if (!$this->‪isSortable($a, $b)) {
425  return 0;
426  }
427 
428  // First sort by using the page-path in current workspace
429  $pathSortingResult = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
430  if ($pathSortingResult !== 0) {
431  return $pathSortingResult;
432  }
433 
434  if ($a[$this->sort] == $b[$this->sort]) {
435  $sortingResult = 0;
436  } elseif ($this->sortDir === 'ASC') {
437  $sortingResult = $a[‪$this->sort] < $b[‪$this->sort] ? -1 : 1;
438  } elseif ($this->sortDir === 'DESC') {
439  $sortingResult = $a[‪$this->sort] > $b[‪$this->sort] ? -1 : 1;
440  } else {
441  $sortingResult = 0;
442  }
443 
444  return $sortingResult;
445  }
446 
450  protected function ‪stringSort(array $a, array $b): int
451  {
452  if (!$this->‪isSortable($a, $b)) {
453  return 0;
454  }
455 
456  // First sort by using the page-path in current workspace
457  $pathSortingResult = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
458  if ($pathSortingResult !== 0) {
459  return $pathSortingResult;
460  }
461 
462  if ($a[$this->sort] == $b[$this->sort]) {
463  $sortingResult = 0;
464  } elseif ($this->sortDir === 'ASC') {
465  $sortingResult = strcasecmp($a[$this->sort], $b[$this->sort]);
466  } elseif ($this->sortDir === 'DESC') {
467  $sortingResult = strcasecmp($a[$this->sort], $b[$this->sort]) * -1;
468  } else {
469  $sortingResult = 0;
470  }
471 
472  return $sortingResult;
473  }
474 
480  protected function ‪isSortable(array $a, array $b): bool
481  {
482  return
485  ;
486  }
487 
492  protected function ‪isPageModuleAllowed(): bool
493  {
494  return $this->moduleProvider->accessGranted('web_layout', $this->‪getBackendUser());
495  }
496 
501  protected function ‪isFilterTextInVisibleColumns(string $filterText, array $versionArray): bool
502  {
503  $backendUser = $this->‪getBackendUser();
504  if (is_array($backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'] ?? false)) {
505  $visibleColumns = $backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['columns'];
506  } else {
507  $visibleColumns = [
508  'workspace_Formated_Tstamp' => ['hidden' => 0],
509  'change' => ['hidden' => 0],
510  'path_Workspace' => ['hidden' => 0],
511  'path_Live' => ['hidden' => 0],
512  'label_Live' => ['hidden' => 0],
513  'label_Stage' => ['hidden' => 0],
514  'label_Workspace' => ['hidden' => 0],
515  ];
516  }
517  foreach ($visibleColumns as $column => $value) {
518  if (isset($value['hidden']) && isset($versionArray[$column])) {
519  if ($value['hidden'] == 0) {
520  switch ($column) {
521  case 'workspace_Tstamp':
522  if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== false) {
523  return true;
524  }
525  break;
526  case 'change':
527  if (stripos((string)$versionArray[$column], str_replace('%', '', $filterText)) !== false) {
528  return true;
529  }
530  break;
531  default:
532  if (stripos((string)$versionArray[$column], $filterText) !== false) {
533  return true;
534  }
535  }
536  }
537  }
538  }
539  return false;
540  }
541 
550  protected function ‪workspaceState(int $stateId, bool $hiddenOnline = false, bool $hiddenOffline = false, bool $hasDiff = true): string
551  {
552  $hiddenState = null;
553  if (!$hiddenOnline && $hiddenOffline) {
554  $hiddenState = 'hidden';
555  } elseif ($hiddenOnline && !$hiddenOffline) {
556  $hiddenState = 'unhidden';
557  }
558  switch (VersionState::tryFrom($stateId)) {
559  case VersionState::NEW_PLACEHOLDER:
560  $state = 'new';
561  break;
562  case VersionState::DELETE_PLACEHOLDER:
563  $state = 'deleted';
564  break;
565  case VersionState::MOVE_POINTER:
566  $state = 'moved';
567  break;
568  default:
569  if (!$hasDiff) {
570  $state = 'unchanged';
571  } else {
572  $state = ($hiddenState ?: 'modified');
573  }
574  }
575 
576  return $state;
577  }
578 
586  protected function ‪getTcaEnableColumnsFieldName(string $table, string $type): ?string
587  {
588  $fieldName = null;
589 
590  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type])) {
591  $fieldName = ‪$GLOBALS['TCA'][$table]['ctrl']['enablecolumns'][$type];
592  }
593 
594  return $fieldName;
595  }
596 
604  protected function ‪getLanguageValue(string $table, array ‪$record): int
605  {
606  $languageValue = 0;
607  if (BackendUtility::isTableLocalizable($table)) {
608  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
609  if (!empty(‪$record[$languageField])) {
610  $languageValue = (int)‪$record[$languageField];
611  }
612  }
613  return $languageValue;
614  }
615 
625  protected function ‪getSystemLanguageValue(int $id, int $pageId, string $key): ?string
626  {
627  $value = null;
628  $systemLanguages = $this->‪getSystemLanguages($pageId);
629  if (!empty($systemLanguages[$id][$key])) {
630  $value = $systemLanguages[$id][$key];
631  }
632  return $value;
633  }
634 
638  protected function ‪calculateStartWithCollections(int $start): int
639  {
640  // The recordsCount is the real record items count, while the
641  // parentRecordsCount only takes the parent records into account
642  $recordsCount = $parentRecordsCount = 0;
643  while ($parentRecordsCount < $start) {
644  // As soon as no more item exists in the dataArray, the loop needs to end
645  // prematurely to prevent invalid items which would may led to some unexpected
646  // behaviour. Note: This usually should never happen since these records must
647  // exists => As they were responsible for increasing the start value. However to
648  // prevent errors in case multiple different users manipulate the records count
649  // somehow simultaneously, we apply this check to be save.
650  if (!isset($this->dataArray[$recordsCount])) {
651  break;
652  }
653  // Loop over the dataArray until we found enough parent records
654  $item = $this->dataArray[$recordsCount];
655  if (($item[self::GridColumn_CollectionLevel] ?? 0) === 0) {
656  // In case the current parent record is the last one ($start is reached),
657  // ensure its collection children are counted as well.
658  if (($parentRecordsCount + 1) === $start && (int)($item[self::GridColumn_Collection] ?? 0) !== 0) {
659  // By not providing the third parameter, we only count the collection children recursively
660  $this->‪addCollectionChildrenRecursive($item, $recordsCount);
661  }
662  // Only increase the parent records count in case $item is a parent record
663  $parentRecordsCount++;
664  }
665  // Always increase the record items count
666  $recordsCount++;
667  }
668 
669  return $recordsCount;
670  }
671 
677  private function ‪fillDataArrayPart(int $start, int $end): array
678  {
679  // Initialize empty data array part
680  $dataArrayPart = [];
681  // The recordsCount is the real record items count, while the
682  // parentRecordsCount only takes the parent records into account.
683  $itemsCount = $parentRecordsCount = $start;
684  while ($parentRecordsCount < $end) {
685  // As soon as no more item exists in the dataArray, the loop needs to end
686  // prematurely to prevent invalid items which would trigger JavaScript errors.
687  if (!isset($this->dataArray[$itemsCount])) {
688  break;
689  }
690  // Loop over the dataArray until we found enough parent records
691  $item = $this->dataArray[$itemsCount];
692  // Add the item to the $dataArrayPart
693  $dataArrayPart[] = $item;
694  if (($item[self::GridColumn_CollectionLevel] ?? 0) === 0) {
695  // In case the current parent record is the last one ($end is reached),
696  // ensure its collection children are added as well.
697  if (($parentRecordsCount + 1) === $end && (int)($item[self::GridColumn_Collection] ?? 0) !== 0) {
698  // Add collection children recursively
699  $this->‪addCollectionChildrenRecursive($item, $itemsCount, $dataArrayPart);
700  }
701  // Only increase the parent records count in case $item is a parent record
702  $parentRecordsCount++;
703  }
704  // Always increase the record items count
705  $itemsCount++;
706  }
707  return $dataArrayPart;
708  }
709 
713  protected function ‪addCollectionChildrenRecursive(array $item, int &$recordsCount, array &$dataArrayPart = []): void
714  {
715  $collectionParent = (string)$item[self::GridColumn_CollectionCurrent];
716  foreach ($this->dataArray as $element) {
717  if ((string)($element[‪self::GridColumn_CollectionParent] ?? '') === $collectionParent) {
718  // Increase the "real" record items count
719  $recordsCount++;
720  // Fetch the children from the dataArray using the current record items
721  // count. This is possible since the dataArray is already sorted.
722  $child = $this->dataArray[$recordsCount];
723  // In case $dataArrayPart is not given, just count the item
724  if ($dataArrayPart !== []) {
725  // Add the children
726  $dataArrayPart[] = $child;
727  }
728  // In case the $child is also a collection, add it's children as well (recursively)
729  if ((int)($child[‪self::GridColumn_Collection] ?? 0) !== 0) {
730  $this->‪addCollectionChildrenRecursive($child, $recordsCount, $dataArrayPart);
731  }
732  }
733  }
734  }
735 
739  protected function ‪getSystemLanguages(int $pageId): array
740  {
741  return GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages($pageId);
742  }
743 
748  {
749  if (!isset($this->integrityService)) {
750  $this->integrityService = GeneralUtility::makeInstance(IntegrityService::class);
751  }
753  }
754 
756  {
757  return GeneralUtility::makeInstance(CollectionService::class);
758  }
759 
761  {
762  return ‪$GLOBALS['BE_USER'];
763  }
764 }
‪TYPO3\CMS\Workspaces\Service\GridDataService\sortDataArray
‪sortDataArray()
Definition: GridDataService.php:387
‪TYPO3\CMS\Workspaces\Service\GridDataService\isFilterTextInVisibleColumns
‪isFilterTextInVisibleColumns(string $filterText, array $versionArray)
Definition: GridDataService.php:501
‪TYPO3\CMS\Workspaces\Service\GridDataService\$currentWorkspace
‪int $currentWorkspace
Definition: GridDataService.php:61
‪TYPO3\CMS\Workspaces\Service\GridDataService\getDataArray
‪getDataArray(int $start, int $limit)
Definition: GridDataService.php:297
‪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:450
‪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:121
‪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:81
‪TYPO3\CMS\Workspaces\Service\GridDataService\calculateHash
‪calculateHash(array $versions, string $filterTxt)
Definition: GridDataService.php:367
‪TYPO3\CMS\Workspaces\Service\GridDataService\initializeWorkspacesCachingFramework
‪initializeWorkspacesCachingFramework()
Definition: GridDataService.php:318
‪TYPO3\CMS\Workspaces\Service\GridDataService\$sortDir
‪string $sortDir
Definition: GridDataService.php:76
‪TYPO3\CMS\Workspaces\Service\GridDataService\calculateStartWithCollections
‪calculateStartWithCollections(int $start)
Definition: GridDataService.php:638
‪TYPO3\CMS\Workspaces\Service\GridDataService\getDataArrayFromCache
‪bool getDataArrayFromCache(array $versions, string $filterTxt)
Definition: GridDataService.php:349
‪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:422
‪TYPO3\CMS\Workspaces\Service\GridDataService\$sort
‪string $sort
Definition: GridDataService.php:71
‪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:739
‪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:625
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_CollectionCurrent
‪const GridColumn_CollectionCurrent
Definition: GridDataService.php:55
‪TYPO3\CMS\Workspaces\Service\GridDataService\getIntegrityService
‪getIntegrityService()
Definition: GridDataService.php:747
‪TYPO3\CMS\Workspaces\Controller\Remote\RemoteServer
Definition: RemoteServer.php:56
‪TYPO3\CMS\Workspaces\Service\GridDataService\fillDataArrayPart
‪fillDataArrayPart(int $start, int $end)
Definition: GridDataService.php:677
‪TYPO3\CMS\Workspaces\Service\GridDataService\isPageModuleAllowed
‪isPageModuleAllowed()
Definition: GridDataService.php:492
‪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:713
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:61
‪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:78
‪TYPO3\CMS\Workspaces\Service\GridDataService\workspaceState
‪workspaceState(int $stateId, bool $hiddenOnline=false, bool $hiddenOffline=false, bool $hasDiff=true)
Definition: GridDataService.php:550
‪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:53
‪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:66
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_Collection
‪const GridColumn_Collection
Definition: GridDataService.php:52
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_CollectionParent
‪const GridColumn_CollectionParent
Definition: GridDataService.php:54
‪TYPO3\CMS\Workspaces\Service\GridDataService\getBackendUser
‪getBackendUser()
Definition: GridDataService.php:760
‪TYPO3\CMS\Workspaces\Service\GridDataService\getDependencyCollectionService
‪getDependencyCollectionService()
Definition: GridDataService.php:755
‪TYPO3\CMS\Workspaces\Service\GridDataService\generateGridListFromVersions
‪array generateGridListFromVersions(array $versions, \stdClass $parameter, int $currentWorkspace, ServerRequestInterface $request)
Definition: GridDataService.php:96
‪TYPO3\CMS\Workspaces\Service\GridDataService\isSortable
‪isSortable(array $a, array $b)
Definition: GridDataService.php:480
‪TYPO3\CMS\Workspaces\Service\GridDataService\GridColumn_CollectionChildren
‪const GridColumn_CollectionChildren
Definition: GridDataService.php:56
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Workspaces\Service\GridDataService\versionIsModified
‪versionIsModified(CombinedRecord $combinedRecord, ServerRequestInterface $request)
Definition: GridDataService.php:264
‪TYPO3\CMS\Workspaces\Service\GridDataService\getTcaEnableColumnsFieldName
‪string null getTcaEnableColumnsFieldName(string $table, string $type)
Definition: GridDataService.php:586
‪TYPO3\CMS\Workspaces\Event\GetVersionedDataEvent
Definition: GetVersionedDataEvent.php:28
‪TYPO3\CMS\Workspaces\Service\GridDataService\$integrityService
‪IntegrityService $integrityService
Definition: GridDataService.php:79
‪TYPO3\CMS\Workspaces\Service\GridDataService\setDataArrayIntoCache
‪setDataArrayIntoCache(array $versions, string $filterTxt)
Definition: GridDataService.php:329
‪TYPO3\CMS\Workspaces\Service\GridDataService\resolveDataArrayDependencies
‪resolveDataArrayDependencies()
Definition: GridDataService.php:282
‪TYPO3\CMS\Workspaces\Service\GridDataService\getLanguageValue
‪int getLanguageValue(string $table, array $record)
Definition: GridDataService.php:604
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Workspaces\Service\GridDataService
Definition: GridDataService.php:49