TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
20 use TYPO3\CMS\Core\Database\Query\Restriction;
26 
31 {
35  protected $pageCache = [];
36 
40  protected $versionsOnPageCache = [];
41 
45  protected $pagesWithVersionsInTable = [];
46 
47  const TABLE_WORKSPACE = 'sys_workspace';
48  const SELECT_ALL_WORKSPACES = -98;
49  const LIVE_WORKSPACE_ID = 0;
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 
65  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_workspace');
66  $queryBuilder->getRestrictions()
67  ->add(GeneralUtility::makeInstance(Restriction\RootLevelRestriction::class));
68 
69  $result = $queryBuilder
70  ->select('uid', 'title', 'adminusers', 'members')
71  ->from('sys_workspace')
72  ->orderBy('title')
73  ->execute();
74 
75  while ($workspace = $result->fetch()) {
76  if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
77  $availableWorkspaces[$workspace['uid']] = $workspace['title'];
78  }
79  }
80  return $availableWorkspaces;
81  }
82 
88  public function getCurrentWorkspace()
89  {
90  $workspaceId = $GLOBALS['BE_USER']->workspace;
91  $activeId = $GLOBALS['BE_USER']->getSessionData('tx_workspace_activeWorkspace');
92 
93  // Avoid invalid workspace settings
94  if ($activeId !== null && $activeId !== self::SELECT_ALL_WORKSPACES) {
95  $availableWorkspaces = $this->getAvailableWorkspaces();
96  if (!isset($availableWorkspaces[$activeId])) {
97  $activeId = null;
98  }
99  }
100 
101  if ($activeId !== null) {
102  $workspaceId = $activeId;
103  }
104 
105  return $workspaceId;
106  }
107 
115  public static function getWorkspaceTitle($wsId)
116  {
117  $title = false;
118  switch ($wsId) {
119  case self::LIVE_WORKSPACE_ID:
120  $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_misc.xlf:shortcut_onlineWS');
121  break;
122  default:
123  $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
124  $wsRecord = BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
125  if (is_array($wsRecord)) {
126  $title = $wsRecord[$labelField];
127  }
128  }
129  if ($title === false) {
130  throw new \InvalidArgumentException('No such workspace defined', 1476045469);
131  }
132  return $title;
133  }
134 
144  public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0, $language = null)
145  {
146  $wsid = (int)$wsid;
147  $cmd = [];
148  if ($wsid >= -1 && $wsid !== 0) {
149  // Define stage to select:
150  $stage = -99;
151  if ($wsid > 0) {
152  $workspaceRec = BackendUtility::getRecord('sys_workspace', $wsid);
153  if ($workspaceRec['publish_access'] & 1) {
155  }
156  }
157  // Select all versions to swap:
158  $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
159  // Traverse the selection to build CMD array:
160  foreach ($versions as $table => $records) {
161  foreach ($records as $rec) {
162  // Build the cmd Array:
163  $cmd[$table][$rec['t3ver_oid']]['version'] = ['action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0];
164  }
165  }
166  }
167  return $cmd;
168  }
169 
179  public function getCmdArrayForFlushWS($wsid, $flush = true, $pageId = 0, $language = null)
180  {
181  $wsid = (int)$wsid;
182  $cmd = [];
183  if ($wsid >= -1 && $wsid !== 0) {
184  // Define stage to select:
185  $stage = -99;
186  // Select all versions to swap:
187  $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 999, 'tables_modify', $language);
188  // Traverse the selection to build CMD array:
189  foreach ($versions as $table => $records) {
190  foreach ($records as $rec) {
191  // Build the cmd Array:
192  $cmd[$table][$rec['uid']]['version'] = ['action' => $flush ? 'flush' : 'clearWSID'];
193  }
194  }
195  }
196  return $cmd;
197  }
198 
213  public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = null)
214  {
215  $wsid = (int)$wsid;
216  $filter = (int)$filter;
217  $output = [];
218  // Contains either nothing or a list with live-uids
219  if ($pageId != -1 && $recursionLevel > 0) {
220  $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
221  } elseif ($pageId != -1) {
222  $pageList = $pageId;
223  } else {
224  $pageList = '';
225  // check if person may only see a "virtual" page-root
226  $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
227  $mountPoints = array_unique($mountPoints);
228  if (!in_array(0, $mountPoints)) {
229  $tempPageIds = [];
230  foreach ($mountPoints as $mountPoint) {
231  $tempPageIds[] = $this->getTreeUids($mountPoint, $wsid, $recursionLevel);
232  }
233  $pageList = implode(',', $tempPageIds);
234  $pageList = implode(',', array_unique(explode(',', $pageList)));
235  }
236  }
237  // Traversing all tables supporting versioning:
238  foreach ($GLOBALS['TCA'] as $table => $cfg) {
239  // we do not collect records from tables without permissions on them.
240  if (!$GLOBALS['BE_USER']->check($selectionType, $table)) {
241  continue;
242  }
243  if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
244  $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language);
245  $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
246  $recs = array_merge($recs, $moveRecs);
247  $recs = $this->filterPermittedElements($recs, $table);
248  if (!empty($recs)) {
249  $output[$table] = $recs;
250  }
251  }
252  }
253  return $output;
254  }
255 
267  protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language = null)
268  {
269  // Include root level page as there might be some records with where root level
270  // restriction is ignored (e.g. FAL records)
271  if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
272  $pageList .= ',0';
273  }
274  $isTableLocalizable = BackendUtility::isTableLocalizable($table);
275  $languageParentField = '';
276  // If table is not localizable, but localized reocrds shall
277  // be collected, an empty result array needs to be returned:
278  if ($isTableLocalizable === false && $language > 0) {
279  return [];
280  } elseif ($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 
742  public static function viewSingleRecord($table, $uid, array $liveRecord = null, array $versionRecord = null)
743  {
744  if ($table === 'pages') {
746  }
747 
748  if ($liveRecord === null) {
749  $liveRecord = BackendUtility::getLiveVersionOfRecord($table, $uid);
750  }
751  if ($versionRecord === null) {
752  $versionRecord = BackendUtility::getRecord($table, $uid);
753  }
754  if (VersionState::cast($versionRecord['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
755  $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], 'pid');
756  }
757 
758  // Directly use pid value and consider move placeholders
759  $previewPageId = (empty($movePlaceholder['pid']) ? $liveRecord['pid'] : $movePlaceholder['pid']);
760  $additionalParameters = '&tx_workspaces_web_workspacesworkspaces[previewWS]=' . $versionRecord['t3ver_wsid'];
761  // Add language parameter if record is a localization
763  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
764  if ($versionRecord[$languageField] > 0) {
765  $additionalParameters .= '&L=' . $versionRecord[$languageField];
766  }
767  }
768 
769  $pageTsConfig = BackendUtility::getPagesTSconfig($previewPageId);
770  $viewUrl = '';
771 
772  // Directly use determined direct page id
773  if ($table === 'pages_language_overlay' || $table === 'tt_content') {
774  $viewUrl = BackendUtility::viewOnClick($previewPageId, '', '', '', '', $additionalParameters);
775  // Analyze Page TSconfig options.workspaces.previewPageId
776  } elseif (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table]) || !empty($pageTsConfig['options.']['workspaces.']['previewPageId'])) {
777  if (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table])) {
778  $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId.'][$table];
779  } else {
780  $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId'];
781  }
782  // Extract possible settings (e.g. "field:pid")
783  list($previewKey, $previewValue) = explode(':', $previewConfiguration, 2);
784  if ($previewKey === 'field') {
785  $previewPageId = (int)$liveRecord[$previewValue];
786  } else {
787  $previewPageId = (int)$previewConfiguration;
788  }
789  $viewUrl = BackendUtility::viewOnClick($previewPageId, '', '', '', '', $additionalParameters);
790  // Call user function to render the single record view
791  } elseif (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
792  $_params = [
793  'table' => $table,
794  'uid' => $uid,
795  'record' => $liveRecord,
796  'liveRecord' => $liveRecord,
797  'versionRecord' => $versionRecord,
798  ];
799  $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
800  $null = null;
801  $viewUrl = GeneralUtility::callUserFunction($_funcRef, $_params, $null);
802  }
803 
804  return $viewUrl;
805  }
806 
814  public function canCreatePreviewLink($pageUid, $workspaceUid)
815  {
816  $result = true;
817  if ($pageUid > 0 && $workspaceUid > 0) {
818  $pageRecord = BackendUtility::getRecord('pages', $pageUid);
819  BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
820  if (
821  !GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'], $pageRecord['doktype'])
822  || VersionState::cast($pageRecord['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
823  ) {
824  $result = false;
825  }
826  } else {
827  $result = false;
828  }
829  return $result;
830  }
831 
838  public function generateWorkspacePreviewLink($uid)
839  {
840  $previewObject = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Hook\PreviewHook::class);
841  $timeToLiveHours = $previewObject->getPreviewLinkLifetime();
842  $previewKeyword = $previewObject->compilePreviewKeyword('', $GLOBALS['BE_USER']->user['uid'], $timeToLiveHours * 3600, $this->getCurrentWorkspace());
843  $linkParams = [
844  'ADMCMD_prev' => $previewKeyword,
845  'id' => $uid
846  ];
847  return BackendUtility::getViewDomain($uid) . '/index.php?' . GeneralUtility::implodeArrayForUrl('', $linkParams);
848  }
849 
857  public function generateWorkspaceSplittedPreviewLink($uid, $addDomain = false)
858  {
859  // In case a $pageUid is submitted we need to make sure it points to a live-page
860  if ($uid > 0) {
861  $uid = $this->getLivePageUid($uid);
862  }
864  $uriBuilder = $this->getObjectManager()->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
865  $redirect = 'index.php?redirect_url=';
866  // @todo this should maybe be changed so that the extbase URI Builder can deal with module names directly
867  $originalM = GeneralUtility::_GET('M');
868  GeneralUtility::_GETset('web_WorkspacesWorkspaces', 'M');
869  $viewScript = $uriBuilder->uriFor('index', [], 'Preview', 'workspaces', 'web_workspacesworkspaces') . '&id=';
870  GeneralUtility::_GETset($originalM, 'M');
871  if ($addDomain === true) {
872  return BackendUtility::getViewDomain($uid) . $redirect . urlencode($viewScript) . $uid;
873  } else {
874  return $viewScript;
875  }
876  }
877 
885  {
886  $previewUrl = $this->generateWorkspacePreviewLink($uid);
887  $previewLanguages = $this->getAvailableLanguages($uid);
888  $previewLinks = [];
889 
890  foreach ($previewLanguages as $languageUid => $language) {
891  $previewLinks[$language] = $previewUrl . '&L=' . $languageUid;
892  }
893 
894  return $previewLinks;
895  }
896 
905  public function getLivePageUid($uid)
906  {
907  if (!isset($this->pageCache[$uid])) {
908  $pageRecord = BackendUtility::getRecord('pages', $uid);
909  if (is_array($pageRecord)) {
910  $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? $pageRecord['t3ver_oid'] : $uid;
911  } else {
912  throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was: ' . $uid, 1290628113);
913  }
914  }
915  return $this->pageCache[$uid];
916  }
917 
925  public function hasPageRecordVersions($workspaceId, $pageId)
926  {
927  if ((int)$workspaceId === 0 || (int)$pageId === 0) {
928  return false;
929  }
930 
931  if (isset($this->versionsOnPageCache[$workspaceId][$pageId])) {
932  return $this->versionsOnPageCache[$workspaceId][$pageId];
933  }
934 
935  $this->versionsOnPageCache[$workspaceId][$pageId] = false;
936 
937  foreach ($GLOBALS['TCA'] as $tableName => $tableConfiguration) {
938  if ($tableName === 'pages' || empty($tableConfiguration['ctrl']['versioningWS'])) {
939  continue;
940  }
941 
942  $pages = $this->fetchPagesWithVersionsInTable($workspaceId, $tableName);
943  // Early break on first match
944  if (!empty($pages[(string)$pageId])) {
945  $this->versionsOnPageCache[$workspaceId][$pageId] = true;
946  break;
947  }
948  }
949 
950  if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['hasPageRecordVersions'])
951  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['hasPageRecordVersions'])) {
952  $parameters = [
953  'workspaceId' => $workspaceId,
954  'pageId' => $pageId,
955  'versionsOnPageCache' => &$this->versionsOnPageCache,
956  ];
957  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['hasPageRecordVersions'] as $hookFunction) {
958  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
959  }
960  }
961 
962  return $this->versionsOnPageCache[$workspaceId][$pageId];
963  }
964 
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  // Consider records that are moved to a different page
1028  $movePointer = new VersionState(VersionState::MOVE_POINTER);
1029 
1030  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
1031  $queryBuilder->getRestrictions()
1032  ->removeAll()
1033  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
1034 
1035  $result = $queryBuilder
1036  ->select('B.pid AS pageId')
1037  ->from($tableName, 'A')
1038  ->from($tableName, 'B')
1039  ->where(
1040  $queryBuilder->expr()->eq(
1041  'A.pid',
1042  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1043  ),
1044  $queryBuilder->expr()->eq(
1045  'A.t3ver_wsid',
1046  $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1047  ),
1048  $queryBuilder->expr()->orX(
1049  $queryBuilder->expr()->andX(
1050  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid')),
1051  $queryBuilder->expr()->neq(
1052  'A.t3ver_state',
1053  $queryBuilder->createNamedParameter($movePointer, \PDO::PARAM_STR)
1054  )
1055 
1056  ),
1057  $queryBuilder->expr()->andX(
1058  $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.t3ver_move_id')),
1059  $queryBuilder->expr()->eq(
1060  'A.t3ver_state',
1061  $queryBuilder->createNamedParameter($movePointer, \PDO::PARAM_STR)
1062  )
1063  )
1064  )
1065  )
1066  ->groupBy('pageId')
1067  ->execute();
1068 
1069  $pageIds = [];
1070  while ($row = $result->fetch()) {
1071  $pageIds[$row['uid']] = $row;
1072  }
1073 
1074  $this->pagesWithVersionsInTable[$workspaceId][$tableName] = $pageIds;
1075 
1076  if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['fetchPagesWithVersionsInTable'])
1077  && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['fetchPagesWithVersionsInTable'])) {
1078  $parameters = [
1079  'workspaceId' => $workspaceId,
1080  'tableName' => $tableName,
1081  'pagesWithVersionsInTable' => &$this->pagesWithVersionsInTable,
1082  ];
1083  foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\\CMS\\Workspaces\\Service\\WorkspaceService']['fetchPagesWithVersionsInTable'] as $hookFunction) {
1084  GeneralUtility::callUserFunction($hookFunction, $parameters, $this);
1085  }
1086  }
1087  }
1088 
1089  return $this->pagesWithVersionsInTable[$workspaceId][$tableName];
1090  }
1091 
1095  protected function getObjectManager()
1096  {
1097  return GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
1098  }
1099 
1106  public function getAvailableLanguages($pageId)
1107  {
1108  $languageOptions = [];
1110  $translationConfigurationProvider = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider::class);
1111  $systemLanguages = $translationConfigurationProvider->getSystemLanguages($pageId);
1112 
1113  if ($GLOBALS['BE_USER']->checkLanguageAccess(0)) {
1114  // Use configured label for default language
1115  $languageOptions[0] = $systemLanguages[0]['title'];
1116  }
1117  $pages = BackendUtility::getRecordsByField('pages_language_overlay', 'pid', $pageId);
1118 
1119  if (!is_array($pages)) {
1120  return $languageOptions;
1121  }
1122 
1123  foreach ($pages as $page) {
1124  $languageId = (int)$page['sys_language_uid'];
1125  // Only add links to active languages the user has access to
1126  if (isset($systemLanguages[$languageId]) && $GLOBALS['BE_USER']->checkLanguageAccess($languageId)) {
1127  $languageOptions[$page['sys_language_uid']] = $systemLanguages[$languageId]['title'];
1128  }
1129  }
1130 
1131  return $languageOptions;
1132  }
1133 }
getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage)
static getMovePlaceholder($table, $uid, $fields= '*', $workspace=null)
static implodeArrayForUrl($name, array $theArray, $str= '', $skipBlank=false, $rawurlencodeParamName=false)
static viewSingleRecord($table, $uid, array $liveRecord=null, array $versionRecord=null)
fetchPagesWithVersionsInTable($workspaceId, $tableName)
static viewOnClick($pageUid, $backPath= '', $rootLine=null, $anchorSection= '', $alternativeUrl= '', $additionalGetVars= '', $switchFocus=true)
static workspaceOL($table, &$row, $wsid=-99, $unsetMovePointers=false)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
static getRecordsByField($theTable, $theField, $theValue, $whereClause= '', $groupBy= '', $orderBy= '', $limit= '', $useDeleteClause=true, $queryBuilder=null)
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
static _GETset($inputGet, $key= '')
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
getCmdArrayForFlushWS($wsid, $flush=true, $pageId=0, $language=null)
selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language=null)
selectVersionsInWorkspace($wsid, $filter=0, $stage=-99, $pageId=-1, $recursionLevel=0, $selectionType= 'tables_select', $language=null)
getCmdArrayForPublishWS($wsid, $doSwap, $pageId=0, $language=null)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static callUserFunction($funcName, &$params, &$ref, $_= '', $errorMode=0)