TYPO3  7.6
WorkspaceService.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Workspaces\Service;
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 
22 
27 {
31  protected $pageCache = array();
32 
36  protected $versionsOnPageCache = array();
37 
38  const TABLE_WORKSPACE = 'sys_workspace';
39  const SELECT_ALL_WORKSPACES = -98;
40  const LIVE_WORKSPACE_ID = 0;
47  public function getAvailableWorkspaces()
48  {
49  $availableWorkspaces = array();
50  // add default workspaces
51  if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string)self::LIVE_WORKSPACE_ID))) {
52  $availableWorkspaces[self::LIVE_WORKSPACE_ID] = self::getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
53  }
54  // add custom workspaces (selecting all, filtering by BE_USER check):
55  $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, title, adminusers, members', 'sys_workspace', 'pid = 0' . BackendUtility::deleteClause('sys_workspace'), '', 'title');
56  if (!empty($customWorkspaces)) {
57  foreach ($customWorkspaces as $workspace) {
58  if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
59  $availableWorkspaces[$workspace['uid']] = $workspace['title'];
60  }
61  }
62  }
63  return $availableWorkspaces;
64  }
65 
71  public function getCurrentWorkspace()
72  {
73  $workspaceId = $GLOBALS['BE_USER']->workspace;
74  $activeId = $GLOBALS['BE_USER']->getSessionData('tx_workspace_activeWorkspace');
75 
76  // Avoid invalid workspace settings
77  if ($activeId !== null && $activeId !== self::SELECT_ALL_WORKSPACES) {
78  $availableWorkspaces = $this->getAvailableWorkspaces();
79  if (!isset($availableWorkspaces[$activeId])) {
80  $activeId = null;
81  }
82  }
83 
84  if ($activeId !== null) {
85  $workspaceId = $activeId;
86  }
87 
88  return $workspaceId;
89  }
90 
98  public static function getWorkspaceTitle($wsId)
99  {
100  $title = false;
101  switch ($wsId) {
102  case self::LIVE_WORKSPACE_ID:
103  $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xlf:shortcut_onlineWS');
104  break;
105  default:
106  $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
107  $wsRecord = BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
108  if (is_array($wsRecord)) {
109  $title = $wsRecord[$labelField];
110  }
111  }
112  if ($title === false) {
113  throw new \InvalidArgumentException('No such workspace defined');
114  }
115  return $title;
116  }
117 
127  public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0, $language = null)
128  {
129  $wsid = (int)$wsid;
130  $cmd = array();
131  if ($wsid >= -1 && $wsid !== 0) {
132  // Define stage to select:
133  $stage = -99;
134  if ($wsid > 0) {
135  $workspaceRec = BackendUtility::getRecord('sys_workspace', $wsid);
136  if ($workspaceRec['publish_access'] & 1) {
137  $stage = \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_ID;
138  }
139  }
140  // Select all versions to swap:
141  $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
142  // Traverse the selection to build CMD array:
143  foreach ($versions as $table => $records) {
144  foreach ($records as $rec) {
145  // Build the cmd Array:
146  $cmd[$table][$rec['t3ver_oid']]['version'] = array('action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0);
147  }
148  }
149  }
150  return $cmd;
151  }
152 
162  public function getCmdArrayForFlushWS($wsid, $flush = true, $pageId = 0, $language = null)
163  {
164  $wsid = (int)$wsid;
165  $cmd = array();
166  if ($wsid >= -1 && $wsid !== 0) {
167  // Define stage to select:
168  $stage = -99;
169  // Select all versions to swap:
170  $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
171  // Traverse the selection to build CMD array:
172  foreach ($versions as $table => $records) {
173  foreach ($records as $rec) {
174  // Build the cmd Array:
175  $cmd[$table][$rec['uid']]['version'] = array('action' => $flush ? 'flush' : 'clearWSID');
176  }
177  }
178  }
179  return $cmd;
180  }
181 
196  public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = null)
197  {
198  $wsid = (int)$wsid;
199  $filter = (int)$filter;
200  $output = array();
201  // Contains either nothing or a list with live-uids
202  if ($pageId != -1 && $recursionLevel > 0) {
203  $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
204  } elseif ($pageId != -1) {
205  $pageList = $pageId;
206  } else {
207  $pageList = '';
208  // check if person may only see a "virtual" page-root
209  $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
210  $mountPoints = array_unique($mountPoints);
211  if (!in_array(0, $mountPoints)) {
212  $tempPageIds = array();
213  foreach ($mountPoints as $mountPoint) {
214  $tempPageIds[] = $this->getTreeUids($mountPoint, $wsid, $recursionLevel);
215  }
216  $pageList = implode(',', $tempPageIds);
217  $pageList = implode(',', array_unique(explode(',', $pageList)));
218  }
219  }
220  // Traversing all tables supporting versioning:
221  foreach ($GLOBALS['TCA'] as $table => $cfg) {
222  // we do not collect records from tables without permissions on them.
223  if (!$GLOBALS['BE_USER']->check($selectionType, $table)) {
224  continue;
225  }
226  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
227  $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language);
228  $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
229  $recs = array_merge($recs, $moveRecs);
230  $recs = $this->filterPermittedElements($recs, $table);
231  if (!empty($recs)) {
232  $output[$table] = $recs;
233  }
234  }
235  }
236  return $output;
237  }
238 
250  protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language = null)
251  {
252  // Include root level page as there might be some records with where root level restriction is ignored (e.g. FAL records)
253  if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
254  $pageList .= ',0';
255  }
256  $isTableLocalizable = BackendUtility::isTableLocalizable($table);
257  $languageParentField = '';
258  // If table is not localizable, but localized reocrds shall
259  // be collected, an empty result array needs to be returned:
260  if ($isTableLocalizable === false && $language > 0) {
261  return array();
262  } elseif ($isTableLocalizable) {
263  $languageParentField = 'A.' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . ', ';
264  }
265  $fields = 'A.uid, A.t3ver_oid, A.t3ver_stage, ' . $languageParentField . 'B.pid AS wspid, B.pid AS livepid';
266  if ($isTableLocalizable) {
267  $fields .= ', A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
268  }
269  $from = $table . ' A,' . $table . ' B';
270  // Table A is the offline version and pid=-1 defines offline
271  $where = 'A.pid=-1 AND A.t3ver_state!=' . new VersionState(VersionState::MOVE_POINTER);
272  if ($pageList) {
273  $pidField = $table === 'pages' ? 'uid' : 'pid';
274  $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
275  $where .= ' AND B.' . $pidField . $pidConstraint;
276  }
277  if ($isTableLocalizable && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($language)) {
278  $where .= ' AND A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . $language;
279  }
280  // For "real" workspace numbers, select by that.
281  // If = -98, select all that are NOT online (zero).
282  // Anything else below -1 will not select on the wsid and therefore select all!
283  if ($wsid > self::SELECT_ALL_WORKSPACES) {
284  $where .= ' AND A.t3ver_wsid=' . $wsid;
285  } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
286  $where .= ' AND A.t3ver_wsid!=0';
287  }
288  // lifecycle filter:
289  // 1 = select all drafts (never-published),
290  // 2 = select all published one or more times (archive/multiple)
291  if ($filter === 1 || $filter === 2) {
292  $where .= ' AND A.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
293  }
294  if ($stage != -99) {
295  $where .= ' AND A.t3ver_stage=' . (int)$stage;
296  }
297  // Table B (online) must have PID >= 0 to signify being online.
298  $where .= ' AND B.pid>=0';
299  // ... and finally the join between the two tables.
300  $where .= ' AND A.t3ver_oid=B.uid';
301  $where .= BackendUtility::deleteClause($table, 'A');
302  $where .= BackendUtility::deleteClause($table, 'B');
303  // Select all records from this table in the database from the workspace
304  // This joins the online version with the offline version as tables A and B
305  // Order by UID, mostly to have a sorting in the backend overview module which doesn't "jump around" when swapping.
306  $res = $this->getDatabaseConnection()->exec_SELECTgetRows($fields, $from, $where, '', 'B.uid');
307  return is_array($res) ? $res : array();
308  }
309 
320  protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage)
321  {
322  // Aliases:
323  // A - moveTo placeholder
324  // B - online record
325  // C - moveFrom placeholder
326  $fields = 'A.pid AS wspid, B.uid AS t3ver_oid, C.uid AS uid, B.pid AS livepid';
327  $from = $table . ' A, ' . $table . ' B,' . $table . ' C';
328  $where = 'A.t3ver_state=' . new VersionState(VersionState::MOVE_PLACEHOLDER) . ' AND B.pid>0 AND B.t3ver_state='
329  . new VersionState(VersionState::DEFAULT_STATE) . ' AND B.t3ver_wsid=0 AND C.pid=-1 AND C.t3ver_state='
331  if ($wsid > self::SELECT_ALL_WORKSPACES) {
332  $where .= ' AND A.t3ver_wsid=' . $wsid . ' AND C.t3ver_wsid=' . $wsid;
333  } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
334  $where .= ' AND A.t3ver_wsid!=0 AND C.t3ver_wsid!=0 ';
335  }
336  // lifecycle filter:
337  // 1 = select all drafts (never-published),
338  // 2 = select all published one or more times (archive/multiple)
339  if ($filter === 1 || $filter === 2) {
340  $where .= ' AND C.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
341  }
342  if ($stage != -99) {
343  $where .= ' AND C.t3ver_stage=' . (int)$stage;
344  }
345  if ($pageList) {
346  $pidField = $table === 'pages' ? 'B.uid' : 'A.pid';
347  $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
348  $where .= ' AND ' . $pidField . $pidConstraint;
349  }
350  $where .= ' AND A.t3ver_move_id = B.uid AND B.uid = C.t3ver_oid';
351  $where .= BackendUtility::deleteClause($table, 'A');
352  $where .= BackendUtility::deleteClause($table, 'B');
353  $where .= BackendUtility::deleteClause($table, 'C');
354  $res = $this->getDatabaseConnection()->exec_SELECTgetRows($fields, $from, $where, '', 'A.uid');
355  return is_array($res) ? $res : array();
356  }
357 
366  protected function getTreeUids($pageId, $wsid, $recursionLevel)
367  {
368  // Reusing existing functionality with the drawback that
369  // mount points are not covered yet
370  $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
372  $searchObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\QueryView::class);
373  if ($pageId > 0) {
374  $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
375  } else {
376  $mountPoints = $GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
377  if (!is_array($mountPoints) || empty($mountPoints)) {
378  $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
379  $mountPoints = array_unique($mountPoints);
380  }
381  $newList = array();
382  foreach ($mountPoints as $mountPoint) {
383  $newList[] = $searchObj->getTreeList($mountPoint, $recursionLevel, 0, $perms_clause);
384  }
385  $pageList = implode(',', $newList);
386  }
387  unset($searchObj);
388  if (BackendUtility::isTableWorkspaceEnabled('pages') && $pageList) {
389  // Remove the "subbranch" if a page was moved away
390  $movedAwayPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, pid, t3ver_move_id', 'pages', 't3ver_move_id IN (' . $pageList . ') AND t3ver_wsid=' . (int)$wsid . BackendUtility::deleteClause('pages'), '', 'uid', '', 't3ver_move_id');
391  $pageIds = GeneralUtility::intExplode(',', $pageList, true);
392  // move all pages away
393  $newList = array_diff($pageIds, array_keys($movedAwayPages));
394  // keep current page in the list
395  $newList[] = $pageId;
396  // move back in if still connected to the "remaining" pages
397  do {
398  $changed = false;
399  foreach ($movedAwayPages as $uid => $rec) {
400  if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
401  $newList[] = $uid;
402  $changed = true;
403  }
404  }
405  } while ($changed);
406  $pageList = implode(',', $newList);
407  // In case moving pages is enabled we need to replace all move-to pointer with their origin
408  $pages = $this->getDatabaseConnection()->exec_SELECTgetRows('uid, t3ver_move_id', 'pages', 'uid IN (' . $pageList . ')' . BackendUtility::deleteClause('pages'), '', 'uid', '', 'uid');
409  $newList = array();
410  $pageIds = GeneralUtility::intExplode(',', $pageList, true);
411  if (!in_array($pageId, $pageIds)) {
412  $pageIds[] = $pageId;
413  }
414  foreach ($pageIds as $pageId) {
415  if ((int)$pages[$pageId]['t3ver_move_id'] > 0) {
416  $newList[] = (int)$pages[$pageId]['t3ver_move_id'];
417  } else {
418  $newList[] = $pageId;
419  }
420  }
421  $pageList = implode(',', $newList);
422  }
423  return $pageList;
424  }
425 
433  protected function filterPermittedElements($recs, $table)
434  {
435  $permittedElements = array();
436  if (is_array($recs)) {
437  foreach ($recs as $rec) {
438  if ($this->isPageAccessibleForCurrentUser($table, $rec) && $this->isLanguageAccessibleForCurrentUser($table, $rec)) {
439  $permittedElements[] = $rec;
440  }
441  }
442  }
443  return $permittedElements;
444  }
445 
453  protected function isPageAccessibleForCurrentUser($table, array $record)
454  {
455  $pageIdField = $table === 'pages' ? 'uid' : 'wspid';
456  $pageId = isset($record[$pageIdField]) ? (int)$record[$pageIdField] : null;
457  if ($pageId === null) {
458  return false;
459  }
460  if ($pageId === 0 && BackendUtility::isRootLevelRestrictionIgnored($table)) {
461  return true;
462  }
463  $page = BackendUtility::getRecord('pages', $pageId, 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
464 
465  return $GLOBALS['BE_USER']->doesUserHaveAccess($page, 1);
466  }
467 
475  protected function isLanguageAccessibleForCurrentUser($table, array $record)
476  {
478  $languageUid = $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
479  } else {
480  return true;
481  }
482  return $GLOBALS['BE_USER']->checkLanguageAccess($languageUid);
483  }
484 
491  public static function isOldStyleWorkspaceUsed()
492  {
493  $cacheKey = 'workspace-oldstyleworkspace-notused';
494  $cacheResult = $GLOBALS['BE_USER']->getSessionData($cacheKey);
495  if (!$cacheResult) {
496  $where = 'adminusers != \'\' AND adminusers NOT LIKE \'%be_users%\' AND adminusers NOT LIKE \'%be_groups%\' AND deleted=0';
497  $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
498  $oldStyleWorkspaceIsUsed = $count > 0;
499  $GLOBALS['BE_USER']->setAndSaveSessionData($cacheKey, !$oldStyleWorkspaceIsUsed);
500  } else {
501  $oldStyleWorkspaceIsUsed = !$cacheResult;
502  }
503  return $oldStyleWorkspaceIsUsed;
504  }
505 
513  public static function isNewPage($id, $language = 0)
514  {
515  $isNewPage = false;
516  // If the language is not default, check state of overlay
517  if ($language > 0) {
518  $whereClause = 'pid = ' . (int)$id;
519  $whereClause .= ' AND ' . $GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'] . ' = ' . (int)$language;
520  $whereClause .= ' AND t3ver_wsid = ' . (int)$GLOBALS['BE_USER']->workspace;
521  $whereClause .= BackendUtility::deleteClause('pages_language_overlay');
522  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('t3ver_state', 'pages_language_overlay', $whereClause);
523  if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
524  $isNewPage = VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
525  }
526  } else {
527  $rec = BackendUtility::getRecord('pages', $id, 't3ver_state');
528  if (is_array($rec)) {
529  $isNewPage = VersionState::cast($rec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
530  }
531  }
532  return $isNewPage;
533  }
534 
545  public static function viewSingleRecord($table, $uid, array $liveRecord = null, array $versionRecord = null)
546  {
547  if ($table === 'pages') {
549  }
550 
551  if ($liveRecord === null) {
552  $liveRecord = BackendUtility::getLiveVersionOfRecord($table, $uid);
553  }
554  if ($versionRecord === null) {
555  $versionRecord = BackendUtility::getRecord($table, $uid);
556  }
557  if (VersionState::cast($versionRecord['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
558  $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], 'pid');
559  }
560 
561  // Directly use pid value and consider move placeholders
562  $previewPageId = (empty($movePlaceholder['pid']) ? $liveRecord['pid'] : $movePlaceholder['pid']);
563  $additionalParameters = '&tx_workspaces_web_workspacesworkspaces[previewWS]=' . $versionRecord['t3ver_wsid'];
564  // Add language parameter if record is a localization
566  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
567  if ($versionRecord[$languageField] > 0) {
568  $additionalParameters .= '&L=' . $versionRecord[$languageField];
569  }
570  }
571 
572  $pageTsConfig = BackendUtility::getPagesTSconfig($previewPageId);
573  $viewUrl = '';
574 
575  // Directly use determined direct page id
576  if ($table === 'pages_language_overlay' || $table === 'tt_content') {
577  $viewUrl = BackendUtility::viewOnClick($previewPageId, '', '', '', '', $additionalParameters);
578  // Analyze Page TSconfig options.workspaces.previewPageId
579  } elseif (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table]) || !empty($pageTsConfig['options.']['workspaces.']['previewPageId'])) {
580  if (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table])) {
581  $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId.'][$table];
582  } else {
583  $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId'];
584  }
585  // Extract possible settings (e.g. "field:pid")
586  list($previewKey, $previewValue) = explode(':', $previewConfiguration, 2);
587  if ($previewKey === 'field') {
588  $previewPageId = (int)$liveRecord[$previewValue];
589  } else {
590  $previewPageId = (int)$previewConfiguration;
591  }
592  $viewUrl = BackendUtility::viewOnClick($previewPageId, '', '', '', '', $additionalParameters);
593  // Call user function to render the single record view
594  } elseif (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
595  $_params = array(
596  'table' => $table,
597  'uid' => $uid,
598  'record' => $liveRecord,
599  'liveRecord' => $liveRecord,
600  'versionRecord' => $versionRecord,
601  );
602  $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
603  $null = null;
604  $viewUrl = GeneralUtility::callUserFunction($_funcRef, $_params, $null);
605  }
606 
607  return $viewUrl;
608  }
609 
617  public function canCreatePreviewLink($pageUid, $workspaceUid)
618  {
619  $result = true;
620  if ($pageUid > 0 && $workspaceUid > 0) {
621  $pageRecord = BackendUtility::getRecord('pages', $pageUid);
622  BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
623  if (
624  !GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'], $pageRecord['doktype'])
625  || VersionState::cast($pageRecord['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
626  ) {
627  $result = false;
628  }
629  } else {
630  $result = false;
631  }
632  return $result;
633  }
634 
641  public function generateWorkspacePreviewLink($uid)
642  {
643  $previewObject = GeneralUtility::makeInstance(\TYPO3\CMS\Version\Hook\PreviewHook::class);
644  $timeToLiveHours = $previewObject->getPreviewLinkLifetime();
645  $previewKeyword = $previewObject->compilePreviewKeyword('', $GLOBALS['BE_USER']->user['uid'], $timeToLiveHours * 3600, $this->getCurrentWorkspace());
646  $linkParams = array(
647  'ADMCMD_prev' => $previewKeyword,
648  'id' => $uid
649  );
650  return BackendUtility::getViewDomain($uid) . '/index.php?' . GeneralUtility::implodeArrayForUrl('', $linkParams);
651  }
652 
660  public function generateWorkspaceSplittedPreviewLink($uid, $addDomain = false)
661  {
662  // In case a $pageUid is submitted we need to make sure it points to a live-page
663  if ($uid > 0) {
664  $uid = $this->getLivePageUid($uid);
665  }
667  $uriBuilder = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
668  $redirect = 'index.php?redirect_url=';
669  // @todo this should maybe be changed so that the extbase URI Builder can deal with module names directly
670  $originalM = GeneralUtility::_GET('M');
671  GeneralUtility::_GETset('web_WorkspacesWorkspaces', 'M');
672  $viewScript = $uriBuilder->uriFor('index', array(), 'Preview', 'workspaces', 'web_workspacesworkspaces') . '&id=';
673  GeneralUtility::_GETset($originalM, 'M');
674  if ($addDomain === true) {
675  return BackendUtility::getViewDomain($uid) . $redirect . urlencode($viewScript) . $uid;
676  } else {
677  return $viewScript;
678  }
679  }
680 
688  {
689  $previewUrl = $this->generateWorkspacePreviewLink($uid);
690  $previewLanguages = $this->getAvailableLanguages($uid);
691  $previewLinks = array();
692 
693  foreach ($previewLanguages as $languageUid => $language) {
694  $previewLinks[$language] = $previewUrl . '&L=' . $languageUid;
695  }
696 
697  return $previewLinks;
698  }
699 
708  public function getLivePageUid($uid)
709  {
710  if (!isset($this->pageCache[$uid])) {
711  $pageRecord = BackendUtility::getRecord('pages', $uid);
712  if (is_array($pageRecord)) {
713  $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? $pageRecord['t3ver_oid'] : $uid;
714  } else {
715  throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was:' . $uid, 1290628113);
716  }
717  }
718  return $this->pageCache[$uid];
719  }
720 
728  public function hasPageRecordVersions($workspace, $pageId)
729  {
730  $workspace = (int)$workspace;
731  $pageId = (int)$pageId;
732  if ($workspace === 0) {
733  return false;
734  }
735 
736  if (isset($this->versionsOnPageCache[$pageId][$workspace])) {
737  return $this->versionsOnPageCache[$pageId][$workspace];
738  }
739 
740  if (!empty($this->versionsOnPageCache)) {
741  return false;
742  }
743 
744  $this->versionsOnPageCache[$pageId][$workspace] = false;
745  foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
746  if ($tableName === 'pages' || empty($tableConfiguration['ctrl']['versioningWS'])) {
747  continue;
748  }
749  // Consider records that are moved to a different page
750  $movePointer = new VersionState(VersionState::MOVE_POINTER);
751  $joinStatement = '(A.t3ver_oid=B.uid AND A.t3ver_state<>' . $movePointer
752  . ' OR A.t3ver_oid=B.t3ver_move_id AND A.t3ver_state=' . $movePointer . ')';
753 
754  // Select all records from this table in the database from the workspace
755  // This joins the online version with the offline version as tables A and B
756  $records = $this->getDatabaseConnection()->exec_SELECTgetRows(
757  'B.uid as live_uid, B.pid as live_pid, A.uid as offline_uid',
758  $tableName . ' A,' . $tableName . ' B',
759  'A.pid=-1 AND A.t3ver_wsid=' . $workspace . ' AND ' . $joinStatement .
760  BackendUtility::deleteClause($tableName, 'A') . BackendUtility::deleteClause($tableName, 'B'),
761  'live_pid'
762  );
763 
764  if (!empty($records)) {
765  foreach ($records as $record) {
766  $this->versionsOnPageCache[$record['live_pid']][$workspace] = true;
767  }
768  }
769  }
770 
771  return $this->versionsOnPageCache[$pageId][$workspace];
772  }
773 
777  protected function getDatabaseConnection()
778  {
779  return $GLOBALS['TYPO3_DB'];
780  }
781 
785  protected function getObjectManager()
786  {
787  return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
788  }
789 
796  public function getAvailableLanguages($pageId)
797  {
798  $languageOptions = array();
800  $translationConfigurationProvider = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider::class);
801  $systemLanguages = $translationConfigurationProvider->getSystemLanguages($pageId);
802 
803  if ($GLOBALS['BE_USER']->checkLanguageAccess(0)) {
804  // Use configured label for default language
805  $languageOptions[0] = $systemLanguages[0]['title'];
806  }
807  $pages = BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $pageId);
808 
809  if (!is_array($pages)) {
810  return $languageOptions;
811  }
812 
813  foreach ($pages as $page) {
814  $languageId = (int)$page['sys_language_uid'];
815  // Only add links to active languages the user has access to
816  if (isset($systemLanguages[$languageId]) && $GLOBALS['BE_USER']->checkLanguageAccess($languageId)) {
817  $languageOptions[$page['sys_language_uid']] = $systemLanguages[$languageId]['title'];
818  }
819  }
820 
821  return $languageOptions;
822  }
823 }