‪TYPO3CMS  10.4
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 
31 
36 {
40  protected ‪$versionsOnPageCache = [];
41 
46 
47  const ‪TABLE_WORKSPACE = 'sys_workspace';
48  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  ->execute();
73 
74  while ($workspace = $result->fetch()) {
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, $doSwap, $pageId = 0, $language = null)
152  {
153  $wsid = (int)$wsid;
154  $cmd = [];
155  if ($wsid > 0) {
156  // Define stage to select:
157  $stage = -99;
158  if ($wsid > 0) {
159  $workspaceRec = ‪BackendUtility::getRecord('sys_workspace', $wsid);
160  if ($workspaceRec['publish_access'] & 1) {
162  }
163  }
164  // Select all versions to swap:
165  $versions = $this->‪selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
166  // Traverse the selection to build CMD array:
167  foreach ($versions as $table => $records) {
168  foreach ($records as $rec) {
169  // Build the cmd Array:
170  $cmd[$table][$rec['t3ver_oid']]['version'] = ['action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0];
171  }
172  }
173  }
174  return $cmd;
175  }
176 
186  public function ‪getCmdArrayForFlushWS($wsid, $flush = true, $pageId = 0, $language = null)
187  {
188  $wsid = (int)$wsid;
189  $cmd = [];
190  if ($wsid > 0) {
191  // Define stage to select:
192  $stage = -99;
193  // Select all versions to swap:
194  $versions = $this->‪selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
195  // Traverse the selection to build CMD array:
196  foreach ($versions as $table => $records) {
197  foreach ($records as $rec) {
198  // Build the cmd Array:
199  $cmd[$table][$rec['uid']]['version'] = ['action' => $flush ? 'flush' : 'clearWSID'];
200  }
201  }
202  }
203  return $cmd;
204  }
205 
220  public function ‪selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = null)
221  {
222  $wsid = (int)$wsid;
223  $filter = (int)$filter;
224  ‪$output = [];
225  // Contains either nothing or a list with live-uids
226  if ($pageId != -1 && $recursionLevel > 0) {
227  $pageList = $this->‪getTreeUids($pageId, $wsid, $recursionLevel);
228  } elseif ($pageId != -1) {
229  $pageList = (string)$pageId;
230  } else {
231  $pageList = '';
232  // check if person may only see a "virtual" page-root
233  $mountPoints = array_map('intval', ‪$GLOBALS['BE_USER']->returnWebmounts());
234  $mountPoints = array_unique($mountPoints);
235  if (!in_array(0, $mountPoints)) {
236  $tempPageIds = [];
237  foreach ($mountPoints as $mountPoint) {
238  $tempPageIds[] = $this->‪getTreeUids($mountPoint, $wsid, $recursionLevel);
239  }
240  $pageList = implode(',', $tempPageIds);
241  $pageList = implode(',', array_unique(explode(',', $pageList)));
242  }
243  }
244  // Traversing all tables supporting versioning:
245  foreach (‪$GLOBALS['TCA'] as $table => $cfg) {
246  // we do not collect records from tables without permissions on them.
247  if (!‪$GLOBALS['BE_USER']->check($selectionType, $table)) {
248  continue;
249  }
251  $recs = $this->‪selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language);
252  $moveRecs = $this->‪getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
253  $recs = array_merge($recs, $moveRecs);
254  $recs = $this->‪filterPermittedElements($recs, $table);
255  if (!empty($recs)) {
256  ‪$output[$table] = $recs;
257  }
258  }
259  }
260  return ‪$output;
261  }
262 
274  protected function ‪selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language = null)
275  {
276  // Include root level page as there might be some records with where root level
277  // restriction is ignored (e.g. FAL records)
278  if ($pageList !== '' && ‪BackendUtility::isRootLevelRestrictionIgnored($table)) {
279  $pageList .= ',0';
280  }
281  $isTableLocalizable = ‪BackendUtility::isTableLocalizable($table);
282  $languageParentField = '';
283  // If table is not localizable, but localized records shall
284  // be collected, an empty result array needs to be returned:
285  if ($isTableLocalizable === false && $language > 0) {
286  return [];
287  }
288  if ($isTableLocalizable) {
289  $languageParentField = 'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
290  }
291 
292  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
293  $queryBuilder->getRestrictions()->removeAll()
294  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
295 
296  ‪$fields = ['A.uid', 'A.pid', 'A.t3ver_oid', 'A.t3ver_stage', 'B.pid', 'B.pid AS wspid', 'B.pid AS livepid'];
297  if ($isTableLocalizable) {
298  ‪$fields[] = $languageParentField;
299  ‪$fields[] = 'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'];
300  }
301  // Table A is the offline version and t3ver_oid>0 defines offline
302  // Table B (online) must have t3ver_oid=0 to signify being online.
303  $constraints = [
304  $queryBuilder->expr()->gt(
305  'A.t3ver_oid',
306  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
307  ),
308  $queryBuilder->expr()->eq(
309  'B.t3ver_oid',
310  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
311  ),
312  $queryBuilder->expr()->neq(
313  'A.t3ver_state',
314  $queryBuilder->createNamedParameter(
315  (string)new VersionState(‪VersionState::MOVE_POINTER),
316  \PDO::PARAM_INT
317  )
318  )
319  ];
320 
321  if ($pageList) {
322  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
323  if ($table === 'pages') {
324  $constraints[] = $queryBuilder->expr()->orX(
325  $queryBuilder->expr()->in(
326  'B.uid',
327  $queryBuilder->createNamedParameter(
328  $pageIdRestriction,
329  Connection::PARAM_INT_ARRAY
330  )
331  ),
332  $queryBuilder->expr()->in(
333  'B.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
334  $queryBuilder->createNamedParameter(
335  $pageIdRestriction,
336  Connection::PARAM_INT_ARRAY
337  )
338  )
339  );
340  } else {
341  $constraints[] = $queryBuilder->expr()->in(
342  'B.pid',
343  $queryBuilder->createNamedParameter(
344  $pageIdRestriction,
345  Connection::PARAM_INT_ARRAY
346  )
347  );
348  }
349  }
350 
351  if ($isTableLocalizable && ‪MathUtility::canBeInterpretedAsInteger($language)) {
352  $constraints[] = $queryBuilder->expr()->eq(
353  'A.' . ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
354  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
355  );
356  }
357 
358  if ($wsid >= 0) {
359  $constraints[] = $queryBuilder->expr()->eq(
360  'A.t3ver_wsid',
361  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
362  );
363  }
364 
365  // lifecycle filter:
366  // 1 = select all drafts (never-published),
367  // 2 = select all published one or more times (archive/multiple)
368  if ($filter === 1) {
369  $constraints[] = $queryBuilder->expr()->eq(
370  'A.t3ver_count',
371  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
372  );
373  } elseif ($filter === 2) {
374  $constraints[] = $queryBuilder->expr()->gt(
375  'A.t3ver_count',
376  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
377  );
378  }
379 
380  if ((int)$stage !== -99) {
381  $constraints[] = $queryBuilder->expr()->eq(
382  'A.t3ver_stage',
383  $queryBuilder->createNamedParameter($stage, \PDO::PARAM_INT)
384  );
385  }
386 
387  // ... and finally the join between the two tables.
388  $constraints[] = $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'));
389 
390  // Select all records from this table in the database from the workspace
391  // This joins the online version with the offline version as tables A and B
392  // Order by UID, mostly to have a sorting in the backend overview module which
393  // doesn't "jump around" when swapping.
394  $rows = $queryBuilder->select(...‪$fields)
395  ->from($table, 'A')
396  ->from($table, 'B')
397  ->where(...$constraints)
398  ->orderBy('B.uid')
399  ->execute()
400  ->fetchAll();
401 
402  return $rows;
403  }
404 
415  protected function ‪getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage)
416  {
417  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
418  $queryBuilder->getRestrictions()->removeAll()
419  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
420 
421  // Aliases:
422  // A - moveTo placeholder
423  // B - online record
424  // C - moveFrom placeholder
425  $constraints = [
426  $queryBuilder->expr()->eq(
427  'A.t3ver_state',
428  $queryBuilder->createNamedParameter(
429  (string)new VersionState(‪VersionState::MOVE_PLACEHOLDER),
430  \PDO::PARAM_INT
431  )
432  ),
433  $queryBuilder->expr()->gt(
434  'B.pid',
435  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
436  ),
437  $queryBuilder->expr()->eq(
438  'B.t3ver_state',
439  $queryBuilder->createNamedParameter(
440  (string)new VersionState(‪VersionState::DEFAULT_STATE),
441  \PDO::PARAM_INT
442  )
443  ),
444  $queryBuilder->expr()->eq(
445  'B.t3ver_wsid',
446  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
447  ),
448  $queryBuilder->expr()->eq(
449  'C.t3ver_state',
450  $queryBuilder->createNamedParameter(
451  (string)new VersionState(‪VersionState::MOVE_POINTER),
452  \PDO::PARAM_INT
453  )
454  ),
455  $queryBuilder->expr()->eq('A.t3ver_move_id', $queryBuilder->quoteIdentifier('B.uid')),
456  $queryBuilder->expr()->eq('B.uid', $queryBuilder->quoteIdentifier('C.t3ver_oid'))
457  ];
458 
459  if ($wsid >= 0) {
460  $constraints[] = $queryBuilder->expr()->eq(
461  'A.t3ver_wsid',
462  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
463  );
464  $constraints[] = $queryBuilder->expr()->eq(
465  'C.t3ver_wsid',
466  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
467  );
468  }
469 
470  // lifecycle filter:
471  // 1 = select all drafts (never-published),
472  // 2 = select all published one or more times (archive/multiple)
473  if ($filter === 1) {
474  $constraints[] = $queryBuilder->expr()->eq(
475  'C.t3ver_count',
476  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
477  );
478  } elseif ($filter === 2) {
479  $constraints[] = $queryBuilder->expr()->gt(
480  'C.t3ver_count',
481  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
482  );
483  }
484 
485  if ((int)$stage != -99) {
486  $constraints[] = $queryBuilder->expr()->eq(
487  'C.t3ver_stage',
488  $queryBuilder->createNamedParameter($stage, \PDO::PARAM_INT)
489  );
490  }
491 
492  if ($pageList) {
493  $pageIdRestriction = ‪GeneralUtility::intExplode(',', $pageList, true);
494  if ($table === 'pages') {
495  $constraints[] = $queryBuilder->expr()->orX(
496  $queryBuilder->expr()->in(
497  'B.uid',
498  $queryBuilder->createNamedParameter(
499  $pageIdRestriction,
500  Connection::PARAM_INT_ARRAY
501  )
502  ),
503  $queryBuilder->expr()->in(
504  'B.' . ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
505  $queryBuilder->createNamedParameter(
506  $pageIdRestriction,
507  Connection::PARAM_INT_ARRAY
508  )
509  )
510  );
511  } else {
512  $constraints[] = $queryBuilder->expr()->in(
513  'A.pid',
514  $queryBuilder->createNamedParameter(
515  $pageIdRestriction,
516  Connection::PARAM_INT_ARRAY
517  )
518  );
519  }
520  }
521 
522  $rows = $queryBuilder
523  ->select('A.pid AS wspid', 'B.uid AS t3ver_oid', 'C.uid AS uid', 'B.pid AS livepid')
524  ->from($table, 'A')
525  ->from($table, 'B')
526  ->from($table, 'C')
527  ->where(...$constraints)
528  ->orderBy('A.uid')
529  ->execute()
530  ->fetchAll();
531 
532  return $rows;
533  }
534 
543  protected function ‪getTreeUids($pageId, $wsid, $recursionLevel)
544  {
545  // Reusing existing functionality with the drawback that
546  // mount points are not covered yet
547  $perms_clause = ‪$GLOBALS['BE_USER']->getPagePermsClause(‪Permission::PAGE_SHOW);
548  $searchObj = GeneralUtility::makeInstance(QueryView::class);
549  if ($pageId > 0) {
550  $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
551  } else {
552  $mountPoints = ‪$GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
553  if (!is_array($mountPoints) || empty($mountPoints)) {
554  $mountPoints = array_map('intval', ‪$GLOBALS['BE_USER']->returnWebmounts());
555  $mountPoints = array_unique($mountPoints);
556  }
557  $newList = [];
558  foreach ($mountPoints as $mountPoint) {
559  $newList[] = $searchObj->getTreeList($mountPoint, $recursionLevel, 0, $perms_clause);
560  }
561  $pageList = implode(',', $newList);
562  }
563  unset($searchObj);
564 
565  if (‪BackendUtility::isTableWorkspaceEnabled('pages') && $pageList) {
566  // Remove the "subbranch" if a page was moved away
567  $pageIds = ‪GeneralUtility::intExplode(',', $pageList, true);
568  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
569  $queryBuilder->getRestrictions()
570  ->removeAll()
571  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
572  $result = $queryBuilder
573  ->select('uid', 'pid', 't3ver_move_id')
574  ->from('pages')
575  ->where(
576  $queryBuilder->expr()->in(
577  't3ver_move_id',
578  $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
579  ),
580  $queryBuilder->expr()->eq(
581  't3ver_wsid',
582  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
583  )
584  )
585  ->orderBy('uid')
586  ->execute();
587 
588  $movedAwayPages = [];
589  while ($row = $result->fetch()) {
590  $movedAwayPages[$row['t3ver_move_id']] = $row;
591  }
592 
593  // move all pages away
594  $newList = array_diff($pageIds, array_keys($movedAwayPages));
595  // keep current page in the list
596  $newList[] = $pageId;
597  // move back in if still connected to the "remaining" pages
598  do {
599  $changed = false;
600  foreach ($movedAwayPages as $uid => $rec) {
601  if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
602  $newList[] = $uid;
603  $changed = true;
604  }
605  }
606  } while ($changed);
607 
608  // In case moving pages is enabled we need to replace all move-to pointer with their origin
609  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
610  $queryBuilder->getRestrictions()
611  ->removeAll()
612  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
613  $result = $queryBuilder->select('uid', 't3ver_move_id')
614  ->from('pages')
615  ->where(
616  $queryBuilder->expr()->in(
617  'uid',
618  $queryBuilder->createNamedParameter($newList, Connection::PARAM_INT_ARRAY)
619  )
620  )
621  ->orderBy('uid')
622  ->execute();
623 
624  $pages = [];
625  while ($row = $result->fetch()) {
626  $pages[$row['uid']] = $row;
627  }
628 
629  $pageIds = $newList;
630  if (!in_array($pageId, $pageIds)) {
631  $pageIds[] = $pageId;
632  }
633 
634  $newList = [];
635  foreach ($pageIds as $pageId) {
636  if ((int)$pages[$pageId]['t3ver_move_id'] > 0) {
637  $newList[] = (int)$pages[$pageId]['t3ver_move_id'];
638  } else {
639  $newList[] = $pageId;
640  }
641  }
642  $pageList = implode(',', $newList);
643  }
644 
645  return $pageList;
646  }
647 
655  protected function ‪filterPermittedElements($recs, $table)
656  {
657  $permittedElements = [];
658  if (is_array($recs)) {
659  foreach ($recs as $rec) {
660  if ($this->‪isPageAccessibleForCurrentUser($table, $rec) && $this->‪isLanguageAccessibleForCurrentUser($table, $rec)) {
661  $permittedElements[] = $rec;
662  }
663  }
664  }
665  return $permittedElements;
666  }
667 
675  protected function ‪isPageAccessibleForCurrentUser($table, array $record)
676  {
677  $pageIdField = $table === 'pages' ? 'uid' : 'wspid';
678  $pageId = isset($record[$pageIdField]) ? (int)$record[$pageIdField] : null;
679  if ($pageId === null) {
680  return false;
681  }
682  if ($pageId === 0 && ‪BackendUtility::isRootLevelRestrictionIgnored($table)) {
683  return true;
684  }
685  $page = ‪BackendUtility::getRecord('pages', $pageId, 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
686 
687  return ‪$GLOBALS['BE_USER']->doesUserHaveAccess($page, 1);
688  }
689 
697  protected function ‪isLanguageAccessibleForCurrentUser($table, array $record)
698  {
700  $languageUid = $record[‪$GLOBALS['TCA'][$table]['ctrl']['languageField']];
701  } else {
702  return true;
703  }
704  return ‪$GLOBALS['BE_USER']->checkLanguageAccess($languageUid);
705  }
706 
714  public static function ‪isNewPage($id, $language = 0)
715  {
716  $isNewPage = false;
717  // If the language is not default, check state of overlay
718  if ($language > 0) {
719  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
720  ->getQueryBuilderForTable('pages');
721  $queryBuilder->getRestrictions()
722  ->removeAll()
723  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
724  $row = $queryBuilder->select('t3ver_state')
725  ->from('pages')
726  ->where(
727  $queryBuilder->expr()->eq(
728  ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
729  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
730  ),
731  $queryBuilder->expr()->eq(
732  ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'],
733  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
734  ),
735  $queryBuilder->expr()->eq(
736  't3ver_wsid',
737  $queryBuilder->createNamedParameter(‪$GLOBALS['BE_USER']->workspace, \PDO::PARAM_INT)
738  )
739  )
740  ->setMaxResults(1)
741  ->execute()
742  ->fetch();
743 
744  if ($row !== false) {
745  $isNewPage = ‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::NEW_PLACEHOLDER);
746  }
747  } else {
748  $rec = ‪BackendUtility::getRecord('pages', $id, 't3ver_state');
749  if (is_array($rec)) {
750  $isNewPage = ‪VersionState::cast($rec['t3ver_state'])->equals(‪VersionState::NEW_PLACEHOLDER);
751  }
752  }
753  return $isNewPage;
754  }
755 
763  public function ‪hasPageRecordVersions($workspaceId, $pageId)
764  {
765  if ((int)$workspaceId === 0 || (int)$pageId === 0) {
766  return false;
767  }
768 
769  if (isset($this->versionsOnPageCache[$workspaceId][$pageId])) {
770  return $this->versionsOnPageCache[$workspaceId][$pageId];
771  }
772 
773  $this->versionsOnPageCache[$workspaceId][$pageId] = false;
774 
775  foreach (‪$GLOBALS['TCA'] as $tableName => $tableConfiguration) {
776  if ($tableName === 'pages' || !‪BackendUtility::isTableWorkspaceEnabled($tableName)) {
777  continue;
778  }
779 
780  $pages = $this->‪fetchPagesWithVersionsInTable($workspaceId, $tableName);
781  // Early break on first match
782  if (!empty($pages[(string)$pageId])) {
783  $this->versionsOnPageCache[$workspaceId][$pageId] = true;
784  break;
785  }
786  }
787 
788  $parameters = [
789  'workspaceId' => $workspaceId,
790  'pageId' => $pageId,
791  'versionsOnPageCache' => &‪$this->versionsOnPageCache,
792  ];
793  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Workspaces\Service\WorkspaceService::class]['hasPageRecordVersions'] ?? [] as $hookFunction) {
794  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
795  }
796 
797  return $this->versionsOnPageCache[$workspaceId][$pageId];
798  }
799 
823  public function ‪getPagesWithVersionsInTable($workspaceId)
824  {
825  foreach (‪$GLOBALS['TCA'] as $tableName => $tableConfiguration) {
826  if ($tableName === 'pages' || !‪BackendUtility::isTableWorkspaceEnabled($tableName)) {
827  continue;
828  }
829 
830  $this->‪fetchPagesWithVersionsInTable($workspaceId, $tableName);
831  }
832 
833  return $this->pagesWithVersionsInTable[$workspaceId];
834  }
835 
851  protected function ‪fetchPagesWithVersionsInTable($workspaceId, $tableName)
852  {
853  if ((int)$workspaceId === 0) {
854  return [];
855  }
856 
857  if (!isset($this->pagesWithVersionsInTable[$workspaceId])) {
858  $this->pagesWithVersionsInTable[$workspaceId] = [];
859  }
860 
861  if (!isset($this->pagesWithVersionsInTable[$workspaceId][$tableName])) {
862  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = [];
863 
864  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
865  $queryBuilder->getRestrictions()
866  ->removeAll()
867  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
868 
869  $movePointerParameter = $queryBuilder->createNamedParameter(
871  \PDO::PARAM_INT
872  );
873  $workspaceIdParameter = $queryBuilder->createNamedParameter(
874  $workspaceId,
875  \PDO::PARAM_INT
876  );
877  $onlineVersionParameter = $queryBuilder->createNamedParameter(
878  0,
879  \PDO::PARAM_INT
880  );
881  // create sub-queries, parameters are available for main query
882  $versionQueryBuilder = $this->‪createQueryBuilderForTable($tableName)
883  ->‪select('A.t3ver_oid')
884  ->‪from($tableName, 'A')
885  ->‪where(
886  $queryBuilder->expr()->gt('A.t3ver_oid', $onlineVersionParameter),
887  $queryBuilder->expr()->eq('A.t3ver_wsid', $workspaceIdParameter),
888  $queryBuilder->expr()->neq('A.t3ver_state', $movePointerParameter)
889  );
890  $movePointerQueryBuilder = $this->‪createQueryBuilderForTable($tableName)
891  ->‪select('A.t3ver_oid')
892  ->‪from($tableName, 'A')
893  ->‪where(
894  $queryBuilder->expr()->gt('A.t3ver_oid', $onlineVersionParameter),
895  $queryBuilder->expr()->eq('A.t3ver_wsid', $workspaceIdParameter),
896  $queryBuilder->expr()->eq('A.t3ver_state', $movePointerParameter)
897  );
898  $subQuery = '%s IN (%s)';
899  // execute main query
900  $result = $queryBuilder
901  ->‪select('B.pid AS pageId')
902  ->‪from($tableName, 'B')
903  ->‪orWhere(
904  sprintf(
905  $subQuery,
906  $queryBuilder->quoteIdentifier('B.uid'),
907  $versionQueryBuilder->getSQL()
908  ),
909  sprintf(
910  $subQuery,
911  $queryBuilder->quoteIdentifier('B.t3ver_move_id'),
912  $movePointerQueryBuilder->getSQL()
913  )
914  )
915  ->‪groupBy('B.pid')
916  ->‪execute();
917 
918  $pageIds = [];
919  while ($row = $result->fetch()) {
920  $pageIds[$row['pageId']] = true;
921  }
922 
923  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = $pageIds;
924 
925  $parameters = [
926  'workspaceId' => $workspaceId,
927  'tableName' => $tableName,
928  'pagesWithVersionsInTable' => &‪$this->pagesWithVersionsInTable,
929  ];
930  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\‪TYPO3\CMS\Workspaces\Service\WorkspaceService::class]['fetchPagesWithVersionsInTable'] ?? [] as $hookFunction) {
931  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
932  }
933  }
934 
935  return $this->pagesWithVersionsInTable[$workspaceId][$tableName];
936  }
937 
942  protected function ‪createQueryBuilderForTable(string $tableName)
943  {
944  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
945  ->getQueryBuilderForTable($tableName);
946  $queryBuilder->‪getRestrictions()
947  ->‪removeAll()
948  ->‪add(GeneralUtility::makeInstance(DeletedRestriction::class));
949  return $queryBuilder;
950  }
951 
955  protected static function ‪getLanguageService(): ?LanguageService
956  {
957  return ‪$GLOBALS['LANG'] ?? null;
958  }
959 }
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\selectVersionsInWorkspace
‪array selectVersionsInWorkspace($wsid, $filter=0, $stage=-99, $pageId=-1, $recursionLevel=0, $selectionType='tables_select', $language=null)
Definition: WorkspaceService.php:218
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\hasPageRecordVersions
‪bool hasPageRecordVersions($workspaceId, $pageId)
Definition: WorkspaceService.php:761
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\groupBy
‪QueryBuilder groupBy(... $groupBy)
Definition: QueryBuilder.php:726
‪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:695
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getTreeUids
‪string getTreeUids($pageId, $wsid, $recursionLevel)
Definition: WorkspaceService.php:541
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\select
‪QueryBuilder select(string ... $selects)
Definition: QueryBuilder.php:416
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:47
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\TABLE_WORKSPACE
‪const TABLE_WORKSPACE
Definition: WorkspaceService.php:45
‪TYPO3\CMS\Workspaces\Service
Definition: AdditionalColumnService.php:16
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getAvailableWorkspaces
‪array getAvailableWorkspaces()
Definition: WorkspaceService.php:54
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getMoveToPlaceHolderFromPages
‪array getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage)
Definition: WorkspaceService.php:413
‪TYPO3
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\fetchPagesWithVersionsInTable
‪array fetchPagesWithVersionsInTable($workspaceId, $tableName)
Definition: WorkspaceService.php:849
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface\removeAll
‪QueryRestrictionContainerInterface removeAll()
‪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:184
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\getRestrictions
‪QueryRestrictionContainerInterface getRestrictions()
Definition: QueryBuilder.php:104
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\LIVE_WORKSPACE_ID
‪const LIVE_WORKSPACE_ID
Definition: WorkspaceService.php:46
‪TYPO3\CMS\Core\Database\QueryView
Definition: QueryView.php:42
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:73
‪TYPO3\CMS\Backend\Utility\BackendUtility\isRootLevelRestrictionIgnored
‪static bool isRootLevelRestrictionIgnored($table)
Definition: BackendUtility.php:4063
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isPageAccessibleForCurrentUser
‪bool isPageAccessibleForCurrentUser($table, array $record)
Definition: WorkspaceService.php:673
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:24
‪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:38
‪TYPO3\CMS\Core\Database\Query\QueryBuilder
Definition: QueryBuilder.php:52
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableWorkspaceEnabled
‪static bool isTableWorkspaceEnabled($table)
Definition: BackendUtility.php:4021
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\from
‪QueryBuilder from(string $from, string $alias=null)
Definition: QueryBuilder.php:531
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:186
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\selectAllVersionsFromPages
‪array selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language=null)
Definition: WorkspaceService.php:272
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\execute
‪Statement Doctrine DBAL ForwardCompatibility Result Doctrine DBAL Driver ResultStatement int execute()
Definition: QueryBuilder.php:204
‪TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction
Definition: RootLevelRestriction.php:27
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getPagesWithVersionsInTable
‪array getPagesWithVersionsInTable($workspaceId)
Definition: WorkspaceService.php:821
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\filterPermittedElements
‪array filterPermittedElements($recs, $table)
Definition: WorkspaceService.php:653
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:33
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Workspaces\Service\WorkspaceService
Definition: WorkspaceService.php:36
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:95
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:36
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getCmdArrayForPublishWS
‪array getCmdArrayForPublishWS($wsid, $doSwap, $pageId=0, $language=null)
Definition: WorkspaceService.php:149
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\isNewPage
‪static bool isNewPage($id, $language=0)
Definition: WorkspaceService.php:712
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getPreviewLinkLifetime
‪int getPreviewLinkLifetime()
Definition: WorkspaceService.php:100
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableLocalizable
‪static bool isTableLocalizable($table)
Definition: BackendUtility.php:578
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:39
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪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:988
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\orWhere
‪QueryBuilder orWhere(... $where)
Definition: QueryBuilder.php:711
‪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\Database\Query\Restriction\QueryRestrictionContainerInterface\add
‪QueryRestrictionContainerInterface add(QueryRestrictionInterface $restriction)
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getLanguageService
‪static LanguageService null getLanguageService()
Definition: WorkspaceService.php:953
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\where
‪QueryBuilder where(... $predicates)
Definition: QueryBuilder.php:677
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\getWorkspaceTitle
‪static string getWorkspaceTitle($wsId)
Definition: WorkspaceService.php:120
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_PLACEHOLDER
‪const MOVE_PLACEHOLDER
Definition: VersionState.php:72
‪TYPO3\CMS\Workspaces\Service\WorkspaceService\createQueryBuilderForTable
‪QueryBuilder createQueryBuilderForTable(string $tableName)
Definition: WorkspaceService.php:940