TYPO3 CMS  TYPO3_8-7
WorkspaceService.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
29 
34 {
38  protected $pageCache = [];
39 
43  protected $versionsOnPageCache = [];
44 
48  protected $pagesWithVersionsInTable = [];
49 
50  const TABLE_WORKSPACE = 'sys_workspace';
51  const SELECT_ALL_WORKSPACES = -98;
52  const LIVE_WORKSPACE_ID = 0;
59  public function getAvailableWorkspaces()
60  {
61  $availableWorkspaces = [];
62  // add default workspaces
63  if ($GLOBALS['BE_USER']->checkWorkspace(['uid' => (string)self::LIVE_WORKSPACE_ID])) {
64  $availableWorkspaces[self::LIVE_WORKSPACE_ID] = self::getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
65  }
66  // add custom workspaces (selecting all, filtering by BE_USER check):
67 
68  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
69  $queryBuilder->getRestrictions()
70  ->add(GeneralUtility::makeInstance(RootLevelRestriction::class));
71 
72  $result = $queryBuilder
73  ->select('uid', 'title', 'adminusers', 'members')
74  ->from('sys_workspace')
75  ->orderBy('title')
76  ->execute();
77 
78  while ($workspace = $result->fetch()) {
79  if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
80  $availableWorkspaces[$workspace['uid']] = $workspace['title'];
81  }
82  }
83  return $availableWorkspaces;
84  }
85 
91  public function getCurrentWorkspace()
92  {
93  $workspaceId = $GLOBALS['BE_USER']->workspace;
94  $activeId = $GLOBALS['BE_USER']->getSessionData('tx_workspace_activeWorkspace');
95 
96  // Avoid invalid workspace settings
97  if ($activeId !== null && $activeId !== self::SELECT_ALL_WORKSPACES) {
98  $availableWorkspaces = $this->getAvailableWorkspaces();
99  if (isset($availableWorkspaces[$activeId])) {
100  $workspaceId = $activeId;
101  }
102  }
103 
104  return $workspaceId;
105  }
106 
114  public static function getWorkspaceTitle($wsId)
115  {
116  $title = false;
117  switch ($wsId) {
118  case self::LIVE_WORKSPACE_ID:
119  $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:shortcut_onlineWS');
120  break;
121  default:
122  $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
123  $wsRecord = BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
124  if (is_array($wsRecord)) {
125  $title = $wsRecord[$labelField];
126  }
127  }
128  if ($title === false) {
129  throw new \InvalidArgumentException('No such workspace defined', 1476045469);
130  }
131  return $title;
132  }
133 
143  public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0, $language = null)
144  {
145  $wsid = (int)$wsid;
146  $cmd = [];
147  if ($wsid >= -1 && $wsid !== 0) {
148  // Define stage to select:
149  $stage = -99;
150  if ($wsid > 0) {
151  $workspaceRec = BackendUtility::getRecord('sys_workspace', $wsid);
152  if ($workspaceRec['publish_access'] & 1) {
154  }
155  }
156  // Select all versions to swap:
157  $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
158  // Traverse the selection to build CMD array:
159  foreach ($versions as $table => $records) {
160  foreach ($records as $rec) {
161  // Build the cmd Array:
162  $cmd[$table][$rec['t3ver_oid']]['version'] = ['action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0];
163  }
164  }
165  }
166  return $cmd;
167  }
168 
178  public function getCmdArrayForFlushWS($wsid, $flush = true, $pageId = 0, $language = null)
179  {
180  $wsid = (int)$wsid;
181  $cmd = [];
182  if ($wsid >= -1 && $wsid !== 0) {
183  // Define stage to select:
184  $stage = -99;
185  // Select all versions to swap:
186  $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
187  // Traverse the selection to build CMD array:
188  foreach ($versions as $table => $records) {
189  foreach ($records as $rec) {
190  // Build the cmd Array:
191  $cmd[$table][$rec['uid']]['version'] = ['action' => $flush ? 'flush' : 'clearWSID'];
192  }
193  }
194  }
195  return $cmd;
196  }
197 
212  public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = null)
213  {
214  $wsid = (int)$wsid;
215  $filter = (int)$filter;
216  $output = [];
217  // Contains either nothing or a list with live-uids
218  if ($pageId != -1 && $recursionLevel > 0) {
219  $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
220  } elseif ($pageId != -1) {
221  $pageList = $pageId;
222  } else {
223  $pageList = '';
224  // check if person may only see a "virtual" page-root
225  $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
226  $mountPoints = array_unique($mountPoints);
227  if (!in_array(0, $mountPoints)) {
228  $tempPageIds = [];
229  foreach ($mountPoints as $mountPoint) {
230  $tempPageIds[] = $this->getTreeUids($mountPoint, $wsid, $recursionLevel);
231  }
232  $pageList = implode(',', $tempPageIds);
233  $pageList = implode(',', array_unique(explode(',', $pageList)));
234  }
235  }
236  // Traversing all tables supporting versioning:
237  foreach ($GLOBALS['TCA'] as $table => $cfg) {
238  // we do not collect records from tables without permissions on them.
239  if (!$GLOBALS['BE_USER']->check($selectionType, $table)) {
240  continue;
241  }
242  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
243  $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language);
244  $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
245  $recs = array_merge($recs, $moveRecs);
246  $recs = $this->filterPermittedElements($recs, $table);
247  if (!empty($recs)) {
248  $output[$table] = $recs;
249  }
250  }
251  }
252  return $output;
253  }
254 
266  protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language = null)
267  {
268  // Include root level page as there might be some records with where root level
269  // restriction is ignored (e.g. FAL records)
270  if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
271  $pageList .= ',0';
272  }
273  $isTableLocalizable = BackendUtility::isTableLocalizable($table);
274  $languageParentField = '';
275  // If table is not localizable, but localized reocrds shall
276  // be collected, an empty result array needs to be returned:
277  if ($isTableLocalizable === false && $language > 0) {
278  return [];
279  }
280  if ($isTableLocalizable) {
281  $languageParentField = 'A.' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
282  }
283 
284  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
285  $queryBuilder->getRestrictions()->removeAll()
286  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
287 
288  $fields = ['A.uid', 'A.t3ver_oid', 'A.t3ver_stage', 'B.pid AS wspid', 'B.pid AS livepid'];
289  if ($isTableLocalizable) {
290  $fields[] = $languageParentField;
291  $fields[] = 'A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
292  }
293  // Table A is the offline version and pid=-1 defines offline
294  // Table B (online) must have PID >= 0 to signify being online.
295  $constraints = [
296  $queryBuilder->expr()->eq(
297  'A.pid',
298  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
299  ),
300  $queryBuilder->expr()->gte(
301  'B.pid',
302  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
303  ),
304  $queryBuilder->expr()->neq(
305  'A.t3ver_state',
306  $queryBuilder->createNamedParameter(
308  \PDO::PARAM_INT
309  )
310  )
311  ];
312 
313  if ($pageList) {
314  $pidField = $table === 'pages' ? 'uid' : 'pid';
315  $constraints[] = $queryBuilder->expr()->in(
316  'B.' . $pidField,
317  $queryBuilder->createNamedParameter(
318  GeneralUtility::intExplode(',', $pageList, true),
319  Connection::PARAM_INT_ARRAY
320  )
321  );
322  }
323 
324  if ($isTableLocalizable && MathUtility::canBeInterpretedAsInteger($language)) {
325  $constraints[] = $queryBuilder->expr()->eq(
326  'A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'],
327  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
328  );
329  }
330 
331  // For "real" workspace numbers, select by that.
332  // If = -98, select all that are NOT online (zero).
333  // Anything else below -1 will not select on the wsid and therefore select all!
334  if ($wsid > self::SELECT_ALL_WORKSPACES) {
335  $constraints[] = $queryBuilder->expr()->eq(
336  'A.t3ver_wsid',
337  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
338  );
339  } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
340  $constraints[] = $queryBuilder->expr()->neq(
341  'A.t3ver_wsid',
342  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
343  );
344  }
345 
346  // lifecycle filter:
347  // 1 = select all drafts (never-published),
348  // 2 = select all published one or more times (archive/multiple)
349  if ($filter === 1) {
350  $constraints[] = $queryBuilder->expr()->eq(
351  'A.t3ver_count',
352  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
353  );
354  } elseif ($filter === 2) {
355  $constraints[] = $queryBuilder->expr()->gt(
356  'A.t3ver_count',
357  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
358  );
359  }
360 
361  if ((int)$stage !== -99) {
362  $constraints[] = $queryBuilder->expr()->eq(
363  'A.t3ver_stage',
364  $queryBuilder->createNamedParameter($stage, \PDO::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 swapping.
375  $rows = $queryBuilder->select(...$fields)
376  ->from($table, 'A')
377  ->from($table, 'B')
378  ->where(...$constraints)
379  ->orderBy('B.uid')
380  ->execute()
381  ->fetchAll();
382 
383  return $rows;
384  }
385 
396  protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage)
397  {
398  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
399  $queryBuilder->getRestrictions()->removeAll()
400  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
401 
402  // Aliases:
403  // A - moveTo placeholder
404  // B - online record
405  // C - moveFrom placeholder
406  $constraints = [
407  $queryBuilder->expr()->eq(
408  'A.t3ver_state',
409  $queryBuilder->createNamedParameter(
411  \PDO::PARAM_INT
412  )
413  ),
414  $queryBuilder->expr()->gt(
415  'B.pid',
416  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
417  ),
418  $queryBuilder->expr()->eq(
419  'B.t3ver_state',
420  $queryBuilder->createNamedParameter(
422  \PDO::PARAM_INT
423  )
424  ),
425  $queryBuilder->expr()->eq(
426  'B.t3ver_wsid',
427  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
428  ),
429  $queryBuilder->expr()->eq(
430  'C.pid',
431  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
432  ),
433  $queryBuilder->expr()->eq(
434  'C.t3ver_state',
435  $queryBuilder->createNamedParameter(
437  \PDO::PARAM_INT
438  )
439  ),
440  $queryBuilder->expr()->eq('A.t3ver_move_id', $queryBuilder->quoteIdentifier('B.uid')),
441  $queryBuilder->expr()->eq('B.uid', $queryBuilder->quoteIdentifier('C.t3ver_oid'))
442  ];
443 
444  if ($wsid > self::SELECT_ALL_WORKSPACES) {
445  $constraints[] = $queryBuilder->expr()->eq(
446  'A.t3ver_wsid',
447  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
448  );
449  $constraints[] = $queryBuilder->expr()->eq(
450  'C.t3ver_wsid',
451  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
452  );
453  } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
454  $constraints[] = $queryBuilder->expr()->neq(
455  'A.t3ver_wsid',
456  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
457  );
458  $constraints[] = $queryBuilder->expr()->neq(
459  'C.t3ver_wsid',
460  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
461  );
462  }
463 
464  // lifecycle filter:
465  // 1 = select all drafts (never-published),
466  // 2 = select all published one or more times (archive/multiple)
467  if ($filter === 1) {
468  $constraints[] = $queryBuilder->expr()->eq(
469  'C.t3ver_count',
470  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
471  );
472  } elseif ($filter === 2) {
473  $constraints[] = $queryBuilder->expr()->gt(
474  'C.t3ver_count',
475  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
476  );
477  }
478 
479  if ((int)$stage != -99) {
480  $constraints[] = $queryBuilder->expr()->eq(
481  'C.t3ver_stage',
482  $queryBuilder->createNamedParameter($stage, \PDO::PARAM_INT)
483  );
484  }
485 
486  if ($pageList) {
487  $pidField = $table === 'pages' ? 'B.uid' : 'A.pid';
488  $constraints[] = $queryBuilder->expr()->in(
489  $pidField,
490  $queryBuilder->createNamedParameter(
491  GeneralUtility::intExplode(',', $pageList, true),
492  Connection::PARAM_INT_ARRAY
493  )
494  );
495  }
496 
497  $rows = $queryBuilder
498  ->select('A.pid AS wspid', 'B.uid AS t3ver_oid', 'C.uid AS uid', 'B.pid AS livepid')
499  ->from($table, 'A')
500  ->from($table, 'B')
501  ->from($table, 'C')
502  ->where(...$constraints)
503  ->orderBy('A.uid')
504  ->execute()
505  ->fetchAll();
506 
507  return $rows;
508  }
509 
518  protected function getTreeUids($pageId, $wsid, $recursionLevel)
519  {
520  // Reusing existing functionality with the drawback that
521  // mount points are not covered yet
522  $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
524  $searchObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\QueryView::class);
525  if ($pageId > 0) {
526  $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
527  } else {
528  $mountPoints = $GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
529  if (!is_array($mountPoints) || empty($mountPoints)) {
530  $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
531  $mountPoints = array_unique($mountPoints);
532  }
533  $newList = [];
534  foreach ($mountPoints as $mountPoint) {
535  $newList[] = $searchObj->getTreeList($mountPoint, $recursionLevel, 0, $perms_clause);
536  }
537  $pageList = implode(',', $newList);
538  }
539  unset($searchObj);
540 
541  if (BackendUtility::isTableWorkspaceEnabled('pages') && $pageList) {
542  // Remove the "subbranch" if a page was moved away
543  $pageIds = GeneralUtility::intExplode(',', $pageList, true);
544  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
545  $queryBuilder->getRestrictions()
546  ->removeAll()
547  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
548  $result = $queryBuilder
549  ->select('uid', 'pid', 't3ver_move_id')
550  ->from('pages')
551  ->where(
552  $queryBuilder->expr()->in(
553  't3ver_move_id',
554  $queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
555  ),
556  $queryBuilder->expr()->eq(
557  't3ver_wsid',
558  $queryBuilder->createNamedParameter($wsid, \PDO::PARAM_INT)
559  )
560  )
561  ->orderBy('uid')
562  ->execute();
563 
564  $movedAwayPages = [];
565  while ($row = $result->fetch()) {
566  $movedAwayPages[$row['t3ver_move_id']] = $row;
567  }
568 
569  // move all pages away
570  $newList = array_diff($pageIds, array_keys($movedAwayPages));
571  // keep current page in the list
572  $newList[] = $pageId;
573  // move back in if still connected to the "remaining" pages
574  do {
575  $changed = false;
576  foreach ($movedAwayPages as $uid => $rec) {
577  if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
578  $newList[] = $uid;
579  $changed = true;
580  }
581  }
582  } while ($changed);
583 
584  // In case moving pages is enabled we need to replace all move-to pointer with their origin
585  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
586  $queryBuilder->getRestrictions()
587  ->removeAll()
588  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
589  $result = $queryBuilder->select('uid', 't3ver_move_id')
590  ->from('pages')
591  ->where(
592  $queryBuilder->expr()->in(
593  'uid',
594  $queryBuilder->createNamedParameter($newList, Connection::PARAM_INT_ARRAY)
595  )
596  )
597  ->orderBy('uid')
598  ->execute();
599 
600  $pages = [];
601  while ($row = $result->fetch()) {
602  $pages[$row['uid']] = $row;
603  }
604 
605  $pageIds = $newList;
606  if (!in_array($pageId, $pageIds)) {
607  $pageIds[] = $pageId;
608  }
609 
610  $newList = [];
611  foreach ($pageIds as $pageId) {
612  if ((int)$pages[$pageId]['t3ver_move_id'] > 0) {
613  $newList[] = (int)$pages[$pageId]['t3ver_move_id'];
614  } else {
615  $newList[] = $pageId;
616  }
617  }
618  $pageList = implode(',', $newList);
619  }
620 
621  return $pageList;
622  }
623 
631  protected function filterPermittedElements($recs, $table)
632  {
633  $permittedElements = [];
634  if (is_array($recs)) {
635  foreach ($recs as $rec) {
636  if ($this->isPageAccessibleForCurrentUser($table, $rec) && $this->isLanguageAccessibleForCurrentUser($table, $rec)) {
637  $permittedElements[] = $rec;
638  }
639  }
640  }
641  return $permittedElements;
642  }
643 
651  protected function isPageAccessibleForCurrentUser($table, array $record)
652  {
653  $pageIdField = $table === 'pages' ? 'uid' : 'wspid';
654  $pageId = isset($record[$pageIdField]) ? (int)$record[$pageIdField] : null;
655  if ($pageId === null) {
656  return false;
657  }
658  if ($pageId === 0 && BackendUtility::isRootLevelRestrictionIgnored($table)) {
659  return true;
660  }
661  $page = BackendUtility::getRecord('pages', $pageId, 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
662 
663  return $GLOBALS['BE_USER']->doesUserHaveAccess($page, 1);
664  }
665 
673  protected function isLanguageAccessibleForCurrentUser($table, array $record)
674  {
676  $languageUid = $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
677  } else {
678  return true;
679  }
680  return $GLOBALS['BE_USER']->checkLanguageAccess($languageUid);
681  }
682 
690  public static function isNewPage($id, $language = 0)
691  {
692  $isNewPage = false;
693  // If the language is not default, check state of overlay
694  if ($language > 0) {
695  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
696  ->getQueryBuilderForTable('pages_language_overlay');
697  $queryBuilder->getRestrictions()
698  ->removeAll()
699  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
700  $row = $queryBuilder->select('t3ver_state')
701  ->from('pages_language_overlay')
702  ->where(
703  $queryBuilder->expr()->eq(
704  'pid',
705  $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
706  ),
707  $queryBuilder->expr()->eq(
708  $GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'],
709  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
710  ),
711  $queryBuilder->expr()->eq(
712  't3ver_wsid',
713  $queryBuilder->createNamedParameter($GLOBALS['BE_USER']->workspace, \PDO::PARAM_INT)
714  )
715  )
716  ->setMaxResults(1)
717  ->execute()
718  ->fetch();
719 
720  if ($row !== false) {
721  $isNewPage = VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
722  }
723  } else {
724  $rec = BackendUtility::getRecord('pages', $id, 't3ver_state');
725  if (is_array($rec)) {
726  $isNewPage = VersionState::cast($rec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
727  }
728  }
729  return $isNewPage;
730  }
731 
741  public static function viewSingleRecord($table, $uid, array $liveRecord = null, array $versionRecord = null)
742  {
743  if ($table === 'pages') {
745  }
746 
747  if ($liveRecord === null) {
748  $liveRecord = BackendUtility::getLiveVersionOfRecord($table, $uid);
749  }
750  if ($versionRecord === null) {
751  $versionRecord = BackendUtility::getRecord($table, $uid);
752  }
753  if (VersionState::cast($versionRecord['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
754  $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], 'pid');
755  }
756 
757  // Directly use pid value and consider move placeholders
758  $previewPageId = (empty($movePlaceholder['pid']) ? $liveRecord['pid'] : $movePlaceholder['pid']);
759  $additionalParameters = '&tx_workspaces_web_workspacesworkspaces[previewWS]=' . $versionRecord['t3ver_wsid'];
760  // Add language parameter if record is a localization
762  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
763  if ($versionRecord[$languageField] > 0) {
764  $additionalParameters .= '&L=' . $versionRecord[$languageField];
765  }
766  }
767 
768  $pageTsConfig = BackendUtility::getPagesTSconfig($previewPageId);
769  $viewUrl = '';
770 
771  // Directly use determined direct page id
772  if ($table === 'pages_language_overlay' || $table === 'tt_content') {
773  $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
774  } elseif (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table]) || !empty($pageTsConfig['options.']['workspaces.']['previewPageId'])) {
775  // Analyze Page TSconfig options.workspaces.previewPageId
776  if (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table])) {
777  $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId.'][$table];
778  } else {
779  $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId'];
780  }
781  // Extract possible settings (e.g. "field:pid")
782  list($previewKey, $previewValue) = explode(':', $previewConfiguration, 2);
783  if ($previewKey === 'field') {
784  $previewPageId = (int)$liveRecord[$previewValue];
785  } else {
786  $previewPageId = (int)$previewConfiguration;
787  }
788  $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
789  } elseif (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
790  // Call user function to render the single record view
791  $_params = [
792  'table' => $table,
793  'uid' => $uid,
794  'record' => $liveRecord,
795  'liveRecord' => $liveRecord,
796  'versionRecord' => $versionRecord,
797  ];
798  $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
799  $null = null;
800  $viewUrl = GeneralUtility::callUserFunction($_funcRef, $_params, $null);
801  }
802 
803  return $viewUrl;
804  }
805 
813  public function canCreatePreviewLink($pageUid, $workspaceUid)
814  {
815  $result = true;
816  if ($pageUid > 0 && $workspaceUid > 0) {
817  $pageRecord = BackendUtility::getRecord('pages', $pageUid);
818  BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
819  if (
820  !GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'], $pageRecord['doktype'])
821  || VersionState::cast($pageRecord['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
822  ) {
823  $result = false;
824  }
825  } else {
826  $result = false;
827  }
828  return $result;
829  }
830 
837  public function generateWorkspacePreviewLink($uid)
838  {
839  $previewObject = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Hook\PreviewHook::class);
840  $timeToLiveHours = $previewObject->getPreviewLinkLifetime();
841  $previewKeyword = $previewObject->compilePreviewKeyword('', $GLOBALS['BE_USER']->user['uid'], $timeToLiveHours * 3600, $this->getCurrentWorkspace());
842  $linkParams = [
843  'ADMCMD_prev' => $previewKeyword,
844  'id' => $uid
845  ];
846  return BackendUtility::getViewDomain($uid) . '/index.php?' . GeneralUtility::implodeArrayForUrl('', $linkParams);
847  }
848 
856  public function generateWorkspaceSplittedPreviewLink($uid, $addDomain = false)
857  {
858  // In case a $pageUid is submitted we need to make sure it points to a live-page
859  if ($uid > 0) {
860  $uid = $this->getLivePageUid($uid);
861  }
863  $uriBuilder = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
864  $redirect = 'index.php?redirect_url=';
865  // @todo this should maybe be changed so that the extbase URI Builder can deal with module names directly
866  $originalM = GeneralUtility::_GET('M');
867  GeneralUtility::_GETset('web_WorkspacesWorkspaces', 'M');
868  $viewScript = $uriBuilder->uriFor('index', [], 'Preview', 'workspaces', 'web_workspacesworkspaces') . '&id=';
869  GeneralUtility::_GETset($originalM, 'M');
870  if ($addDomain === true) {
871  return BackendUtility::getViewDomain($uid) . $redirect . urlencode($viewScript) . $uid;
872  }
873  return $viewScript;
874  }
875 
883  {
884  $previewUrl = $this->generateWorkspacePreviewLink($uid);
885  $previewLanguages = $this->getAvailableLanguages($uid);
886  $previewLinks = [];
887 
888  foreach ($previewLanguages as $languageUid => $language) {
889  $previewLinks[$language] = $previewUrl . '&L=' . $languageUid;
890  }
891 
892  return $previewLinks;
893  }
894 
903  public function getLivePageUid($uid)
904  {
905  if (!isset($this->pageCache[$uid])) {
906  $pageRecord = BackendUtility::getRecord('pages', $uid);
907  if (is_array($pageRecord)) {
908  $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? $pageRecord['t3ver_oid'] : $uid;
909  } else {
910  throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was: ' . $uid, 1290628113);
911  }
912  }
913  return $this->pageCache[$uid];
914  }
915 
923  public function hasPageRecordVersions($workspaceId, $pageId)
924  {
925  if ((int)$workspaceId === 0 || (int)$pageId === 0) {
926  return false;
927  }
928 
929  if (isset($this->versionsOnPageCache[$workspaceId][$pageId])) {
930  return $this->versionsOnPageCache[$workspaceId][$pageId];
931  }
932 
933  $this->versionsOnPageCache[$workspaceId][$pageId] = false;
934 
935  foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
936  if ($tableName === 'pages' || empty($tableConfiguration['ctrl']['versioningWS'])) {
937  continue;
938  }
939 
940  $pages = $this->fetchPagesWithVersionsInTable($workspaceId, $tableName);
941  // Early break on first match
942  if (!empty($pages[(string)$pageId])) {
943  $this->versionsOnPageCache[$workspaceId][$pageId] = true;
944  break;
945  }
946  }
947 
948  if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['hasPageRecordVersions'])
949  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['hasPageRecordVersions'])) {
950  $parameters = [
951  'workspaceId' => $workspaceId,
952  'pageId' => $pageId,
953  'versionsOnPageCache' => &$this->versionsOnPageCache,
954  ];
955  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['hasPageRecordVersions'] as $hookFunction) {
956  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
957  }
958  }
959 
960  return $this->versionsOnPageCache[$workspaceId][$pageId];
961  }
962 
986  public function getPagesWithVersionsInTable($workspaceId)
987  {
988  foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
989  if ($tableName === 'pages' || empty($tableConfiguration['ctrl']['versioningWS'])) {
990  continue;
991  }
992 
993  $this->fetchPagesWithVersionsInTable($workspaceId, $tableName);
994  }
995 
996  return $this->pagesWithVersionsInTable[$workspaceId];
997  }
998 
1014  protected function fetchPagesWithVersionsInTable($workspaceId, $tableName)
1015  {
1016  if ((int)$workspaceId === 0) {
1017  return [];
1018  }
1019 
1020  if (!isset($this->pagesWithVersionsInTable[$workspaceId])) {
1021  $this->pagesWithVersionsInTable[$workspaceId] = [];
1022  }
1023 
1024  if (!isset($this->pagesWithVersionsInTable[$workspaceId][$tableName])) {
1025  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = [];
1026 
1027  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
1028  $queryBuilder->getRestrictions()
1029  ->removeAll()
1030  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1031 
1032  $movePointerParameter = $queryBuilder->createNamedParameter(
1034  \PDO::PARAM_INT
1035  );
1036  $workspaceIdParameter = $queryBuilder->createNamedParameter(
1037  $workspaceId,
1038  \PDO::PARAM_INT
1039  );
1040  $pageIdParameter = $queryBuilder->createNamedParameter(
1041  -1,
1042  \PDO::PARAM_INT
1043  );
1044  // create sub-queries, parameters are available for main query
1045  $versionQueryBuilder = $this->createQueryBuilderForTable($tableName)
1046  ->select('A.t3ver_oid')
1047  ->from($tableName, 'A')
1048  ->where(
1049  $queryBuilder->expr()->eq('A.pid', $pageIdParameter),
1050  $queryBuilder->expr()->eq('A.t3ver_wsid', $workspaceIdParameter),
1051  $queryBuilder->expr()->neq('A.t3ver_state', $movePointerParameter)
1052  );
1053  $movePointerQueryBuilder = $this->createQueryBuilderForTable($tableName)
1054  ->select('A.t3ver_oid')
1055  ->from($tableName, 'A')
1056  ->where(
1057  $queryBuilder->expr()->eq('A.pid', $pageIdParameter),
1058  $queryBuilder->expr()->eq('A.t3ver_wsid', $workspaceIdParameter),
1059  $queryBuilder->expr()->eq('A.t3ver_state', $movePointerParameter)
1060  );
1061  $subQuery = '%s IN (%s)';
1062  // execute main query
1063  $result = $queryBuilder
1064  ->select('B.pid AS pageId')
1065  ->from($tableName, 'B')
1066  ->orWhere(
1067  sprintf(
1068  $subQuery,
1069  $queryBuilder->quoteIdentifier('B.uid'),
1070  $versionQueryBuilder->getSQL()
1071  ),
1072  sprintf(
1073  $subQuery,
1074  $queryBuilder->quoteIdentifier('B.t3ver_move_id'),
1075  $movePointerQueryBuilder->getSQL()
1076  )
1077  )
1078  ->groupBy('B.pid')
1079  ->execute();
1080 
1081  $pageIds = [];
1082  while ($row = $result->fetch()) {
1083  $pageIds[$row['pageId']] = true;
1084  }
1085 
1086  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = $pageIds;
1087 
1088  if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['fetchPagesWithVersionsInTable'])
1089  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['fetchPagesWithVersionsInTable'])) {
1090  $parameters = [
1091  'workspaceId' => $workspaceId,
1092  'tableName' => $tableName,
1093  'pagesWithVersionsInTable' => &$this->pagesWithVersionsInTable,
1094  ];
1095  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['fetchPagesWithVersionsInTable'] as $hookFunction) {
1096  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
1097  }
1098  }
1099  }
1100 
1101  return $this->pagesWithVersionsInTable[$workspaceId][$tableName];
1102  }
1103 
1108  protected function createQueryBuilderForTable(string $tableName)
1109  {
1110  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1111  ->getQueryBuilderForTable($tableName);
1112  $queryBuilder->getRestrictions()
1113  ->removeAll()
1114  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1115  return $queryBuilder;
1116  }
1117 
1121  protected function getObjectManager()
1122  {
1123  return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
1124  }
1125 
1132  public function getAvailableLanguages($pageId)
1133  {
1134  $languageOptions = [];
1136  $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
1137  $systemLanguages = $translationConfigurationProvider->getSystemLanguages($pageId);
1138 
1139  if ($GLOBALS['BE_USER']->checkLanguageAccess(0)) {
1140  // Use configured label for default language
1141  $languageOptions[0] = $systemLanguages[0]['title'];
1142  }
1143 
1144  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1145  ->getQueryBuilderForTable('pages_language_overlay');
1146  $queryBuilder->getRestrictions()
1147  ->removeAll()
1148  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1149  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1150 
1151  $result = $queryBuilder->select('sys_language_uid')
1152  ->from('pages_language_overlay')
1153  ->where(
1154  $queryBuilder->expr()->eq(
1155  'pid',
1156  $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
1157  )
1158  )
1159  ->execute();
1160 
1161  while ($row = $result->fetch()) {
1162  $languageId = (int)$row['sys_language_uid'];
1163  // Only add links to active languages the user has access to
1164  if (isset($systemLanguages[$languageId]) && $GLOBALS['BE_USER']->checkLanguageAccess($languageId)) {
1165  $languageOptions[$languageId] = $systemLanguages[$languageId]['title'];
1166  }
1167  }
1168 
1169  return $languageOptions;
1170  }
1171 }
getCmdArrayForPublishWS($wsid, $doSwap, $pageId=0, $language=null)
static viewSingleRecord($table, $uid, array $liveRecord=null, array $versionRecord=null)
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
getCmdArrayForFlushWS($wsid, $flush=true, $pageId=0, $language=null)
getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage)
static callUserFunction($funcName, &$params, &$ref, $_='', $errorMode=0)
static viewOnClick( $pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
fetchPagesWithVersionsInTable($workspaceId, $tableName)
static getViewDomain($pageId, $rootLine=null)
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
static makeInstance($className,... $constructorArguments)
selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language=null)
$fields
Definition: pages.php:4
static _GETset($inputGet, $key='')
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
selectVersionsInWorkspace($wsid, $filter=0, $stage=-99, $pageId=-1, $recursionLevel=0, $selectionType='tables_select', $language=null)
static getLiveVersionOfRecord($table, $uid, $fields=' *')
static getMovePlaceholder($table, $uid, $fields=' *', $workspace=null)
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']