‪TYPO3CMS  ‪main
WorkspaceService.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 TYPO3\CMS\Backend\Utility\BackendUtility;
33 
38 {
39  protected array ‪$versionsOnPageCache = [];
40  protected array ‪$pagesWithVersionsInTable = [];
41 
42  public const ‪TABLE_WORKSPACE = 'sys_workspace';
43  public const ‪LIVE_WORKSPACE_ID = 0;
44 
48 
55  public function ‪getAvailableWorkspaces(): array
56  {
57  $backendUser = $this->‪getBackendUser();
58  $availableWorkspaces = [];
59  // add default workspaces
60  if ($backendUser->checkWorkspace(self::LIVE_WORKSPACE_ID)) {
61  $availableWorkspaces[‪self::LIVE_WORKSPACE_ID] = $this->‪getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
62  }
63  // add custom workspaces (selecting all, filtering by BE_USER check):
64  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
65  $queryBuilder->getRestrictions()
66  ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
67 
68  $result = $queryBuilder
69  ->select('uid', 'title', 'adminusers', 'members')
70  ->from('sys_workspace')
71  ->orderBy('title')
72  ->executeQuery();
73 
74  while ($workspace = $result->fetchAssociative()) {
75  if ($backendUser->checkWorkspace($workspace)) {
76  $availableWorkspaces[$workspace['uid']] = $workspace['title'];
77  }
78  }
79  return $availableWorkspaces;
80  }
81 
85  public function ‪getCurrentWorkspace(): int
86  {
87  return $this->‪getBackendUser()->workspace;
88  }
89 
100  public function ‪getPreviewLinkLifetime(): int
101  {
102  $workspaceId = $this->‪getCurrentWorkspace();
103  if ($workspaceId > 0) {
104  $wsRecord = BackendUtility::getRecord('sys_workspace', $workspaceId, '*');
105  if (($wsRecord['previewlink_lifetime'] ?? 0) > 0) {
106  return (int)$wsRecord['previewlink_lifetime'];
107  }
108  }
109  $ttlHours = (int)($this->‪getBackendUser()->getTSConfig()['options.']['workspaces.']['previewLinkTTLHours'] ?? 0);
110  return $ttlHours ?: 24 * 2;
111  }
112 
116  public function ‪getWorkspaceTitle(int $wsId): string
117  {
118  $title = false;
119  switch ($wsId) {
121  $title = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:shortcut_onlineWS');
122  break;
123  default:
124  $labelField = ‪$GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
125  $wsRecord = BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
126  if (is_array($wsRecord)) {
127  $title = (string)$wsRecord[$labelField];
128  }
129  }
130  if ($title === false) {
131  throw new \InvalidArgumentException('No such workspace defined', 1476045469);
132  }
133  return $title;
134  }
135 
144  public function ‪getCmdArrayForPublishWS(int $wsid, bool $_ = false, int $language = null): array
145  {
146  $cmd = [];
147  if ($wsid > 0) {
148  // Define stage to select:
149  $stage = -99;
150  $workspaceRec = BackendUtility::getRecord('sys_workspace', $wsid);
151  if ($workspaceRec['publish_access'] & self::PUBLISH_ACCESS_ONLY_IN_PUBLISH_STAGE) {
153  }
154  // Select all versions to publishing
155  $versions = $this->‪selectVersionsInWorkspace(
156  $wsid,
157  $stage,
158  -1,
159  999,
160  'tables_modify',
161  $language
162  );
163  // Traverse the selection to build CMD array:
164  foreach ($versions as $table => $records) {
165  foreach ($records as $rec) {
166  // For new records, the live ID is the same as the version ID
167  $liveId = $rec['t3ver_oid'] ?: $rec['uid'];
168  $cmd[$table][$liveId]['version'] = ['action' => 'swap', 'swapWith' => $rec['uid']];
169  }
170  }
171  }
172  return $cmd;
173  }
174 
182  public function ‪getCmdArrayForFlushWS(int $wsid, int $language = null)
183  {
184  $cmd = [];
185  if ($wsid > 0) {
186  // Define stage to select:
187  $stage = -99;
188  // Select all versions to publish
189  $versions = $this->‪selectVersionsInWorkspace(
190  $wsid,
191  $stage,
192  -1,
193  999,
194  'tables_modify',
195  $language
196  );
197  // Traverse the selection to build CMD array:
198  foreach ($versions as $table => $records) {
199  foreach ($records as $rec) {
200  $cmd[$table][$rec['uid']]['version'] = ['action' => 'flush'];
201  }
202  }
203  }
204  return $cmd;
205  }
206 
220  public function ‪selectVersionsInWorkspace(int $wsid, int $stage = -99, int $pageId = -1, int $recursionLevel = 0, string $selectionType = 'tables_select', int $language = null): array
221  {
222  $backendUser = $this->‪getBackendUser();
223  ‪$output = [];
224  // Contains either nothing or a list with live-uids
225  if ($pageId != -1 && $recursionLevel > 0) {
226  $pageList = $this->‪getTreeUids($pageId, $wsid, $recursionLevel);
227  } elseif ($pageId != -1) {
228  $pageList = (string)$pageId;
229  } else {
230  $pageList = '';
231  // check if person may only see a "virtual" page-root
232  $mountPoints = array_map('intval', $backendUser->returnWebmounts());
233  $mountPoints = array_unique($mountPoints);
234  if (!in_array(0, $mountPoints)) {
235  $tempPageIds = [];
236  foreach ($mountPoints as $mountPoint) {
237  $tempPageIds[] = $this->‪getTreeUids($mountPoint, $wsid, $recursionLevel);
238  }
239  $pageList = implode(',', $tempPageIds);
240  $pageList = implode(',', array_unique(explode(',', $pageList)));
241  }
242  }
243  // Traversing all tables supporting versioning:
244  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
245  // we do not collect records from tables without permissions on them.
246  if (!$backendUser->check($selectionType, $table)) {
247  continue;
248  }
249  if (BackendUtility::isTableWorkspaceEnabled($table)) {
250  $recs = $this->‪selectAllVersionsFromPages($table, $pageList, $wsid, $stage, $language);
251  $newRecords = $this->‪getNewVersionsForPages($table, $pageList, $wsid, $stage, $language);
252  foreach ($newRecords as &$newRecord) {
253  // If we're dealing with a 'new' record, this one has no t3ver_oid. On publish, there is no
254  // live counterpart, but the publish methods later need a live uid to publish to. We thus
255  // use the uid as t3ver_oid here to be transparent on javascript side.
256  $newRecord['t3ver_oid'] = $newRecord['uid'];
257  }
258  unset($newRecord);
259  $moveRecs = $this->‪getMovedRecordsFromPages($table, $pageList, $wsid, $stage);
260  $recs = array_merge($recs, $newRecords, $moveRecs);
261  $recs = $this->‪filterPermittedElements($recs, $table);
262  if (!empty($recs)) {
263  ‪$output[$table] = $recs;
264  }
265  }
266  }
267  return ‪$output;
268  }
269 
273  protected function ‪selectAllVersionsFromPages(string $table, string $pageList, int $wsid, int $stage, int $language = null): array
274  {
275  // Include root level page as there might be some records with where root level
276  // restriction is ignored (e.g. FAL records)
277  if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
278  $pageList .= ',0';
279  }
280  $isTableLocalizable = BackendUtility::isTableLocalizable($table);
281  $languageParentField = '';
282  // If table is not localizable, but localized records shall
283  // be collected, an empty result array needs to be returned:
284  if ($isTableLocalizable === false && $language > 0) {
285  return [];
286  }
287  if ($isTableLocalizable) {
288  $languageParentField = 'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
289  }
290 
291  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
292  $queryBuilder->getRestrictions()->removeAll()
293  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
294 
295  ‪$fields = ['A.uid', 'A.pid', 'A.t3ver_oid', 'A.t3ver_stage', 'B.pid', 'B.pid AS wspid', 'B.pid AS livepid'];
296  if ($isTableLocalizable) {
297  ‪$fields[] = $languageParentField;
298  ‪$fields[] = 'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
299  }
300  // Table A is the offline version and t3ver_oid>0 defines offline
301  // Table B (online) must have t3ver_oid=0 to signify being online.
302  $constraints = [
303  $queryBuilder->expr()->gt(
304  'A.t3ver_oid',
305  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
306  ),
307  $queryBuilder->expr()->eq(
308  'B.t3ver_oid',
309  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
310  ),
311  $queryBuilder->expr()->neq(
312  'A.t3ver_state',
313  $queryBuilder->createNamedParameter(
314  VersionState::MOVE_POINTER->value,
316  )
317  ),
318  ];
319 
320  if ($pageList) {
321  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
322  if ($table === 'pages') {
323  $constraints[] = $queryBuilder->expr()->or(
324  $queryBuilder->expr()->in(
325  'B.uid',
326  $queryBuilder->createNamedParameter(
327  $pageIdRestriction,
329  )
330  ),
331  $queryBuilder->expr()->in(
332  'B.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
333  $queryBuilder->createNamedParameter(
334  $pageIdRestriction,
336  )
337  )
338  );
339  } else {
340  $constraints[] = $queryBuilder->expr()->in(
341  'B.pid',
342  $queryBuilder->createNamedParameter(
343  $pageIdRestriction,
345  )
346  );
347  }
348  }
349 
350  if ($isTableLocalizable && ‪MathUtility::canBeInterpretedAsInteger($language)) {
351  $constraints[] = $queryBuilder->expr()->eq(
352  'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
353  $queryBuilder->createNamedParameter($language, ‪Connection::PARAM_INT)
354  );
355  }
356 
357  if ($wsid >= 0) {
358  $constraints[] = $queryBuilder->expr()->eq(
359  'A.t3ver_wsid',
360  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
361  );
362  }
363 
364  if ((int)$stage !== -99) {
365  $constraints[] = $queryBuilder->expr()->eq(
366  'A.t3ver_stage',
367  $queryBuilder->createNamedParameter($stage, ‪Connection::PARAM_INT)
368  );
369  }
370 
371  // ... and finally the join between the two tables.
372  $constraints[] = $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'));
373 
374  // Select all records from this table in the database from the workspace
375  // This joins the online version with the offline version as tables A and B
376  // Order by UID, mostly to have a sorting in the backend overview module which
377  // doesn't "jump around" when publishing.
378  $rows = $queryBuilder->select(...‪$fields)
379  ->from($table, 'A')
380  ->from($table, 'B')
381  ->where(...$constraints)
382  ->orderBy('B.uid')
383  ->executeQuery()
384  ->fetchAllAssociative();
385 
386  return $rows;
387  }
388 
393  protected function ‪getNewVersionsForPages(
394  string $table,
395  string $pageList,
396  int $wsid,
397  int $stage,
398  ?int $language
399  ): array {
400  // Include root level page as there might be some records with where root level
401  // restriction is ignored (e.g. FAL records)
402  if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
403  $pageList .= ',0';
404  }
405  $isTableLocalizable = BackendUtility::isTableLocalizable($table);
406  // If table is not localizable, but localized records shall
407  // be collected, an empty result array needs to be returned:
408  if ($isTableLocalizable === false && $language > 0) {
409  return [];
410  }
411 
412  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
413  $transOrigPointerField = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '';
414 
415  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
416  $queryBuilder->getRestrictions()->removeAll()
417  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
418 
419  ‪$fields = ['uid', 'pid', 't3ver_oid', 't3ver_state', 't3ver_stage', 'pid AS wspid', 'pid AS livepid'];
420 
421  // If the table is localizable, $languageField and $transOrigPointerField
422  // are set and should be added to the query
423  if ($isTableLocalizable) {
424  ‪$fields[] = $languageField;
425  ‪$fields[] = $transOrigPointerField;
426  }
427 
428  $constraints = [
429  $queryBuilder->expr()->eq(
430  't3ver_state',
431  $queryBuilder->createNamedParameter(
432  VersionState::NEW_PLACEHOLDER->value,
434  )
435  ),
436  ];
437 
438  if ($pageList) {
439  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
440  if ($table === 'pages' && $transOrigPointerField !== '') {
441  $constraints[] = $queryBuilder->expr()->or(
442  $queryBuilder->expr()->in(
443  'uid',
444  $queryBuilder->createNamedParameter(
445  $pageIdRestriction,
447  )
448  ),
449  $queryBuilder->expr()->in(
450  $transOrigPointerField,
451  $queryBuilder->createNamedParameter(
452  $pageIdRestriction,
454  )
455  )
456  );
457  } else {
458  $constraints[] = $queryBuilder->expr()->in(
459  'pid',
460  $queryBuilder->createNamedParameter(
461  $pageIdRestriction,
463  )
464  );
465  }
466  }
467 
468  if ($isTableLocalizable && ‪MathUtility::canBeInterpretedAsInteger($language)) {
469  $constraints[] = $queryBuilder->expr()->eq(
470  $languageField,
471  $queryBuilder->createNamedParameter((int)$language, ‪Connection::PARAM_INT)
472  );
473  }
474 
475  if ($wsid >= 0) {
476  $constraints[] = $queryBuilder->expr()->eq(
477  't3ver_wsid',
478  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
479  );
480  }
481 
482  if ($stage !== -99) {
483  $constraints[] = $queryBuilder->expr()->eq(
484  't3ver_stage',
485  $queryBuilder->createNamedParameter($stage, ‪Connection::PARAM_INT)
486  );
487  }
488 
489  // Select all records from this table in the database from the workspace
490  // Order by UID, mostly to have a sorting in the backend overview module which
491  // doesn't "jump around" when publishing.
492  return $queryBuilder
493  ->select(...‪$fields)
494  ->from($table)
495  ->where(...$constraints)
496  ->orderBy('uid')
497  ->executeQuery()
498  ->fetchAllAssociative();
499  }
500 
504  protected function ‪getMovedRecordsFromPages(string $table, string $pageList, int $wsid, int $stage): array
505  {
506  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
507  $queryBuilder->getRestrictions()->removeAll()
508  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
509 
510  // Aliases:
511  // B - online record
512  // C - move pointer (t3ver_state = 4)
513  $constraints = [
514  $queryBuilder->expr()->eq(
515  'B.t3ver_state',
516  $queryBuilder->createNamedParameter(
517  VersionState::DEFAULT_STATE->value,
519  )
520  ),
521  $queryBuilder->expr()->eq(
522  'B.t3ver_wsid',
523  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
524  ),
525  $queryBuilder->expr()->eq(
526  'C.t3ver_state',
527  $queryBuilder->createNamedParameter(
528  VersionState::MOVE_POINTER->value,
530  )
531  ),
532  $queryBuilder->expr()->eq('B.uid', $queryBuilder->quoteIdentifier('C.t3ver_oid')),
533  ];
534 
535  if ($wsid >= 0) {
536  $constraints[] = $queryBuilder->expr()->eq(
537  'C.t3ver_wsid',
538  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
539  );
540  }
541 
542  if ((int)$stage !== -99) {
543  $constraints[] = $queryBuilder->expr()->eq(
544  'C.t3ver_stage',
545  $queryBuilder->createNamedParameter($stage, ‪Connection::PARAM_INT)
546  );
547  }
548 
549  if ($pageList) {
550  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
551  if ($table === 'pages') {
552  $constraints[] = $queryBuilder->expr()->or(
553  $queryBuilder->expr()->in(
554  'B.uid',
555  $queryBuilder->createNamedParameter(
556  $pageIdRestriction,
558  )
559  ),
560  $queryBuilder->expr()->in(
561  'C.pid',
562  $queryBuilder->createNamedParameter(
563  $pageIdRestriction,
565  )
566  ),
567  $queryBuilder->expr()->in(
568  'B.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
569  $queryBuilder->createNamedParameter(
570  $pageIdRestriction,
572  )
573  )
574  );
575  } else {
576  $constraints[] = $queryBuilder->expr()->in(
577  'C.pid',
578  $queryBuilder->createNamedParameter(
579  $pageIdRestriction,
581  )
582  );
583  }
584  }
585 
586  $rows = $queryBuilder
587  ->select('C.pid AS wspid', 'B.uid AS t3ver_oid', 'C.uid AS uid', 'B.pid AS livepid', 'C.t3ver_stage as t3ver_stage')
588  ->from($table, 'B')
589  ->from($table, 'C')
590  ->where(...$constraints)
591  ->orderBy('C.uid')
592  ->executeQuery()
593  ->fetchAllAssociative();
594 
595  return $rows;
596  }
597 
603  protected function ‪getTreeUids(int $pageId, int $wsid, int $recursionLevel): string
604  {
605  $backendUser = $this->‪getBackendUser();
606  // Reusing existing functionality with the drawback that
607  // mount points are not covered yet
609  $backendUser->getPagePermsClause(‪Permission::PAGE_SHOW)
610  );
611  if ($pageId > 0) {
612  $pageList = array_merge(
613  [ (int)$pageId ],
614  $this->‪getPageChildrenRecursive((int)$pageId, (int)$recursionLevel, 0, $permsClause)
615  );
616  } else {
617  $mountPoints = $backendUser->uc['pageTree_temporaryMountPoint'];
618  if (!is_array($mountPoints) || empty($mountPoints)) {
619  $mountPoints = array_map('intval', $backendUser->returnWebmounts());
620  $mountPoints = array_unique($mountPoints);
621  }
622  $pageList = [];
623  foreach ($mountPoints as $mountPoint) {
624  $pageList = array_merge(
625  $pageList,
626  [ (int)$mountPoint ],
627  $this->‪getPageChildrenRecursive((int)$mountPoint, (int)$recursionLevel, 0, $permsClause)
628  );
629  }
630  }
631  $pageList = array_unique($pageList);
632 
633  if (BackendUtility::isTableWorkspaceEnabled('pages') && !empty($pageList)) {
634  // Remove the "subbranch" if a page was moved away
635  $pageIds = $pageList;
636  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
637  $queryBuilder->getRestrictions()
638  ->removeAll()
639  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
640  $result = $queryBuilder
641  ->select('uid', 'pid', 't3ver_oid')
642  ->from('pages')
643  ->where(
644  $queryBuilder->expr()->in(
645  't3ver_oid',
646  $queryBuilder->createNamedParameter($pageIds, ‪Connection::PARAM_INT_ARRAY)
647  ),
648  $queryBuilder->expr()->eq(
649  't3ver_wsid',
650  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
651  ),
652  $queryBuilder->expr()->eq(
653  't3ver_state',
654  $queryBuilder->createNamedParameter(VersionState::MOVE_POINTER->value, ‪Connection::PARAM_INT)
655  )
656  )
657  ->orderBy('uid')
658  ->executeQuery();
659 
660  $movedAwayPages = [];
661  while ($row = $result->fetchAssociative()) {
662  $movedAwayPages[$row['t3ver_oid']] = $row;
663  }
664 
665  // move all pages away
666  $newList = array_diff($pageIds, array_keys($movedAwayPages));
667  // keep current page in the list
668  $newList[] = $pageId;
669  // move back in if still connected to the "remaining" pages
670  do {
671  $changed = false;
672  foreach ($movedAwayPages as ‪$uid => $rec) {
673  if (in_array($rec['pid'], $newList) && !in_array(‪$uid, $newList)) {
674  $newList[] = ‪$uid;
675  $changed = true;
676  }
677  }
678  } while ($changed);
679 
680  // In case moving pages is enabled we need to replace all move-to pointer with their origin
681  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
682  $queryBuilder->getRestrictions()
683  ->removeAll()
684  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
685  $result = $queryBuilder->select('uid', 't3ver_oid')
686  ->from('pages')
687  ->where(
688  $queryBuilder->expr()->in(
689  'uid',
690  $queryBuilder->createNamedParameter($newList, ‪Connection::PARAM_INT_ARRAY)
691  )
692  )
693  ->orderBy('uid')
694  ->executeQuery();
695 
696  $pages = [];
697  while ($row = $result->fetchAssociative()) {
698  $pages[$row['uid']] = $row;
699  }
700 
701  $pageIds = $newList;
702  if (!in_array($pageId, $pageIds)) {
703  $pageIds[] = $pageId;
704  }
705 
706  $newList = [];
707  foreach ($pageIds as $pageId) {
708  if ((int)$pages[$pageId]['t3ver_oid'] > 0) {
709  $newList[] = (int)$pages[$pageId]['t3ver_oid'];
710  } else {
711  $newList[] = $pageId;
712  }
713  }
714  $pageList = $newList;
715  }
716 
717  return implode(',', $pageList);
718  }
719 
726  protected function ‪getPageChildrenRecursive(int $pid, int $depth, int $begin, string $permsClause): array
727  {
728  $children = [];
729  if ($pid && $depth > 0) {
730  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
731  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
732  $statement = $queryBuilder->select('uid')
733  ->from('pages')
734  ->where(
735  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, ‪Connection::PARAM_INT)),
736  $queryBuilder->expr()->eq('sys_language_uid', 0),
737  $permsClause
738  )
739  ->executeQuery();
740  while ($row = $statement->fetchAssociative()) {
741  if ($begin <= 0) {
742  $children[] = (int)$row['uid'];
743  }
744  if ($depth > 1) {
745  $theSubList = $this->‪getPageChildrenRecursive((int)$row['uid'], $depth - 1, $begin - 1, $permsClause);
746  $children = array_merge($children, $theSubList);
747  }
748  }
749  }
750  return $children;
751  }
752 
756  protected function ‪filterPermittedElements(array $recs, string $table): array
757  {
758  $permittedElements = [];
759  foreach ($recs as $rec) {
760  if ($this->‪isPageAccessibleForCurrentUser($table, $rec) && $this->‪isLanguageAccessibleForCurrentUser($table, $rec)) {
761  $permittedElements[] = $rec;
762  }
763  }
764  return $permittedElements;
765  }
766 
773  protected function ‪isPageAccessibleForCurrentUser(string $table, array ‪$record): bool
774  {
775  $pageIdField = $table === 'pages' ? 'uid' : 'wspid';
776  $pageId = isset(‪$record[$pageIdField]) ? (int)‪$record[$pageIdField] : null;
777  if ($pageId === null) {
778  return false;
779  }
780  if ($pageId === 0 && BackendUtility::isRootLevelRestrictionIgnored($table)) {
781  return true;
782  }
783  $page = BackendUtility::getRecord('pages', $pageId, 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
784 
785  return $this->‪getBackendUser()->doesUserHaveAccess($page, ‪Permission::PAGE_SHOW);
786  }
787 
794  protected function ‪isLanguageAccessibleForCurrentUser(string $table, array ‪$record): bool
795  {
796  if (BackendUtility::isTableLocalizable($table)) {
797  $languageUid = ‪$record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] ?? 0;
798  } else {
799  return true;
800  }
801  return $this->‪getBackendUser()->checkLanguageAccess($languageUid);
802  }
803 
810  public function ‪isNewPage(int $id, int $language = 0): bool
811  {
812  $isNewPage = false;
813  // If the language is not default, check state of overlay
814  if ($language > 0) {
815  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
816  ->getQueryBuilderForTable('pages');
817  $queryBuilder->getRestrictions()
818  ->removeAll()
819  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
820  $row = $queryBuilder->select('t3ver_state')
821  ->from('pages')
822  ->where(
823  $queryBuilder->expr()->eq(
824  ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
825  $queryBuilder->createNamedParameter($id, ‪Connection::PARAM_INT)
826  ),
827  $queryBuilder->expr()->eq(
828  ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'],
829  $queryBuilder->createNamedParameter($language, ‪Connection::PARAM_INT)
830  ),
831  $queryBuilder->expr()->eq(
832  't3ver_wsid',
833  $queryBuilder->createNamedParameter($this->getCurrentWorkspace(), ‪Connection::PARAM_INT)
834  )
835  )
836  ->setMaxResults(1)
837  ->executeQuery()
838  ->fetchAssociative();
839 
840  if ($row !== false) {
841  $isNewPage = VersionState::tryFrom($row['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER;
842  }
843  } else {
844  $rec = BackendUtility::getRecord('pages', $id, 't3ver_state');
845  if (is_array($rec)) {
846  $isNewPage = VersionState::tryFrom($rec['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER;
847  }
848  }
849  return $isNewPage;
850  }
851 
859  public function ‪hasPageRecordVersions(int $workspaceId, int $pageId): bool
860  {
861  if ($workspaceId === 0 || $pageId === 0) {
862  return false;
863  }
864 
865  if (isset($this->versionsOnPageCache[$workspaceId][$pageId])) {
866  return $this->versionsOnPageCache[$workspaceId][$pageId];
867  }
868 
869  $this->versionsOnPageCache[$workspaceId][$pageId] = false;
870 
871  foreach (‪$GLOBALS['TCA'] as $tableName => $tableConfiguration) {
872  if ($tableName === 'pages' || !BackendUtility::isTableWorkspaceEnabled($tableName)) {
873  continue;
874  }
875 
876  $pages = $this->‪fetchPagesWithVersionsInTable($workspaceId, $tableName);
877  // Early break on first match
878  if (!empty($pages[(string)$pageId])) {
879  $this->versionsOnPageCache[$workspaceId][$pageId] = true;
880  break;
881  }
882  }
883 
884  return $this->versionsOnPageCache[$workspaceId][$pageId];
885  }
886 
906  public function ‪getPagesWithVersionsInTable(int $workspaceId): array
907  {
908  foreach (‪$GLOBALS['TCA'] as $tableName => $tableConfiguration) {
909  if ($tableName === 'pages' || !BackendUtility::isTableWorkspaceEnabled($tableName)) {
910  continue;
911  }
912 
913  $this->‪fetchPagesWithVersionsInTable($workspaceId, $tableName);
914  }
915 
916  return $this->pagesWithVersionsInTable[$workspaceId];
917  }
918 
930  protected function ‪fetchPagesWithVersionsInTable(int $workspaceId, string $tableName): array
931  {
932  if ((int)$workspaceId === 0) {
933  return [];
934  }
935 
936  if (!isset($this->pagesWithVersionsInTable[$workspaceId])) {
937  $this->pagesWithVersionsInTable[$workspaceId] = [];
938  }
939 
940  if (!isset($this->pagesWithVersionsInTable[$workspaceId][$tableName])) {
941  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = [];
942 
943  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
944  $queryBuilder->getRestrictions()
945  ->removeAll()
946  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
947 
948  // Fetch all versioned record within a workspace
949  $result = $queryBuilder
950  ->select('pid')
951  ->from($tableName)
952  ->where(
953  $queryBuilder->expr()->or(
954  $queryBuilder->expr()->eq(
955  't3ver_state',
956  $queryBuilder->createNamedParameter(VersionState::NEW_PLACEHOLDER->value, ‪Connection::PARAM_INT)
957  ),
958  $queryBuilder->expr()->gt(
959  't3ver_oid',
960  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
961  ),
962  ),
963  $queryBuilder->expr()->eq(
964  't3ver_wsid',
965  $queryBuilder->createNamedParameter(
966  $workspaceId,
968  )
969  )
970  )
971  ->groupBy('pid')
972  ->executeQuery();
973 
974  $pageIds = [];
975  while ($row = $result->fetchAssociative()) {
976  $pageIds[$row['pid']] = true;
977  }
978 
979  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = $pageIds;
980  }
981 
982  return $this->pagesWithVersionsInTable[$workspaceId][$tableName];
983  }
984 
986  {
987  return ‪$GLOBALS['LANG'];
988  }
989 
991  {
992  return ‪$GLOBALS['BE_USER'];
993  }
994 }
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isLanguageAccessibleForCurrentUser
‪isLanguageAccessibleForCurrentUser(string $table, array $record)
Definition: WorkspaceService.php:794
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\selectVersionsInWorkspace
‪array selectVersionsInWorkspace(int $wsid, int $stage=-99, int $pageId=-1, int $recursionLevel=0, string $selectionType='tables_select', int $language=null)
Definition: WorkspaceService.php:220
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\hasPageRecordVersions
‪bool hasPageRecordVersions(int $workspaceId, int $pageId)
Definition: WorkspaceService.php:859
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\TABLE_WORKSPACE
‪const TABLE_WORKSPACE
Definition: WorkspaceService.php:42
‪TYPO3\CMS\Workspaces\Service
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getAvailableWorkspaces
‪array getAvailableWorkspaces()
Definition: WorkspaceService.php:55
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\PUBLISH_ACCESS_HIDE_ENTIRE_WORKSPACE_ACTION_DROPDOWN
‪const PUBLISH_ACCESS_HIDE_ENTIRE_WORKSPACE_ACTION_DROPDOWN
Definition: WorkspaceService.php:47
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getBackendUser
‪getBackendUser()
Definition: WorkspaceService.php:990
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\$versionsOnPageCache
‪array $versionsOnPageCache
Definition: WorkspaceService.php:39
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isNewPage
‪isNewPage(int $id, int $language=0)
Definition: WorkspaceService.php:810
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\LIVE_WORKSPACE_ID
‪const LIVE_WORKSPACE_ID
Definition: WorkspaceService.php:43
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getPageChildrenRecursive
‪int[] getPageChildrenRecursive(int $pid, int $depth, int $begin, string $permsClause)
Definition: WorkspaceService.php:726
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\$pagesWithVersionsInTable
‪array $pagesWithVersionsInTable
Definition: WorkspaceService.php:40
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_ID
‪const STAGE_PUBLISH_ID
Definition: StagesService.php:38
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getWorkspaceTitle
‪getWorkspaceTitle(int $wsId)
Definition: WorkspaceService.php:116
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getLanguageService
‪getLanguageService()
Definition: WorkspaceService.php:985
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:32
‪TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction
Definition: RootLevelRestriction.php:27
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getNewVersionsForPages
‪getNewVersionsForPages(string $table, string $pageList, int $wsid, int $stage, ?int $language)
Definition: WorkspaceService.php:393
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getCmdArrayForPublishWS
‪array getCmdArrayForPublishWS(int $wsid, bool $_=false, int $language=null)
Definition: WorkspaceService.php:144
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getCmdArrayForFlushWS
‪array getCmdArrayForFlushWS(int $wsid, int $language=null)
Definition: WorkspaceService.php:182
‪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\WorkspaceService\getCurrentWorkspace
‪getCurrentWorkspace()
Definition: WorkspaceService.php:85
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getMovedRecordsFromPages
‪getMovedRecordsFromPages(string $table, string $pageList, int $wsid, int $stage)
Definition: WorkspaceService.php:504
‪TYPO3\CMS\Workspaces\Service\WorkspaceService
Definition: WorkspaceService.php:38
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\filterPermittedElements
‪filterPermittedElements(array $recs, string $table)
Definition: WorkspaceService.php:756
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\fetchPagesWithVersionsInTable
‪fetchPagesWithVersionsInTable(int $workspaceId, string $tableName)
Definition: WorkspaceService.php:930
‪$output
‪$output
Definition: annotationChecker.php:114
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getPreviewLinkLifetime
‪int getPreviewLinkLifetime()
Definition: WorkspaceService.php:100
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:171
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isPageAccessibleForCurrentUser
‪isPageAccessibleForCurrentUser(string $table, array $record)
Definition: WorkspaceService.php:773
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\PUBLISH_ACCESS_ONLY_WORKSPACE_OWNERS
‪const PUBLISH_ACCESS_ONLY_WORKSPACE_OWNERS
Definition: WorkspaceService.php:46
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\GeneralUtility\intExplode
‪static list< int > intExplode(string $delimiter, string $string, bool $removeEmptyValues=false)
Definition: GeneralUtility.php:756
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\selectAllVersionsFromPages
‪selectAllVersionsFromPages(string $table, string $pageList, int $wsid, int $stage, int $language=null)
Definition: WorkspaceService.php:273
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getTreeUids
‪string getTreeUids(int $pageId, int $wsid, int $recursionLevel)
Definition: WorkspaceService.php:603
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getPagesWithVersionsInTable
‪getPagesWithVersionsInTable(int $workspaceId)
Definition: WorkspaceService.php:906