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