[BUGFIX] Make sys_file_metadata publishable
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Service / WorkspaceService.php
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
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19 use TYPO3\CMS\Core\Versioning\VersionState;
20
21 /**
22 * Workspace service
23 *
24 * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
25 */
26 class WorkspaceService implements \TYPO3\CMS\Core\SingletonInterface {
27
28 /**
29 * @var array
30 */
31 protected $pageCache = array();
32
33 const TABLE_WORKSPACE = 'sys_workspace';
34 const SELECT_ALL_WORKSPACES = -98;
35 const LIVE_WORKSPACE_ID = 0;
36 /**
37 * retrieves the available workspaces from the database and checks whether
38 * they're available to the current BE user
39 *
40 * @return array array of worspaces available to the current user
41 */
42 public function getAvailableWorkspaces() {
43 $availableWorkspaces = array();
44 // add default workspaces
45 if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::LIVE_WORKSPACE_ID))) {
46 $availableWorkspaces[self::LIVE_WORKSPACE_ID] = self::getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
47 }
48 // add custom workspaces (selecting all, filtering by BE_USER check):
49 $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, title, adminusers, members', 'sys_workspace', 'pid = 0' . BackendUtility::deleteClause('sys_workspace'), '', 'title');
50 if (count($customWorkspaces)) {
51 foreach ($customWorkspaces as $workspace) {
52 if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
53 $availableWorkspaces[$workspace['uid']] = $workspace['title'];
54 }
55 }
56 }
57 return $availableWorkspaces;
58 }
59
60 /**
61 * Gets the current workspace ID.
62 *
63 * @return integer The current workspace ID
64 */
65 public function getCurrentWorkspace() {
66 $workspaceId = $GLOBALS['BE_USER']->workspace;
67 $activeId = $GLOBALS['BE_USER']->getSessionData('tx_workspace_activeWorkspace');
68
69 // Avoid invalid workspace settings
70 if ($activeId !== NULL && $activeId !== self::SELECT_ALL_WORKSPACES) {
71 $availableWorkspaces = $this->getAvailableWorkspaces();
72 if (!isset($availableWorkspaces[$activeId])) {
73 $activeId = NULL;
74 }
75 }
76
77 if ($activeId !== NULL) {
78 $workspaceId = $activeId;
79 }
80
81 return $workspaceId;
82 }
83
84 /**
85 * Find the title for the requested workspace.
86 *
87 * @param integer $wsId
88 * @return string
89 */
90 static public function getWorkspaceTitle($wsId) {
91 $title = FALSE;
92 switch ($wsId) {
93 case self::LIVE_WORKSPACE_ID:
94 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xlf:shortcut_onlineWS');
95 break;
96 default:
97 $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
98 $wsRecord = BackendUtility::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
99 if (is_array($wsRecord)) {
100 $title = $wsRecord[$labelField];
101 }
102 }
103 if ($title === FALSE) {
104 throw new \InvalidArgumentException('No such workspace defined');
105 }
106 return $title;
107 }
108
109 /**
110 * Building tcemain CMD-array for swapping all versions in a workspace.
111 *
112 * @param integer Real workspace ID, cannot be ONLINE (zero).
113 * @param boolean If set, then the currently online versions are swapped into the workspace in exchange for the offline versions. Otherwise the workspace is emptied.
114 * @param integer $pageId: ...
115 * @param integer $language Select specific language only
116 * @return array Command array for tcemain
117 */
118 public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0, $language = NULL) {
119 $wsid = (int)$wsid;
120 $cmd = array();
121 if ($wsid >= -1 && $wsid !== 0) {
122 // Define stage to select:
123 $stage = -99;
124 if ($wsid > 0) {
125 $workspaceRec = BackendUtility::getRecord('sys_workspace', $wsid);
126 if ($workspaceRec['publish_access'] & 1) {
127 $stage = \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_ID;
128 }
129 }
130 // Select all versions to swap:
131 $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 0, 'tables_modify', $language);
132 // Traverse the selection to build CMD array:
133 foreach ($versions as $table => $records) {
134 foreach ($records as $rec) {
135 // Build the cmd Array:
136 $cmd[$table][$rec['t3ver_oid']]['version'] = array('action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0);
137 }
138 }
139 }
140 return $cmd;
141 }
142
143 /**
144 * Building tcemain CMD-array for releasing all versions in a workspace.
145 *
146 * @param integer Real workspace ID, cannot be ONLINE (zero).
147 * @param boolean Run Flush (TRUE) or ClearWSID (FALSE) command
148 * @param integer $pageId: ...
149 * @param integer $language Select specific language only
150 * @return array Command array for tcemain
151 */
152 public function getCmdArrayForFlushWS($wsid, $flush = TRUE, $pageId = 0, $language = NULL) {
153 $wsid = (int)$wsid;
154 $cmd = array();
155 if ($wsid >= -1 && $wsid !== 0) {
156 // Define stage to select:
157 $stage = -99;
158 // Select all versions to swap:
159 $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ?: -1, 0, 'tables_modify', $language);
160 // Traverse the selection to build CMD array:
161 foreach ($versions as $table => $records) {
162 foreach ($records as $rec) {
163 // Build the cmd Array:
164 $cmd[$table][$rec['uid']]['version'] = array('action' => $flush ? 'flush' : 'clearWSID');
165 }
166 }
167 }
168 return $cmd;
169 }
170
171 /**
172 * Select all records from workspace pending for publishing
173 * Used from backend to display workspace overview
174 * User for auto-publishing for selecting versions for publication
175 *
176 * @param integer Workspace ID. If -99, will select ALL versions from ANY workspace. If -98 will select all but ONLINE. >=-1 will select from the actual workspace
177 * @param integer Lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple), anything else selects all.
178 * @param integer Stage filter: -99 means no filtering, otherwise it will be used to select only elements with that stage. For publishing, that would be "10
179 * @param integer Page id: Live page for which to find versions in workspace!
180 * @param integer Recursion Level - select versions recursive - parameter is only relevant if $pageId != -1
181 * @param string How to collect records for "listing" or "modify" these tables. Support the permissions of each type of record, see \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::check.
182 * @param integer $language Select specific language only
183 * @return array Array of all records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oidfields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid
184 */
185 public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select', $language = NULL) {
186 $wsid = (int)$wsid;
187 $filter = (int)$filter;
188 $output = array();
189 // Contains either nothing or a list with live-uids
190 if ($pageId != -1 && $recursionLevel > 0) {
191 $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
192 } elseif ($pageId != -1) {
193 $pageList = $pageId;
194 } else {
195 $pageList = '';
196 // check if person may only see a "virtual" page-root
197 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
198 $mountPoints = array_unique($mountPoints);
199 if (!in_array(0, $mountPoints)) {
200 $tempPageIds = array();
201 foreach ($mountPoints as $mountPoint) {
202 $tempPageIds[] = $this->getTreeUids($mountPoint, $wsid, $recursionLevel);
203 }
204 $pageList = implode(',', $tempPageIds);
205 }
206 }
207 // Traversing all tables supporting versioning:
208 foreach ($GLOBALS['TCA'] as $table => $cfg) {
209 // we do not collect records from tables without permissions on them.
210 if (!$GLOBALS['BE_USER']->check($selectionType, $table)) {
211 continue;
212 }
213 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
214 $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language);
215 if ((int)$GLOBALS['TCA'][$table]['ctrl']['versioningWS'] === 2) {
216 $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
217 $recs = array_merge($recs, $moveRecs);
218 }
219 $recs = $this->filterPermittedElements($recs, $table);
220 if (count($recs)) {
221 $output[$table] = $recs;
222 }
223 }
224 }
225 return $output;
226 }
227
228 /**
229 * Find all versionized elements except moved records.
230 *
231 * @param string $table
232 * @param string $pageList
233 * @param integer $wsid
234 * @param integer $filter
235 * @param integer $stage
236 * @param integer $language
237 * @return array
238 */
239 protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage, $language = NULL) {
240 // Include root level page as there might be some records with where root level restriction is ignored (e.g. FAL records)
241 if ($pageList !== '' && BackendUtility::isRootLevelRestrictionIgnored($table)) {
242 $pageList .= ',0';
243 }
244 $isTableLocalizable = BackendUtility::isTableLocalizable($table);
245 $languageParentField = '';
246 // If table is not localizable, but localized reocrds shall
247 // be collected, an empty result array needs to be returned:
248 if ($isTableLocalizable === FALSE && $language > 0) {
249 return array();
250 } elseif ($isTableLocalizable) {
251 $languageParentField = 'A.' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . ', ';
252 }
253 $fields = 'A.uid, A.t3ver_oid, A.t3ver_stage, ' . $languageParentField . 'B.pid AS wspid, B.pid AS livepid';
254 if ($isTableLocalizable) {
255 $fields .= ', A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'];
256 }
257 $from = $table . ' A,' . $table . ' B';
258 // Table A is the offline version and pid=-1 defines offline
259 $where = 'A.pid=-1 AND A.t3ver_state!=' . new VersionState(VersionState::MOVE_POINTER);
260 if ($pageList) {
261 $pidField = $table === 'pages' ? 'uid' : 'pid';
262 $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
263 $where .= ' AND B.' . $pidField . $pidConstraint;
264 }
265 if ($isTableLocalizable && \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($language)) {
266 $where .= ' AND A.' . $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . $language;
267 }
268 // For "real" workspace numbers, select by that.
269 // If = -98, select all that are NOT online (zero).
270 // Anything else below -1 will not select on the wsid and therefore select all!
271 if ($wsid > self::SELECT_ALL_WORKSPACES) {
272 $where .= ' AND A.t3ver_wsid=' . $wsid;
273 } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
274 $where .= ' AND A.t3ver_wsid!=0';
275 }
276 // lifecycle filter:
277 // 1 = select all drafts (never-published),
278 // 2 = select all published one or more times (archive/multiple)
279 if ($filter === 1 || $filter === 2) {
280 $where .= ' AND A.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
281 }
282 if ($stage != -99) {
283 $where .= ' AND A.t3ver_stage=' . (int)$stage;
284 }
285 // Table B (online) must have PID >= 0 to signify being online.
286 $where .= ' AND B.pid>=0';
287 // ... and finally the join between the two tables.
288 $where .= ' AND A.t3ver_oid=B.uid';
289 $where .= BackendUtility::deleteClause($table, 'A');
290 $where .= BackendUtility::deleteClause($table, 'B');
291 // Select all records from this table in the database from the workspace
292 // This joins the online version with the offline version as tables A and B
293 // Order by UID, mostly to have a sorting in the backend overview module which doesn't "jump around" when swapping.
294 $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'B.uid');
295 return is_array($res) ? $res : array();
296 }
297
298 /**
299 * Find all moved records at their new position.
300 *
301 * @param string $table
302 * @param string $pageList
303 * @param integer $wsid
304 * @param integer $filter
305 * @param integer $stage
306 * @return array
307 */
308 protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage) {
309 // Aliases:
310 // A - moveTo placeholder
311 // B - online record
312 // C - moveFrom placeholder
313 $fields = 'A.pid AS wspid, B.uid AS t3ver_oid, C.uid AS uid, B.pid AS livepid';
314 $from = $table . ' A, ' . $table . ' B,' . $table . ' C';
315 $where = 'A.t3ver_state=' . new VersionState(VersionState::MOVE_PLACEHOLDER) . ' AND B.pid>0 AND B.t3ver_state='
316 . new VersionState(VersionState::DEFAULT_STATE) . ' AND B.t3ver_wsid=0 AND C.pid=-1 AND C.t3ver_state='
317 . new VersionState(VersionState::MOVE_POINTER);
318 if ($wsid > self::SELECT_ALL_WORKSPACES) {
319 $where .= ' AND A.t3ver_wsid=' . $wsid . ' AND C.t3ver_wsid=' . $wsid;
320 } elseif ($wsid === self::SELECT_ALL_WORKSPACES) {
321 $where .= ' AND A.t3ver_wsid!=0 AND C.t3ver_wsid!=0 ';
322 }
323 // lifecycle filter:
324 // 1 = select all drafts (never-published),
325 // 2 = select all published one or more times (archive/multiple)
326 if ($filter === 1 || $filter === 2) {
327 $where .= ' AND C.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
328 }
329 if ($stage != -99) {
330 $where .= ' AND C.t3ver_stage=' . (int)$stage;
331 }
332 if ($pageList) {
333 $pidField = $table === 'pages' ? 'B.uid' : 'A.pid';
334 $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
335 $where .= ' AND ' . $pidField . $pidConstraint;
336 }
337 $where .= ' AND A.t3ver_move_id = B.uid AND B.uid = C.t3ver_oid';
338 $where .= BackendUtility::deleteClause($table, 'A');
339 $where .= BackendUtility::deleteClause($table, 'B');
340 $where .= BackendUtility::deleteClause($table, 'C');
341 $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'A.uid');
342 return is_array($res) ? $res : array();
343 }
344
345 /**
346 * Find all page uids recursive starting from a specific page
347 *
348 * @param integer $pageId
349 * @param integer $wsid
350 * @param integer $recursionLevel
351 * @return string Comma sep. uid list
352 */
353 protected function getTreeUids($pageId, $wsid, $recursionLevel) {
354 // Reusing existing functionality with the drawback that
355 // mount points are not covered yet
356 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
357 /** @var $searchObj \TYPO3\CMS\Core\Database\QueryView */
358 $searchObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\QueryView');
359 if ($pageId > 0) {
360 $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
361 } else {
362 $mountPoints = $GLOBALS['BE_USER']->uc['pageTree_temporaryMountPoint'];
363 if (!is_array($mountPoints) || empty($mountPoints)) {
364 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
365 $mountPoints = array_unique($mountPoints);
366 }
367 $newList = array();
368 foreach ($mountPoints as $mountPoint) {
369 $newList[] = $searchObj->getTreeList($mountPoint, $recursionLevel, 0, $perms_clause);
370 }
371 $pageList = implode(',', $newList);
372 }
373 unset($searchObj);
374 if ((int)$GLOBALS['TCA']['pages']['ctrl']['versioningWS'] === 2 && $pageList) {
375 // Remove the "subbranch" if a page was moved away
376 $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');
377 $pageIds = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $pageList, TRUE);
378 // move all pages away
379 $newList = array_diff($pageIds, array_keys($movedAwayPages));
380 // keep current page in the list
381 $newList[] = $pageId;
382 // move back in if still connected to the "remaining" pages
383 do {
384 $changed = FALSE;
385 foreach ($movedAwayPages as $uid => $rec) {
386 if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
387 $newList[] = $uid;
388 $changed = TRUE;
389 }
390 }
391 } while ($changed);
392 $pageList = implode(',', $newList);
393 // In case moving pages is enabled we need to replace all move-to pointer with their origin
394 $pages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, t3ver_move_id', 'pages', 'uid IN (' . $pageList . ')' . BackendUtility::deleteClause('pages'), '', 'uid', '', 'uid');
395 $newList = array();
396 $pageIds = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $pageList, TRUE);
397 if (!in_array($pageId, $pageIds)) {
398 $pageIds[] = $pageId;
399 }
400 foreach ($pageIds as $pageId) {
401 if ((int)$pages[$pageId]['t3ver_move_id'] > 0) {
402 $newList[] = (int)$pages[$pageId]['t3ver_move_id'];
403 } else {
404 $newList[] = $pageId;
405 }
406 }
407 $pageList = implode(',', $newList);
408 }
409 return $pageList;
410 }
411
412 /**
413 * Remove all records which are not permitted for the user
414 *
415 * @param array $recs
416 * @param string $table
417 * @return array
418 */
419 protected function filterPermittedElements($recs, $table) {
420 $permittedElements = array();
421 if (is_array($recs)) {
422 foreach ($recs as $rec) {
423 if ($this->isPageAccessibleForCurrentUser($table, $rec) && $this->isLanguageAccessibleForCurrentUser($table, $rec)) {
424 $permittedElements[] = $rec;
425 }
426 }
427 }
428 return $permittedElements;
429 }
430
431 /**
432 * Checking access to the page the record is on, respecting ignored root level restrictions
433 *
434 * @param string $table Name of the table
435 * @param array $record Record row to be checked
436 * @return bool
437 */
438 protected function isPageAccessibleForCurrentUser($table, array $record) {
439 $pageIdField = $table === 'pages' ? 'uid' : 'wspid';
440 $pageId = isset($record[$pageIdField]) ? (int)$record[$pageIdField] : NULL;
441 if ($pageId === NULL) {
442 return FALSE;
443 }
444 if ($pageId === 0 && BackendUtility::isRootLevelRestrictionIgnored($table)) {
445 return TRUE;
446 }
447 $page = BackendUtility::getRecord('pages', $pageId, 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
448
449 return $GLOBALS['BE_USER']->doesUserHaveAccess($page, 1);
450 }
451
452 /**
453 * Check current be users language access on given record.
454 *
455 * @param string $table Name of the table
456 * @param array $record Record row to be checked
457 * @return boolean
458 */
459 protected function isLanguageAccessibleForCurrentUser($table, array $record) {
460 if (BackendUtility::isTableLocalizable($table)) {
461 $languageUid = $record[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
462 } else {
463 return TRUE;
464 }
465 return $GLOBALS['BE_USER']->checkLanguageAccess($languageUid);
466 }
467
468 /**
469 * Trivial check to see if the user already migrated his workspaces
470 * to the new style (either manually or with the migrator scripts)
471 *
472 * @return boolean
473 */
474 static public function isOldStyleWorkspaceUsed() {
475 $cacheKey = 'workspace-oldstyleworkspace-notused';
476 $cacheResult = $GLOBALS['BE_USER']->getSessionData($cacheKey);
477 if (!$cacheResult) {
478 $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
479 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
480 $oldStyleWorkspaceIsUsed = $count > 0;
481 $GLOBALS['BE_USER']->setAndSaveSessionData($cacheKey, !$oldStyleWorkspaceIsUsed);
482 } else {
483 $oldStyleWorkspaceIsUsed = !$cacheResult;
484 }
485 return $oldStyleWorkspaceIsUsed;
486 }
487
488 /**
489 * Determine whether a specific page is new and not yet available in the LIVE workspace
490 *
491 * @static
492 * @param integer $id Primary key of the page to check
493 * @param integer $language Language for which to check the page
494 * @return boolean
495 */
496 static public function isNewPage($id, $language = 0) {
497 $isNewPage = FALSE;
498 // If the language is not default, check state of overlay
499 if ($language > 0) {
500 $whereClause = 'pid = ' . (int)$id;
501 $whereClause .= ' AND ' . $GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'] . ' = ' . (int)$language;
502 $whereClause .= ' AND t3ver_wsid = ' . (int)$GLOBALS['BE_USER']->workspace;
503 $whereClause .= BackendUtility::deleteClause('pages_language_overlay');
504 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('t3ver_state', 'pages_language_overlay', $whereClause);
505 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
506 $isNewPage = VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
507 }
508 } else {
509 $rec = BackendUtility::getRecord('pages', $id, 't3ver_state');
510 if (is_array($rec)) {
511 $isNewPage = VersionState::cast($rec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER);
512 }
513 }
514 return $isNewPage;
515 }
516
517 /**
518 * Generates a view link for a page.
519 *
520 * @static
521 * @param string $table Table to be used
522 * @param integer $uid Uid of the version(!) record
523 * @param array $liveRecord Optional live record data
524 * @param array $versionRecord Optional version record data
525 * @return string
526 */
527 static public function viewSingleRecord($table, $uid, array $liveRecord = NULL, array $versionRecord = NULL) {
528 $viewUrl = '';
529
530 if ($table == 'pages') {
531 $viewUrl = BackendUtility::viewOnClick(BackendUtility::getLiveVersionIdOfRecord('pages', $uid));
532 } elseif ($table === 'pages_language_overlay' || $table === 'tt_content') {
533 if ($liveRecord === NULL) {
534 $liveRecord = BackendUtility::getLiveVersionOfRecord($table, $uid);
535 }
536 if ($versionRecord === NULL) {
537 $versionRecord = BackendUtility::getRecord($table, $uid);
538 }
539 if (VersionState::cast($versionRecord['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
540 $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], 'pid');
541 }
542
543 $previewPageId = (empty($movePlaceholder['pid']) ? $liveRecord['pid'] : $movePlaceholder['pid']);
544 $additionalParameters = '&tx_workspaces_web_workspacesworkspaces[previewWS]=' . $versionRecord['t3ver_wsid'];
545
546 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
547 if ($versionRecord[$languageField] > 0) {
548 $additionalParameters .= '&L=' . $versionRecord[$languageField];
549 }
550
551 $viewUrl = BackendUtility::viewOnClick($previewPageId, '', '', '', '', $additionalParameters);
552 } else {
553 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
554 $_params = array(
555 'table' => $table,
556 'uid' => $uid,
557 'record' => $liveRecord,
558 'liveRecord' => $liveRecord,
559 'versionRecord' => $versionRecord,
560 );
561 $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
562 $null = NULL;
563 $viewUrl = \TYPO3\CMS\Core\Utility\GeneralUtility::callUserFunction($_funcRef, $_params, $null);
564 }
565 }
566
567 return $viewUrl;
568 }
569
570 /**
571 * Determine whether this page for the current
572 *
573 * @param integer $pageUid
574 * @param integer $workspaceUid
575 * @return boolean
576 */
577 public function canCreatePreviewLink($pageUid, $workspaceUid) {
578 $result = TRUE;
579 if ($pageUid > 0 && $workspaceUid > 0) {
580 $pageRecord = BackendUtility::getRecord('pages', $pageUid);
581 BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
582 if (
583 !GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'], $pageRecord['doktype'])
584 || VersionState::cast($pageRecord['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
585 ) {
586 $result = FALSE;
587 }
588 } else {
589 $result = FALSE;
590 }
591 return $result;
592 }
593
594 /**
595 * Generates a workspace preview link.
596 *
597 * @param integer $uid The ID of the record to be linked
598 * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
599 */
600 public function generateWorkspacePreviewLink($uid) {
601 $previewObject = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Version\\Hook\\PreviewHook');
602 $timeToLiveHours = $previewObject->getPreviewLinkLifetime();
603 $previewKeyword = $previewObject->compilePreviewKeyword('', $GLOBALS['BE_USER']->user['uid'], $timeToLiveHours * 3600, $this->getCurrentWorkspace());
604 $linkParams = array(
605 'ADMCMD_prev' => $previewKeyword,
606 'id' => $uid
607 );
608 return BackendUtility::getViewDomain($uid) . '/index.php?' . \TYPO3\CMS\Core\Utility\GeneralUtility::implodeArrayForUrl('', $linkParams);
609 }
610
611 /**
612 * Generates a workspace splitted preview link.
613 *
614 * @param integer $uid The ID of the record to be linked
615 * @param boolean $addDomain Parameter to decide if domain should be added to the generated link, FALSE per default
616 * @return string the preview link without the trailing '/'
617 */
618 public function generateWorkspaceSplittedPreviewLink($uid, $addDomain = FALSE) {
619 // In case a $pageUid is submitted we need to make sure it points to a live-page
620 if ($uid > 0) {
621 $uid = $this->getLivePageUid($uid);
622 }
623 /** @var $uriBuilder \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder */
624 $uriBuilder = $this->getObjectManager()->get('TYPO3\\CMS\\Extbase\\Mvc\\Web\\Routing\\UriBuilder');
625 // This seems to be very harsh to set this directly to "/typo3 but the viewOnClick also
626 // has /index.php as fixed value here and dealing with the backPath is very error-prone
627 // @todo make sure this would work in local extension installation too
628 $backPath = '/' . TYPO3_mainDir;
629 $redirect = $backPath . 'index.php?redirect_url=';
630 // @todo this should maybe be changed so that the extbase URI Builder can deal with module names directly
631 $originalM = GeneralUtility::_GET('M');
632 GeneralUtility::_GETset('web_WorkspacesWorkspaces', 'M');
633 $viewScript = $backPath . $uriBuilder->uriFor('index', array(), 'Preview', 'workspaces', 'web_workspacesworkspaces') . '&id=';
634 GeneralUtility::_GETset($originalM, 'M');
635 if ($addDomain === TRUE) {
636 return BackendUtility::getViewDomain($uid) . $redirect . urlencode($viewScript) . $uid;
637 } else {
638 return $viewScript;
639 }
640 }
641
642 /**
643 * Find the Live-Uid for a given page,
644 * the results are cached at run-time to avoid too many database-queries
645 *
646 * @throws \InvalidArgumentException
647 * @param integer $uid
648 * @return integer
649 */
650 public function getLivePageUid($uid) {
651 if (!isset($this->pageCache[$uid])) {
652 $pageRecord = BackendUtility::getRecord('pages', $uid);
653 if (is_array($pageRecord)) {
654 $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? $pageRecord['t3ver_oid'] : $uid;
655 } else {
656 throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was:' . $uid, 1290628113);
657 }
658 }
659 return $this->pageCache[$uid];
660 }
661
662 /**
663 * @return \TYPO3\CMS\Extbase\Object\ObjectManager
664 */
665 protected function getObjectManager() {
666 return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
667 }
668
669 }