2 /***************************************************************
5 * (c) 2010-2011 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
29 * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
33 class tx_Workspaces_Service_Workspaces
{
34 const TABLE_WORKSPACE
= 'sys_workspace';
35 const SELECT_ALL_WORKSPACES
= -98;
36 const LIVE_WORKSPACE_ID
= 0;
39 * retrieves the available workspaces from the database and checks whether
40 * they're available to the current BE user
42 * @return array array of worspaces available to the current user
44 public function getAvailableWorkspaces() {
45 $availableWorkspaces = array();
47 // add default workspaces
48 if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self
::LIVE_WORKSPACE_ID
))) {
49 $availableWorkspaces[self
::LIVE_WORKSPACE_ID
] = self
::getWorkspaceTitle(self
::LIVE_WORKSPACE_ID
);
52 // add custom workspaces (selecting all, filtering by BE_USER check):
53 $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, title, adminusers, members', 'sys_workspace', 'pid = 0' . t3lib_BEfunc
::deleteClause('sys_workspace'), '', 'title');
54 if (count($customWorkspaces)) {
55 foreach ($customWorkspaces as $workspace) {
56 if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
57 $availableWorkspaces[$workspace['uid']] = htmlspecialchars($workspace['title']);
62 return $availableWorkspaces;
67 * Find the title for the requested workspace.
69 * @param integer $wsId
72 public static function getWorkspaceTitle($wsId) {
75 case self
::LIVE_WORKSPACE_ID
:
76 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_onlineWS');
79 $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
80 $wsRecord = t3lib_beFunc
::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
81 if (is_array($wsRecord)) {
82 $title = $wsRecord[$labelField];
86 if ($title === FALSE) {
87 throw new InvalidArgumentException('No such workspace defined');
95 * Building tcemain CMD-array for swapping all versions in a workspace.
97 * @param integer Real workspace ID, cannot be ONLINE (zero).
98 * @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.
99 * @param integer $pageId: ...
100 * @return array Command array for tcemain
102 public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0) {
104 $wsid = intval($wsid);
107 if ($wsid >= -1 && $wsid!==0) {
109 // Define stage to select:
112 $workspaceRec = t3lib_BEfunc
::getRecord('sys_workspace', $wsid);
113 if ($workspaceRec['publish_access'] & 1) {
114 $stage = Tx_Workspaces_Service_Stages
::STAGE_PUBLISH_ID
;
118 // Select all versions to swap:
119 $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ?
$pageId : -1), 0, 'tables_modify');
121 // Traverse the selection to build CMD array:
122 foreach ($versions as $table => $records) {
123 foreach ($records as $rec) {
124 // Build the cmd Array:
125 $cmd[$table][$rec['t3ver_oid']]['version'] = array('action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ?
1 : 0);
134 * Building tcemain CMD-array for releasing all versions in a workspace.
136 * @param integer Real workspace ID, cannot be ONLINE (zero).
137 * @param boolean Run Flush (TRUE) or ClearWSID (FALSE) command
138 * @param integer $pageId: ...
139 * @return array Command array for tcemain
141 public function getCmdArrayForFlushWS($wsid, $flush = TRUE, $pageId = 0) {
143 $wsid = intval($wsid);
146 if ($wsid >= -1 && $wsid!==0) {
147 // Define stage to select:
150 // Select all versions to swap:
151 $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ?
$pageId : -1), 0, 'tables_modify');
153 // Traverse the selection to build CMD array:
154 foreach ($versions as $table => $records) {
155 foreach ($records as $rec) {
156 // Build the cmd Array:
157 $cmd[$table][$rec['uid']]['version'] = array('action' => ($flush ?
'flush' : 'clearWSID'));
166 * Select all records from workspace pending for publishing
167 * Used from backend to display workspace overview
168 * User for auto-publishing for selecting versions for publication
170 * @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
171 * @param integer Lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple), anything else selects all.
172 * @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"
173 * @param integer Page id: Live page for which to find versions in workspace!
174 * @param integer Recursion Level - select versions recursive - parameter is only relevant if $pageId != -1
175 * @param string How to collect records for "listing" or "modify" these tables. Support the permissions of each type of record (@see t3lib_userAuthGroup::check).
176 * @return array Array of all records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid, t3ver_oid and t3ver_swapmode fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid"
178 public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0, $selectionType = 'tables_select') {
180 $wsid = intval($wsid);
181 $filter = intval($filter);
184 // Contains either nothing or a list with live-uids
185 if ($pageId != -1 && $recursionLevel > 0) {
186 $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
187 } elseif ($pageId != -1) {
193 // Traversing all tables supporting versioning:
194 foreach ($GLOBALS['TCA'] as $table => $cfg) {
196 // we do not collect records from tables without permissions on them.
197 if (! $GLOBALS['BE_USER']->check($selectionType, $table)) {
201 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
203 $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage);
204 if (intval($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) === 2) {
205 $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
206 $recs = array_merge($recs, $moveRecs);
208 $recs = $this->filterPermittedElements($recs, $table);
210 $output[$table] = $recs;
218 * Find all versionized elements except moved records.
220 * @param string $table
221 * @param string $pageList
222 * @param integer $filter
223 * @param integer $stage
226 protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage) {
228 $fields = 'A.uid, A.t3ver_oid,' . ($table==='pages' ?
' A.t3ver_swapmode,' : '') . 'B.pid AS wspid, B.pid AS livepid';
229 $from = $table . ' A,' . $table . ' B';
231 // Table A is the offline version and pid=-1 defines offline
232 $where = 'A.pid=-1 AND A.t3ver_state!=4';
234 $pidField = ($table==='pages' ?
'uid' : 'pid');
235 $pidConstraint = strstr($pageList, ',') ?
' IN (' . $pageList . ')' : '=' . $pageList;
236 $where .= ' AND B.' . $pidField . $pidConstraint;
240 * For "real" workspace numbers, select by that.
241 * If = -98, select all that are NOT online (zero).
242 * Anything else below -1 will not select on the wsid and therefore select all!
244 if ($wsid > self
::SELECT_ALL_WORKSPACES
) {
245 $where .= ' AND A.t3ver_wsid=' . $wsid;
246 } elseif ($wsid === self
::SELECT_ALL_WORKSPACES
) {
247 $where .= ' AND A.t3ver_wsid!=0';
252 * 1 = select all drafts (never-published),
253 * 2 = select all published one or more times (archive/multiple)
255 if ($filter===1 ||
$filter===2) {
256 $where .= ' AND A.t3ver_count ' . ($filter === 1 ?
'= 0' : '> 0');
260 $where .= ' AND A.t3ver_stage=' . intval($stage);
263 // Table B (online) must have PID >= 0 to signify being online.
264 $where .= ' AND B.pid>=0';
265 // ... and finally the join between the two tables.
266 $where .= ' AND A.t3ver_oid=B.uid';
267 $where .= t3lib_BEfunc
::deleteClause($table, 'A');
268 $where .= t3lib_BEfunc
::deleteClause($table, 'B');
271 * Select all records from this table in the database from the workspace
272 * This joins the online version with the offline version as tables A and B
273 * Order by UID, mostly to have a sorting in the backend overview module which doesn't "jump around" when swapping.
275 $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'B.uid');
276 return is_array($res) ?
$res : array();
280 * Find all moved records at their new position.
282 * @param string $table
283 * @param string $pageList
284 * @param integer $wsid
287 protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage) {
291 * A - moveTo placeholder
293 * C - moveFrom placeholder
295 $fields = 'A.pid AS wspid, B.uid AS t3ver_oid, C.uid AS uid, B.pid AS livepid';
296 $from = $table . ' A, ' . $table . ' B,' . $table . ' C';
297 $where = 'A.t3ver_state=3 AND B.pid>0 AND B.t3ver_state=0 AND B.t3ver_wsid=0 AND C.pid=-1 AND C.t3ver_state=4';
299 if ($wsid > self
::SELECT_ALL_WORKSPACES
) {
300 $where .= ' AND A.t3ver_wsid=' . $wsid . ' AND C.t3ver_wsid=' . $wsid;
301 } elseif ($wsid === self
::SELECT_ALL_WORKSPACES
) {
302 $where .= ' AND A.t3ver_wsid!=0 AND C.t3ver_wsid!=0 ';
307 * 1 = select all drafts (never-published),
308 * 2 = select all published one or more times (archive/multiple)
310 if ($filter===1 ||
$filter===2) {
311 $where .= ' AND C.t3ver_count ' . ($filter === 1 ?
'= 0' : '> 0');
315 $where .= ' AND C.t3ver_stage=' . intval($stage);
319 $pidField = ($table==='pages' ?
'B.uid' : 'A.pid');
320 $pidConstraint = strstr($pageList, ',') ?
' IN (' . $pageList . ')' : '=' . $pageList;
321 $where .= ' AND ' . $pidField . $pidConstraint;
324 $where .= ' AND A.t3ver_move_id = B.uid AND B.uid = C.t3ver_oid';
325 $where .= t3lib_BEfunc
::deleteClause($table, 'A');
326 $where .= t3lib_BEfunc
::deleteClause($table, 'B');
327 $where .= t3lib_BEfunc
::deleteClause($table, 'C');
328 $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'A.uid');
330 return is_array($res) ?
$res : array();
335 * Find all page uids recursive starting from a specific page
337 * @param integer $pageId
338 * @param integer $wsid
339 * @param integer $recursionLevel
340 * @return string Comma sep. uid list
342 protected function getTreeUids($pageId, $wsid, $recursionLevel) {
344 * Reusing existing functionality with the drawback that
345 * mount points are not covered yet
347 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
348 $searchObj = t3lib_div
::makeInstance('t3lib_fullsearch');
351 $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
353 $mountPoints = $GLOBALS['BE_USER']->uc
['pageTree_temporaryMountPoint'];
354 if (!is_array($mountPoints) ||
empty($mountPoints)) {
355 $mountPoints = array_map('intval', $GLOBALS['BE_USER']->returnWebmounts());
356 $mountPoints = array_unique($mountPoints);
359 foreach($mountPoints as $mountPoint) {
360 $newList[] = $searchObj->getTreeList($mountPoint, $recursionLevel, 0, $perms_clause);
362 $pageList = implode(',', $newList);
366 if (intval($GLOBALS['TCA']['pages']['ctrl']['versioningWS']) === 2 && $pageList) {
367 // Remove the "subbranch" if a page was moved away
368 $movedAwayPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
369 'uid, pid, t3ver_move_id',
371 't3ver_move_id IN (' . $pageList . ') AND t3ver_wsid=' . intval($wsid) . t3lib_BEfunc
::deleteClause('pages'),
377 $pageIds = t3lib_div
::intExplode(',', $pageList, TRUE);
379 // move all pages away
380 $newList = array_diff($pageIds, array_keys($movedAwayPages));
382 // keep current page in the list
383 $newList[] = $pageId;
384 // move back in if still connected to the "remaining" pages
387 foreach ($movedAwayPages as $uid => $rec) {
388 if (in_array($rec['pid'], $newList) && !in_array($uid, $newList)) {
394 $pageList = implode(',', $newList);
396 // In case moving pages is enabled we need to replace all move-to pointer with their origin
397 $pages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
398 'uid, t3ver_move_id',
400 'uid IN (' . $pageList . ')' . t3lib_BEfunc
::deleteClause('pages'),
408 $pageIds = t3lib_div
::intExplode(',', $pageList, TRUE);
409 if (!in_array($pageId, $pageIds)) {
410 $pageIds[] = $pageId;
412 foreach ($pageIds as $pageId) {
413 if (intval($pages[$pageId]['t3ver_move_id']) > 0) {
414 $newList[] = intval($pages[$pageId]['t3ver_move_id']);
416 $newList[] = $pageId;
419 $pageList = implode(',', $newList);
425 * Remove all records which are not permitted for the user
428 * @param string $table
431 protected function filterPermittedElements($recs, $table) {
432 $checkField = ($table == 'pages') ?
'uid' : 'wspid';
433 $permittedElements = array();
434 if (is_array($recs)) {
435 foreach ($recs as $rec) {
436 $page = t3lib_beFunc
::getRecord('pages', $rec[$checkField], 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
437 if ($GLOBALS['BE_USER']->doesUserHaveAccess($page, 1)) {
438 $permittedElements[] = $rec;
442 return $permittedElements;
447 * Trivial check to see if the user already migrated his workspaces
448 * to the new style (either manually or with the migrator scripts)
452 public static function isOldStyleWorkspaceUsed() {
453 $oldStyleWorkspaceIsUsed = FALSE;
454 $cacheKey = 'workspace-oldstyleworkspace-notused';
455 $cacheResult = $GLOBALS['BE_USER']->getSessionData($cacheKey);
457 $where = 'adminusers != "" AND adminusers NOT LIKE "%be_users%" AND adminusers NOT LIKE "%be_groups%" AND deleted=0';
458 $count = $GLOBALS['TYPO3_DB']->exec_SELECTcountRows('uid', 'sys_workspace', $where);
459 $oldStyleWorkspaceIsUsed = $count > 0;
460 $GLOBALS['BE_USER']->setAndSaveSessionData($cacheKey, !$oldStyleWorkspaceIsUsed);
462 $oldStyleWorkspaceIsUsed = !$cacheResult;
464 return $oldStyleWorkspaceIsUsed;
468 * Determine whether a specific page is new and not yet available in the LIVE workspace
471 * @param $id Primary key of the page to check
472 * @param $language Language for which to check the page
475 public static function isNewPage($id, $language = 0) {
477 // If the language is not default, check state of overlay
479 $whereClause = 'pid = ' . $id;
480 $whereClause .= ' AND ' .$GLOBALS['TCA']['pages_language_overlay']['ctrl']['languageField'] . ' = ' . $language;
481 $whereClause .= ' AND t3ver_wsid = ' . $GLOBALS['BE_USER']->workspace
;
482 $whereClause .= t3lib_BEfunc
::deleteClause('pages_language_overlay');
483 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('t3ver_state', 'pages_language_overlay', $whereClause);
484 if (($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res))) {
485 $isNewPage = (int) $row['t3ver_state'] === 1;
488 // Otherwise check state of page itself
490 $rec = t3lib_BEfunc
::getRecord('pages', $id, 't3ver_state');
491 if (is_array($rec)) {
492 $isNewPage = (int) $rec['t3ver_state'] === 1;
499 * Generates a view link for a page.
507 public static function viewSingleRecord($table, $uid, $record=NULL) {
509 if ($table == 'pages') {
510 $viewUrl = t3lib_BEfunc
::viewOnClick(t3lib_BEfunc
::getLiveVersionIdOfRecord('pages', $uid));
511 } elseif ($table == 'pages_language_overlay' ||
$table == 'tt_content') {
512 $elementRecord = is_array($record) ?
$record : t3lib_BEfunc
::getLiveVersionOfRecord($table, $uid);
513 $viewUrl = t3lib_BEfunc
::viewOnClick($elementRecord['pid']);
515 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
516 $_params = array('table' => $table, 'uid' => $uid, 'record' => $record);
517 $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
518 $viewUrl = t3lib_div
::callUserFunction($_funcRef, $_params, NULL);
525 * Determine whether this page for the current
528 * @param $workspaceUid
531 public function canCreatePreviewLink($pageUid, $workspaceUid) {
533 if ($pageUid > 0 && $workspaceUid > 0) {
534 $pageRecord = t3lib_BEfunc
::getRecord('pages', $pageUid);
535 t3lib_BEfunc
::workspaceOL('pages', $pageRecord, $workspaceUid);
536 if (!t3lib_div
::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'], $pageRecord['doktype'])) {
547 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php'])) {
548 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE
]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php']);