‪TYPO3CMS  11.5
WorkspaceService.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
31 
36 {
40  protected ‪$versionsOnPageCache = [];
41 
46 
47  public const ‪TABLE_WORKSPACE = 'sys_workspace';
48  public const ‪LIVE_WORKSPACE_ID = 0;
49 
56  public function ‪getAvailableWorkspaces()
57  {
58  $availableWorkspaces = [];
59  // add default workspaces
60  if (‪$GLOBALS['BE_USER']->checkWorkspace(['uid' => (string)self::LIVE_WORKSPACE_ID])) {
61  $availableWorkspaces[‪self::LIVE_WORKSPACE_ID] = ‪self::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 (‪$GLOBALS['BE_USER']->checkWorkspace($workspace)) {
76  $availableWorkspaces[$workspace['uid']] = $workspace['title'];
77  }
78  }
79  return $availableWorkspaces;
80  }
81 
87  public function ‪getCurrentWorkspace()
88  {
89  return ‪$GLOBALS['BE_USER']->workspace;
90  }
91 
102  public function ‪getPreviewLinkLifetime(): int
103  {
104  $workspaceId = ‪$GLOBALS['BE_USER']->workspace;
105  if ($workspaceId > 0) {
106  $wsRecord = BackendUtility::getRecord('sys_workspace', $workspaceId, '*');
107  if (($wsRecord['previewlink_lifetime'] ?? 0) > 0) {
108  return (int)$wsRecord['previewlink_lifetime'];
109  }
110  }
111  $ttlHours = (int)(‪$GLOBALS['BE_USER']->getTSConfig()['options.']['workspaces.']['previewLinkTTLHours'] ?? 0);
112  return $ttlHours ?: 24 * 2;
113  }
114 
122  public static function ‪getWorkspaceTitle($wsId)
123  {
124  $title = false;
125  switch ($wsId) {
127  $title = static::getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:shortcut_onlineWS');
128  break;
129  default:
130  $labelField = ‪$GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
131  $wsRecord = BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
132  if (is_array($wsRecord)) {
133  $title = $wsRecord[$labelField];
134  }
135  }
136  if ($title === false) {
137  throw new \InvalidArgumentException('No such workspace defined', 1476045469);
138  }
139  return $title;
140  }
141 
151  public function ‪getCmdArrayForPublishWS($wsid, $_ = false, $pageId = 0, $language = null)
152  {
153  $wsid = (int)$wsid;
154  $cmd = [];
155  if ($wsid > 0) {
156  // Define stage to select:
157  $stage = -99;
158  $workspaceRec = BackendUtility::getRecord('sys_workspace', $wsid);
159  if ($workspaceRec['publish_access'] & 1) {
161  }
162  // Select all versions to publishing
163  $versions = $this->‪selectVersionsInWorkspace(
164  $wsid,
165  $stage,
166  $pageId ?: -1,
167  999,
168  'tables_modify',
169  $language
170  );
171  // Traverse the selection to build CMD array:
172  foreach ($versions as $table => $records) {
173  foreach ($records as $rec) {
174  // For new records, the live ID is the same as the version ID
175  $liveId = $rec['t3ver_oid'] ?: $rec['uid'];
176  $cmd[$table][$liveId]['version'] = ['action' => 'swap', 'swapWith' => $rec['uid']];
177  }
178  }
179  }
180  return $cmd;
181  }
182 
192  public function ‪getCmdArrayForFlushWS($wsid, $flush = true, $pageId = 0, $language = null)
193  {
194  $wsid = (int)$wsid;
195  $cmd = [];
196  if ($wsid > 0) {
197  // Define stage to select:
198  $stage = -99;
199  // Select all versions to publish
200  $versions = $this->‪selectVersionsInWorkspace(
201  $wsid,
202  $stage,
203  $pageId ?: -1,
204  999,
205  'tables_modify',
206  $language
207  );
208  // Traverse the selection to build CMD array:
209  foreach ($versions as $table => $records) {
210  foreach ($records as $rec) {
211  // Build the cmd Array:
212  $cmd[$table][$rec['uid']]['version'] = ['action' => $flush ? 'flush' : 'clearWSID'];
213  }
214  }
215  }
216  return $cmd;
217  }
218 
232  public function ‪selectVersionsInWorkspace($wsid, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = null)
233  {
234  $wsid = (int)$wsid;
235  ‪$output = [];
236  // Contains either nothing or a list with live-uids
237  if ($pageId != -1 && $recursionLevel > 0) {
238  $pageList = $this->‪getTreeUids($pageId, $wsid, $recursionLevel);
239  } elseif ($pageId != -1) {
240  $pageList = (string)$pageId;
241  } else {
242  $pageList = '';
243  // check if person may only see a "virtual" page-root
244  $mountPoints = array_map('intval', ‪$GLOBALS['BE_USER']->returnWebmounts());
245  $mountPoints = array_unique($mountPoints);
246  if (!in_array(0, $mountPoints)) {
247  $tempPageIds = [];
248  foreach ($mountPoints as $mountPoint) {
249  $tempPageIds[] = $this->‪getTreeUids($mountPoint, $wsid, $recursionLevel);
250  }
251  $pageList = implode(',', $tempPageIds);
252  $pageList = implode(',', array_unique(explode(',', $pageList)));
253  }
254  }
255  // Traversing all tables supporting versioning:
256  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
257  // we do not collect records from tables without permissions on them.
258  if (!‪$GLOBALS['BE_USER']->check($selectionType, $table)) {
259  continue;
260  }
261  if (BackendUtility::isTableWorkspaceEnabled($table)) {
262  $recs = $this->‪selectAllVersionsFromPages($table, $pageList, $wsid, $stage, $language);
263  $newRecords = $this->‪getNewVersionsForPages($table, $pageList, $wsid, (int)$stage, $language);
264  foreach ($newRecords as &$newRecord) {
265  // If we're dealing with a 'new' record, this one has no t3ver_oid. On publish, there is no
266  // live counterpart, but the publish methods later need a live uid to publish to. We thus
267  // use the uid as t3ver_oid here to be transparent on javascript side.
268  $newRecord['t3ver_oid'] = $newRecord['uid'];
269  }
270  unset($newRecord);
271  $moveRecs = $this->‪getMovedRecordsFromPages($table, $pageList, $wsid, $stage);
272  $recs = array_merge($recs, $newRecords, $moveRecs);
273  $recs = $this->‪filterPermittedElements($recs, $table);
274  if (!empty($recs)) {
275  ‪$output[$table] = $recs;
276  }
277  }
278  }
279  return ‪$output;
280  }
281 
292  protected function ‪selectAllVersionsFromPages($table, $pageList, $wsid, $stage, $language = null)
293  {
294  // Include root level page as there might be some records with where root level
295  // restriction is ignored (e.g. FAL records)
296  if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
297  $pageList .= ',0';
298  }
299  $isTableLocalizable = BackendUtility::isTableLocalizable($table);
300  $languageParentField = '';
301  // If table is not localizable, but localized records shall
302  // be collected, an empty result array needs to be returned:
303  if ($isTableLocalizable === false && $language > 0) {
304  return [];
305  }
306  if ($isTableLocalizable) {
307  $languageParentField = 'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
308  }
309 
310  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
311  $queryBuilder->getRestrictions()->removeAll()
312  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
313 
314  ‪$fields = ['A.uid', 'A.pid', 'A.t3ver_oid', 'A.t3ver_stage', 'B.pid', 'B.pid AS wspid', 'B.pid AS livepid'];
315  if ($isTableLocalizable) {
316  ‪$fields[] = $languageParentField;
317  ‪$fields[] = 'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
318  }
319  // Table A is the offline version and t3ver_oid>0 defines offline
320  // Table B (online) must have t3ver_oid=0 to signify being online.
321  $constraints = [
322  $queryBuilder->expr()->gt(
323  'A.t3ver_oid',
324  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
325  ),
326  $queryBuilder->expr()->eq(
327  'B.t3ver_oid',
328  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
329  ),
330  $queryBuilder->expr()->neq(
331  'A.t3ver_state',
332  $queryBuilder->createNamedParameter(
333  (string)new VersionState(‪VersionState::MOVE_POINTER),
335  )
336  ),
337  ];
338 
339  if ($pageList) {
340  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
341  if ($table === 'pages') {
342  $constraints[] = $queryBuilder->expr()->orX(
343  $queryBuilder->expr()->in(
344  'B.uid',
345  $queryBuilder->createNamedParameter(
346  $pageIdRestriction,
347  Connection::PARAM_INT_ARRAY
348  )
349  ),
350  $queryBuilder->expr()->in(
351  'B.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
352  $queryBuilder->createNamedParameter(
353  $pageIdRestriction,
354  Connection::PARAM_INT_ARRAY
355  )
356  )
357  );
358  } else {
359  $constraints[] = $queryBuilder->expr()->in(
360  'B.pid',
361  $queryBuilder->createNamedParameter(
362  $pageIdRestriction,
363  Connection::PARAM_INT_ARRAY
364  )
365  );
366  }
367  }
368 
369  if ($isTableLocalizable && ‪MathUtility::canBeInterpretedAsInteger($language)) {
370  $constraints[] = $queryBuilder->expr()->eq(
371  'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
372  $queryBuilder->createNamedParameter($language, ‪Connection::PARAM_INT)
373  );
374  }
375 
376  if ($wsid >= 0) {
377  $constraints[] = $queryBuilder->expr()->eq(
378  'A.t3ver_wsid',
379  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
380  );
381  }
382 
383  if ((int)$stage !== -99) {
384  $constraints[] = $queryBuilder->expr()->eq(
385  'A.t3ver_stage',
386  $queryBuilder->createNamedParameter($stage, ‪Connection::PARAM_INT)
387  );
388  }
389 
390  // ... and finally the join between the two tables.
391  $constraints[] = $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'));
392 
393  // Select all records from this table in the database from the workspace
394  // This joins the online version with the offline version as tables A and B
395  // Order by UID, mostly to have a sorting in the backend overview module which
396  // doesn't "jump around" when publishing.
397  $rows = $queryBuilder->select(...‪$fields)
398  ->from($table, 'A')
399  ->from($table, 'B')
400  ->where(...$constraints)
401  ->orderBy('B.uid')
402  ->executeQuery()
403  ->fetchAllAssociative();
404 
405  return $rows;
406  }
407 
419  protected function ‪getNewVersionsForPages(
420  string $table,
421  string $pageList,
422  int $wsid,
423  int $stage,
424  ?int $language
425  ): array {
426  // Include root level page as there might be some records with where root level
427  // restriction is ignored (e.g. FAL records)
428  if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
429  $pageList .= ',0';
430  }
431  $isTableLocalizable = BackendUtility::isTableLocalizable($table);
432  // If table is not localizable, but localized records shall
433  // be collected, an empty result array needs to be returned:
434  if ($isTableLocalizable === false && $language > 0) {
435  return [];
436  }
437 
438  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
439  $transOrigPointerField = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '';
440 
441  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
442  $queryBuilder->getRestrictions()->removeAll()
443  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
444 
445  ‪$fields = ['uid', 'pid', 't3ver_oid', 't3ver_state', 't3ver_stage', 'pid AS wspid', 'pid AS livepid'];
446 
447  // If the table is localizable, $languageField and $transOrigPointerField
448  // are set and should be added to the query
449  if ($isTableLocalizable) {
450  ‪$fields[] = $languageField;
451  ‪$fields[] = $transOrigPointerField;
452  }
453 
454  $constraints = [
455  $queryBuilder->expr()->eq(
456  't3ver_state',
457  $queryBuilder->createNamedParameter(
460  )
461  ),
462  ];
463 
464  if ($pageList) {
465  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
466  if ($table === 'pages' && $transOrigPointerField !== '') {
467  $constraints[] = $queryBuilder->expr()->orX(
468  $queryBuilder->expr()->in(
469  'uid',
470  $queryBuilder->createNamedParameter(
471  $pageIdRestriction,
472  Connection::PARAM_INT_ARRAY
473  )
474  ),
475  $queryBuilder->expr()->in(
476  $transOrigPointerField,
477  $queryBuilder->createNamedParameter(
478  $pageIdRestriction,
479  Connection::PARAM_INT_ARRAY
480  )
481  )
482  );
483  } else {
484  $constraints[] = $queryBuilder->expr()->in(
485  'pid',
486  $queryBuilder->createNamedParameter(
487  $pageIdRestriction,
488  Connection::PARAM_INT_ARRAY
489  )
490  );
491  }
492  }
493 
494  if ($isTableLocalizable && ‪MathUtility::canBeInterpretedAsInteger($language)) {
495  $constraints[] = $queryBuilder->expr()->eq(
496  $languageField,
497  $queryBuilder->createNamedParameter((int)$language, ‪Connection::PARAM_INT)
498  );
499  }
500 
501  if ($wsid >= 0) {
502  $constraints[] = $queryBuilder->expr()->eq(
503  't3ver_wsid',
504  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
505  );
506  }
507 
508  if ($stage !== -99) {
509  $constraints[] = $queryBuilder->expr()->eq(
510  't3ver_stage',
511  $queryBuilder->createNamedParameter($stage, ‪Connection::PARAM_INT)
512  );
513  }
514 
515  // Select all records from this table in the database from the workspace
516  // Order by UID, mostly to have a sorting in the backend overview module which
517  // doesn't "jump around" when publishing.
518  return $queryBuilder
519  ->select(...‪$fields)
520  ->from($table)
521  ->where(...$constraints)
522  ->orderBy('uid')
523  ->executeQuery()
524  ->fetchAllAssociative();
525  }
526 
536  protected function ‪getMovedRecordsFromPages($table, $pageList, $wsid, $stage)
537  {
538  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
539  $queryBuilder->getRestrictions()->removeAll()
540  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
541 
542  // Aliases:
543  // B - online record
544  // C - move pointer (t3ver_state = 4)
545  $constraints = [
546  $queryBuilder->expr()->eq(
547  'B.t3ver_state',
548  $queryBuilder->createNamedParameter(
549  (string)new VersionState(‪VersionState::DEFAULT_STATE),
551  )
552  ),
553  $queryBuilder->expr()->eq(
554  'B.t3ver_wsid',
555  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
556  ),
557  $queryBuilder->expr()->eq(
558  'C.t3ver_state',
559  $queryBuilder->createNamedParameter(
560  (string)new VersionState(‪VersionState::MOVE_POINTER),
562  )
563  ),
564  $queryBuilder->expr()->eq('B.uid', $queryBuilder->quoteIdentifier('C.t3ver_oid')),
565  ];
566 
567  if ($wsid >= 0) {
568  $constraints[] = $queryBuilder->expr()->eq(
569  'C.t3ver_wsid',
570  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
571  );
572  }
573 
574  if ((int)$stage !== -99) {
575  $constraints[] = $queryBuilder->expr()->eq(
576  'C.t3ver_stage',
577  $queryBuilder->createNamedParameter($stage, ‪Connection::PARAM_INT)
578  );
579  }
580 
581  if ($pageList) {
582  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
583  if ($table === 'pages') {
584  $constraints[] = $queryBuilder->expr()->orX(
585  $queryBuilder->expr()->in(
586  'B.uid',
587  $queryBuilder->createNamedParameter(
588  $pageIdRestriction,
589  Connection::PARAM_INT_ARRAY
590  )
591  ),
592  $queryBuilder->expr()->in(
593  'C.pid',
594  $queryBuilder->createNamedParameter(
595  $pageIdRestriction,
596  Connection::PARAM_INT_ARRAY
597  )
598  ),
599  $queryBuilder->expr()->in(
600  'B.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
601  $queryBuilder->createNamedParameter(
602  $pageIdRestriction,
603  Connection::PARAM_INT_ARRAY
604  )
605  )
606  );
607  } else {
608  $constraints[] = $queryBuilder->expr()->in(
609  'C.pid',
610  $queryBuilder->createNamedParameter(
611  $pageIdRestriction,
612  Connection::PARAM_INT_ARRAY
613  )
614  );
615  }
616  }
617 
618  $rows = $queryBuilder
619  ->select('C.pid AS wspid', 'B.uid AS t3ver_oid', 'C.uid AS uid', 'B.pid AS livepid', 'C.t3ver_stage as t3ver_stage')
620  ->from($table, 'B')
621  ->from($table, 'C')
622  ->where(...$constraints)
623  ->orderBy('C.uid')
624  ->executeQuery()
625  ->fetchAllAssociative();
626 
627  return $rows;
628  }
629 
638  protected function ‪getTreeUids($pageId, $wsid, $recursionLevel)
639  {
640  // Reusing existing functionality with the drawback that
641  // mount points are not covered yet
643  ‪$GLOBALS['BE_USER']->getPagePermsClause(‪Permission::PAGE_SHOW)
644  );
645  if ($pageId > 0) {
646  $pageList = array_merge(
647  [ (int)$pageId ],
648  $this->‪getPageChildrenRecursive((int)$pageId, (int)$recursionLevel, 0, $permsClause)
649  );
650  } else {
651  $mountPoints = ‪$GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
652  if (!is_array($mountPoints) || empty($mountPoints)) {
653  $mountPoints = array_map('intval', ‪$GLOBALS['BE_USER']->returnWebmounts());
654  $mountPoints = array_unique($mountPoints);
655  }
656  $pageList = [];
657  foreach ($mountPoints as $mountPoint) {
658  $pageList = array_merge(
659  $pageList,
660  [ (int)$mountPoint ],
661  $this->‪getPageChildrenRecursive((int)$mountPoint, (int)$recursionLevel, 0, $permsClause)
662  );
663  }
664  }
665  $pageList = array_unique($pageList);
666 
667  if (BackendUtility::isTableWorkspaceEnabled('pages') && !empty($pageList)) {
668  // Remove the "subbranch" if a page was moved away
669  $pageIds = $pageList;
670  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
671  $queryBuilder->getRestrictions()
672  ->removeAll()
673  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
674  $result = $queryBuilder
675  ->select('uid', 'pid', 't3ver_oid')
676  ->from('pages')
677  ->where(
678  $queryBuilder->expr()->in(
679  't3ver_oid',
680  $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
681  ),
682  $queryBuilder->expr()->eq(
683  't3ver_wsid',
684  $queryBuilder->createNamedParameter($wsid, ‪Connection::PARAM_INT)
685  ),
686  $queryBuilder->expr()->eq(
687  't3ver_state',
688  $queryBuilder->createNamedParameter(‪VersionState::MOVE_POINTER, ‪Connection::PARAM_INT)
689  )
690  )
691  ->orderBy('uid')
692  ->executeQuery();
693 
694  $movedAwayPages = [];
695  while ($row = $result->fetchAssociative()) {
696  $movedAwayPages[$row['t3ver_oid']] = $row;
697  }
698 
699  // move all pages away
700  $newList = array_diff($pageIds, array_keys($movedAwayPages));
701  // keep current page in the list
702  $newList[] = $pageId;
703  // move back in if still connected to the "remaining" pages
704  do {
705  $changed = false;
706  foreach ($movedAwayPages as $uid => $rec) {
707  if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
708  $newList[] = $uid;
709  $changed = true;
710  }
711  }
712  } while ($changed);
713 
714  // In case moving pages is enabled we need to replace all move-to pointer with their origin
715  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
716  $queryBuilder->getRestrictions()
717  ->removeAll()
718  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
719  $result = $queryBuilder->select('uid', 't3ver_oid')
720  ->from('pages')
721  ->where(
722  $queryBuilder->expr()->in(
723  'uid',
724  $queryBuilder->createNamedParameter($newList, Connection::PARAM_INT_ARRAY)
725  )
726  )
727  ->orderBy('uid')
728  ->executeQuery();
729 
730  $pages = [];
731  while ($row = $result->fetchAssociative()) {
732  $pages[$row['uid']] = $row;
733  }
734 
735  $pageIds = $newList;
736  if (!in_array($pageId, $pageIds)) {
737  $pageIds[] = $pageId;
738  }
739 
740  $newList = [];
741  foreach ($pageIds as $pageId) {
742  if ((int)$pages[$pageId]['t3ver_oid'] > 0) {
743  $newList[] = (int)$pages[$pageId]['t3ver_oid'];
744  } else {
745  $newList[] = $pageId;
746  }
747  }
748  $pageList = $newList;
749  }
750 
751  return implode(',', $pageList);
752  }
753 
763  protected function ‪getPageChildrenRecursive(int $pid, int $depth, int $begin, string $permsClause): array
764  {
765  $children = [];
766  if ($pid && $depth > 0) {
767  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
768  $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
769  $statement = $queryBuilder->select('uid')
770  ->from('pages')
771  ->where(
772  $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, ‪Connection::PARAM_INT)),
773  $queryBuilder->expr()->eq('sys_language_uid', 0),
774  $permsClause
775  )
776  ->executeQuery();
777  while ($row = $statement->fetchAssociative()) {
778  if ($begin <= 0) {
779  $children[] = (int)$row['uid'];
780  }
781  if ($depth > 1) {
782  $theSubList = $this->‪getPageChildrenRecursive((int)$row['uid'], $depth - 1, $begin - 1, $permsClause);
783  $children = array_merge($children, $theSubList);
784  }
785  }
786  }
787  return $children;
788  }
789 
797  protected function ‪filterPermittedElements($recs, $table)
798  {
799  $permittedElements = [];
800  if (is_array($recs)) {
801  foreach ($recs as $rec) {
802  if ($this->‪isPageAccessibleForCurrentUser($table, $rec) && $this->‪isLanguageAccessibleForCurrentUser($table, $rec)) {
803  $permittedElements[] = $rec;
804  }
805  }
806  }
807  return $permittedElements;
808  }
809 
817  protected function ‪isPageAccessibleForCurrentUser($table, array $record)
818  {
819  $pageIdField = $table === 'pages' ? 'uid' : 'wspid';
820  $pageId = isset($record[$pageIdField]) ? (int)$record[$pageIdField] : null;
821  if ($pageId === null) {
822  return false;
823  }
824  if ($pageId === 0 && BackendUtility::isRootLevelRestrictionIgnored($table)) {
825  return true;
826  }
827  $page = BackendUtility::getRecord('pages', $pageId, 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
828 
829  return ‪$GLOBALS['BE_USER']->doesUserHaveAccess($page, ‪Permission::PAGE_SHOW);
830  }
831 
839  protected function ‪isLanguageAccessibleForCurrentUser($table, array $record)
840  {
841  if (BackendUtility::isTableLocalizable($table)) {
842  $languageUid = $record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']] ?? 0;
843  } else {
844  return true;
845  }
846  return ‪$GLOBALS['BE_USER']->checkLanguageAccess($languageUid);
847  }
848 
856  public static function ‪isNewPage($id, $language = 0)
857  {
858  $isNewPage = false;
859  // If the language is not default, check state of overlay
860  if ($language > 0) {
861  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
862  ->getQueryBuilderForTable('pages');
863  $queryBuilder->getRestrictions()
864  ->removeAll()
865  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
866  $row = $queryBuilder->select('t3ver_state')
867  ->from('pages')
868  ->where(
869  $queryBuilder->expr()->eq(
870  ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
871  $queryBuilder->createNamedParameter($id, ‪Connection::PARAM_INT)
872  ),
873  $queryBuilder->expr()->eq(
874  ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'],
875  $queryBuilder->createNamedParameter($language, ‪Connection::PARAM_INT)
876  ),
877  $queryBuilder->expr()->eq(
878  't3ver_wsid',
879  $queryBuilder->createNamedParameter(‪$GLOBALS['BE_USER']->workspace, ‪Connection::PARAM_INT)
880  )
881  )
882  ->setMaxResults(1)
883  ->executeQuery()
884  ->fetchAssociative();
885 
886  if ($row !== false) {
887  $isNewPage = ‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::NEW_PLACEHOLDER);
888  }
889  } else {
890  $rec = BackendUtility::getRecord('pages', $id, 't3ver_state');
891  if (is_array($rec)) {
892  $isNewPage = ‪VersionState::cast($rec['t3ver_state'])->equals(‪VersionState::NEW_PLACEHOLDER);
893  }
894  }
895  return $isNewPage;
896  }
897 
905  public function ‪hasPageRecordVersions($workspaceId, $pageId)
906  {
907  if ((int)$workspaceId === 0 || (int)$pageId === 0) {
908  return false;
909  }
910 
911  if (isset($this->versionsOnPageCache[$workspaceId][$pageId])) {
912  return $this->versionsOnPageCache[$workspaceId][$pageId];
913  }
914 
915  $this->versionsOnPageCache[$workspaceId][$pageId] = false;
916 
917  foreach (‪$GLOBALS['TCA'] as $tableName => $tableConfiguration) {
918  if ($tableName === 'pages' || !BackendUtility::isTableWorkspaceEnabled($tableName)) {
919  continue;
920  }
921 
922  $pages = $this->‪fetchPagesWithVersionsInTable($workspaceId, $tableName);
923  // Early break on first match
924  if (!empty($pages[(string)$pageId])) {
925  $this->versionsOnPageCache[$workspaceId][$pageId] = true;
926  break;
927  }
928  }
929 
930  $parameters = [
931  'workspaceId' => $workspaceId,
932  'pageId' => $pageId,
933  'versionsOnPageCache' => &‪$this->versionsOnPageCache,
934  ];
935  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Workspaces\Service\WorkspaceService::class]['hasPageRecordVersions'] ?? [] as $hookFunction) {
936  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
937  }
938 
939  return $this->versionsOnPageCache[$workspaceId][$pageId];
940  }
941 
965  public function ‪getPagesWithVersionsInTable($workspaceId)
966  {
967  foreach (‪$GLOBALS['TCA'] as $tableName => $tableConfiguration) {
968  if ($tableName === 'pages' || !BackendUtility::isTableWorkspaceEnabled($tableName)) {
969  continue;
970  }
971 
972  $this->‪fetchPagesWithVersionsInTable($workspaceId, $tableName);
973  }
974 
975  return $this->pagesWithVersionsInTable[$workspaceId];
976  }
977 
993  protected function ‪fetchPagesWithVersionsInTable($workspaceId, $tableName)
994  {
995  if ((int)$workspaceId === 0) {
996  return [];
997  }
998 
999  if (!isset($this->pagesWithVersionsInTable[$workspaceId])) {
1000  $this->pagesWithVersionsInTable[$workspaceId] = [];
1001  }
1002 
1003  if (!isset($this->pagesWithVersionsInTable[$workspaceId][$tableName])) {
1004  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = [];
1005 
1006  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
1007  $queryBuilder->getRestrictions()
1008  ->removeAll()
1009  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1010 
1011  // Fetch all versioned record within a workspace
1012  $result = $queryBuilder
1013  ->select('pid')
1014  ->from($tableName)
1015  ->where(
1016  $queryBuilder->expr()->orX(
1017  $queryBuilder->expr()->eq(
1018  't3ver_state',
1019  $queryBuilder->createNamedParameter(‪VersionState::NEW_PLACEHOLDER, ‪Connection::PARAM_INT)
1020  ),
1021  $queryBuilder->expr()->gt(
1022  't3ver_oid',
1023  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1024  ),
1025  ),
1026  $queryBuilder->expr()->eq(
1027  't3ver_wsid',
1028  $queryBuilder->createNamedParameter(
1029  $workspaceId,
1031  )
1032  )
1033  )
1034  ->groupBy('pid')
1035  ->executeQuery();
1036 
1037  $pageIds = [];
1038  while ($row = $result->fetchAssociative()) {
1039  $pageIds[$row['pid']] = true;
1040  }
1041 
1042  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = $pageIds;
1043 
1044  $parameters = [
1045  'workspaceId' => $workspaceId,
1046  'tableName' => $tableName,
1047  'pagesWithVersionsInTable' => &‪$this->pagesWithVersionsInTable,
1048  ];
1049  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Workspaces\Service\WorkspaceService::class]['fetchPagesWithVersionsInTable'] ?? [] as $hookFunction) {
1050  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
1051  }
1052  }
1053 
1054  return $this->pagesWithVersionsInTable[$workspaceId][$tableName];
1055  }
1056 
1061  protected function ‪createQueryBuilderForTable(string $tableName)
1062  {
1063  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1064  ->getQueryBuilderForTable($tableName);
1065  $queryBuilder->getRestrictions()
1066  ->removeAll()
1067  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1068  return $queryBuilder;
1069  }
1070 
1074  protected static function ‪getLanguageService(): ?LanguageService
1075  {
1076  return ‪$GLOBALS['LANG'] ?? null;
1077  }
1078 }
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\hasPageRecordVersions
‪bool hasPageRecordVersions($workspaceId, $pageId)
Definition: WorkspaceService.php:903
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getNewVersionsForPages
‪array getNewVersionsForPages(string $table, string $pageList, int $wsid, int $stage, ?int $language)
Definition: WorkspaceService.php:417
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isLanguageAccessibleForCurrentUser
‪bool isLanguageAccessibleForCurrentUser($table, array $record)
Definition: WorkspaceService.php:837
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getTreeUids
‪string getTreeUids($pageId, $wsid, $recursionLevel)
Definition: WorkspaceService.php:636
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:53
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\selectVersionsInWorkspace
‪array selectVersionsInWorkspace($wsid, $stage=-99, $pageId=-1, $recursionLevel=0, $selectionType='tables_select', $language=null)
Definition: WorkspaceService.php:230
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\TABLE_WORKSPACE
‪const TABLE_WORKSPACE
Definition: WorkspaceService.php:45
‪TYPO3\CMS\Workspaces\Service
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getAvailableWorkspaces
‪array getAvailableWorkspaces()
Definition: WorkspaceService.php:54
‪TYPO3
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\fetchPagesWithVersionsInTable
‪array fetchPagesWithVersionsInTable($workspaceId, $tableName)
Definition: WorkspaceService.php:991
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\$versionsOnPageCache
‪array $versionsOnPageCache
Definition: WorkspaceService.php:39
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getCurrentWorkspace
‪int getCurrentWorkspace()
Definition: WorkspaceService.php:85
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getCmdArrayForFlushWS
‪array getCmdArrayForFlushWS($wsid, $flush=true, $pageId=0, $language=null)
Definition: WorkspaceService.php:190
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\LIVE_WORKSPACE_ID
‪const LIVE_WORKSPACE_ID
Definition: WorkspaceService.php:46
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:78
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\selectAllVersionsFromPages
‪array selectAllVersionsFromPages($table, $pageList, $wsid, $stage, $language=null)
Definition: WorkspaceService.php:290
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isPageAccessibleForCurrentUser
‪bool isPageAccessibleForCurrentUser($table, array $record)
Definition: WorkspaceService.php:815
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getPageChildrenRecursive
‪int[] getPageChildrenRecursive(int $pid, int $depth, int $begin, string $permsClause)
Definition: WorkspaceService.php:761
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\$pagesWithVersionsInTable
‪array $pagesWithVersionsInTable
Definition: WorkspaceService.php:43
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_ID
‪const STAGE_PUBLISH_ID
Definition: StagesService.php:37
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getMovedRecordsFromPages
‪array getMovedRecordsFromPages($table, $pageList, $wsid, $stage)
Definition: WorkspaceService.php:534
‪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\getPagesWithVersionsInTable
‪array getPagesWithVersionsInTable($workspaceId)
Definition: WorkspaceService.php:963
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\filterPermittedElements
‪array filterPermittedElements($recs, $table)
Definition: WorkspaceService.php:795
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Workspaces\Service\WorkspaceService
Definition: WorkspaceService.php:36
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪$output
‪$output
Definition: annotationChecker.php:121
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isNewPage
‪static bool isNewPage($id, $language=0)
Definition: WorkspaceService.php:854
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getPreviewLinkLifetime
‪int getPreviewLinkLifetime()
Definition: WorkspaceService.php:100
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:171
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:44
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getCmdArrayForPublishWS
‪array getCmdArrayForPublishWS($wsid, $_=false, $pageId=0, $language=null)
Definition: WorkspaceService.php:149
‪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\Core\Utility\GeneralUtility\intExplode
‪static int[] intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:927
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getLanguageService
‪static LanguageService null getLanguageService()
Definition: WorkspaceService.php:1072
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getWorkspaceTitle
‪static string getWorkspaceTitle($wsId)
Definition: WorkspaceService.php:120
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\createQueryBuilderForTable
‪QueryBuilder createQueryBuilderForTable(string $tableName)
Definition: WorkspaceService.php:1059