Cleanup: Added PHPdoc comments and some formatting changes
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Service / Workspaces.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
6 * All rights reserved
7 *
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.
13 *
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.
18 *
19 *
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.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
30 * @package Workspaces
31 * @subpackage Service
32 */
33 class tx_Workspaces_Service_Workspaces {
34 const SELECT_ALL_WORKSPACES = -98;
35 const LIVE_WORKSPACE_ID = 0;
36 const DRAFT_WORKSPACE_ID = -1;
37
38 /**
39 * retrieves the available workspaces from the database and checks whether
40 * they're available to the current BE user
41 *
42 * @return array array of worspaces available to the current user
43 */
44 public function getAvailableWorkspaces() {
45 $availableWorkspaces = array();
46
47 // add default workspaces
48 if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::LIVE_WORKSPACE_ID))) {
49 $availableWorkspaces[self::LIVE_WORKSPACE_ID] = $this->getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
50 }
51 if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::DRAFT_WORKSPACE_ID))) {
52 $availableWorkspaces[self::DRAFT_WORKSPACE_ID] = $this->getWorkspaceTitle(self::DRAFT_WORKSPACE_ID);
53 }
54
55 // add custom workspaces (selecting all, filtering by BE_USER check):
56 $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, title, adminusers, members', 'sys_workspace', 'pid = 0' . t3lib_BEfunc::deleteClause('sys_workspace'), '', 'title');
57 if (count($customWorkspaces)) {
58 foreach ($customWorkspaces as $workspace) {
59 if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
60 $availableWorkspaces[$workspace['uid']] = $workspace['uid'] . ': ' . htmlspecialchars($workspace['title']);
61 }
62 }
63 }
64
65 return $availableWorkspaces;
66 }
67
68
69 /**
70 * Find the title for the requested workspace.
71 *
72 * @param integer $wsId
73 * @return string
74 */
75 public static function getWorkspaceTitle($wsId) {
76 $title = FALSE;
77 switch ($wsId) {
78 case self::LIVE_WORKSPACE_ID:
79 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_onlineWS');
80 break;
81 case self::DRAFT_WORKSPACE_ID:
82 $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_offlineWS');
83 break;
84 default:
85 $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
86 $wsRecord = t3lib_beFunc::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
87 if (is_array($wsRecord)) {
88 $title = $wsRecord[$labelField];
89 }
90 }
91
92 if ($title === FALSE) {
93 throw new InvalidArgumentException('No such workspace defined');
94 }
95
96 return $title;
97 }
98
99
100 /**
101 * Building tcemain CMD-array for swapping all versions in a workspace.
102 *
103 * @param integer Real workspace ID, cannot be ONLINE (zero).
104 * @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.
105 * @param integer $pageId: ...
106 * @return array Command array for tcemain
107 */
108 public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0) {
109
110 $wsid = intval($wsid);
111 $cmd = array();
112
113 if ($wsid >= -1 && $wsid!==0) {
114
115 // Define stage to select:
116 $stage = -99;
117 if ($wsid > 0) {
118 $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $wsid);
119 if ($workspaceRec['publish_access'] & 1) {
120 $stage = Tx_Workspaces_Service_Stages::STAGE_PUBLISH_ID;
121 }
122 }
123
124 // Select all versions to swap:
125 $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ? $pageId : -1));
126
127 // Traverse the selection to build CMD array:
128 foreach ($versions as $table => $records) {
129 foreach ($records as $rec) {
130 // Build the cmd Array:
131 $cmd[$table][$rec['t3ver_oid']]['version'] = array('action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0);
132 }
133 }
134 }
135 return $cmd;
136 }
137
138
139 /**
140 * Building tcemain CMD-array for releasing all versions in a workspace.
141 *
142 * @param integer Real workspace ID, cannot be ONLINE (zero).
143 * @param boolean Run Flush (true) or ClearWSID (false) command
144 * @param integer $pageId: ...
145 * @return array Command array for tcemain
146 */
147 public function getCmdArrayForFlushWS($wsid, $flush = TRUE, $pageId = 0) {
148
149 $wsid = intval($wsid);
150 $cmd = array();
151
152 if ($wsid >= -1 && $wsid!==0) {
153 // Define stage to select:
154 $stage = -99;
155
156 // Select all versions to swap:
157 $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ? $pageId : -1));
158
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['uid']]['version'] = array('action' => ($flush ? 'flush' : 'clearWSID'));
164 }
165 }
166 }
167 return $cmd;
168 }
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 * @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"
182 */
183 public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0) {
184
185 $wsid = intval($wsid);
186 $filter = intval($filter);
187 $output = array();
188
189 // Contains either nothing or a list with live-uids
190 if ($pageId != -1 && $recursionLevel > 0) {
191 $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
192 } else if ($pageId != -1) {
193 $pageList = $pageId;
194 } else {
195 $pageList = '';
196 }
197
198 // Traversing all tables supporting versioning:
199 foreach ($GLOBALS['TCA'] as $table => $cfg) {
200 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
201
202 $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage);
203 if (intval($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) === 2) {
204 $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
205 $recs = array_merge($recs, $moveRecs);
206 }
207 $recs = $this->filterPermittedElements($recs, $table);
208 if (count($recs)) {
209 $output[$table] = $recs;
210 }
211 }
212 }
213 return $output;
214 }
215
216 /**
217 * Find all versionized elements except moved records.
218 *
219 * @param string $table
220 * @param string $pageList
221 * @param integer $filter
222 * @param integer $stage
223 * @return array
224 */
225 protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage) {
226
227 $fields = 'A.uid, A.t3ver_oid,' . ($table==='pages' ? ' A.t3ver_swapmode,' : '') . 'B.pid AS wspid, B.pid AS livepid';
228 $from = $table . ' A,' . $table . ' B';
229
230 // Table A is the offline version and pid=-1 defines offline
231 $where = 'A.pid=-1 AND A.t3ver_state!=4';
232 if ($pageList) {
233 $pidField = ($table==='pages' ? 'uid' : 'pid');
234 $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
235 $where .= ' AND B.' . $pidField . $pidConstraint;
236 }
237
238 /**
239 * For "real" workspace numbers, select by that.
240 * If = -98, select all that are NOT online (zero).
241 * Anything else below -1 will not select on the wsid and therefore select all!
242 */
243 if ($wsid > self::SELECT_ALL_WORKSPACES) {
244 $where .= ' AND A.t3ver_wsid=' . $wsid;
245 } else if ($wsid === self::SELECT_ALL_WORKSPACES) {
246 $where .= ' AND A.t3ver_wsid!=0';
247 }
248
249 /**
250 * lifecycle filter:
251 * 1 = select all drafts (never-published),
252 * 2 = select all published one or more times (archive/multiple)
253 */
254 if ($filter===1 || $filter===2) {
255 $where .= ' AND A.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
256 }
257
258 if ($stage != -99) {
259 $where .= ' AND A.t3ver_stage=' . intval($stage);
260 }
261
262 // Table B (online) must have PID >= 0 to signify being online.
263 $where .= ' AND B.pid>=0';
264 // ... and finally the join between the two tables.
265 $where .= ' AND A.t3ver_oid=B.uid';
266 $where .= t3lib_BEfunc::deleteClause($table, 'A');
267 $where .= t3lib_BEfunc::deleteClause($table, 'B');
268
269 /**
270 * Select all records from this table in the database from the workspace
271 * This joins the online version with the offline version as tables A and B
272 * Order by UID, mostly to have a sorting in the backend overview module which doesn't "jump around" when swapping.
273 */
274 return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'B.uid');
275 }
276
277 /**
278 * Find all moved records at their new position.
279 *
280 * @param string $table
281 * @param string $pageList
282 * @param integer $wsid
283 * @return array
284 */
285 protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage) {
286
287 /**
288 * Aliases:
289 * A - moveTo placeholder
290 * B - online record
291 * C - moveFrom placeholder
292 */
293 $fields = 'A.pid AS wspid, B.uid AS t3ver_oid, C.uid AS uid, B.pid AS livepid';
294 $from = $table . ' A, ' . $table . ' B,' . $table . ' C';
295 $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';
296
297 if ($wsid > self::SELECT_ALL_WORKSPACES) {
298 $where .= ' AND A.t3ver_wsid=' . $wsid . ' AND C.t3ver_wsid=' . $wsid;
299 } else if ($wsid === self::SELECT_ALL_WORKSPACES) {
300 $where .= ' AND A.t3ver_wsid!=0 AND C.t3ver_wsid!=0 ';
301 }
302
303 /**
304 * lifecycle filter:
305 * 1 = select all drafts (never-published),
306 * 2 = select all published one or more times (archive/multiple)
307 */
308 if ($filter===1 || $filter===2) {
309 $where .= ' AND C.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
310 }
311
312 if ($stage != -99) {
313 $where .= ' AND C.t3ver_stage=' . intval($stage);
314 }
315
316 if ($pageList) {
317 $pidField = ($table==='pages' ? 'B.uid' : 'A.pid');
318 $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
319 $where .= ' AND ' . $pidField . $pidConstraint;
320 }
321
322 $where .= ' AND A.t3ver_move_id = B.uid AND B.uid = C.t3ver_oid';
323 $where .= t3lib_BEfunc::deleteClause($table, 'A');
324 $where .= t3lib_BEfunc::deleteClause($table, 'B');
325 $where .= t3lib_BEfunc::deleteClause($table, 'C');
326 $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'A.uid');
327
328 return $res;
329 }
330
331
332 /**
333 * Find all page uids recursive starting from a specific page
334 *
335 * @param integer $pageId
336 * @param integer $wsid
337 * @param integer $recursionLevel
338 * @return string Comma sep. uid list
339 */
340 protected function getTreeUids($pageId, $wsid, $recursionLevel) {
341 /**
342 * Reusing existing functionality with the drawback that
343 * mount points are not covered yet
344 **/
345 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
346 $searchObj = t3lib_div::makeInstance('t3lib_fullsearch');
347 $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
348
349 unset($searchObj);
350
351 if (intval($GLOBALS['TCA']['pages']['ctrl']['versioningWS']) === 2 && $pageList) {
352 if ($pageList) {
353 // Remove the "subbranch" if a page was moved away
354 $movedAwayPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, pid, t3ver_move_id', 'pages', 't3ver_move_id IN (' . $pageList . ') AND t3ver_wsid=' . $wsid . t3lib_BEfunc::deleteClause($table), '', 'uid', '', 't3ver_move_id');
355 $newList = array();
356 $pageIds = t3lib_div::intExplode(',', $pageList, TRUE);
357
358 foreach ($pageIds as $tmpId) {
359 if (isset($movedAwayPages[$tmpId]) && !empty($newList) && !in_array($movedAwayPages[$tmpId]['pid'], intval($newList))) {
360 break;
361 }
362 $newList[] = $tmpId;
363 }
364 $pageList = implode(',', $newList);
365 }
366 // In case moving pages is enabled we need to replace all move-to pointer with their origin
367 $pages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, t3ver_move_id', 'pages', 'uid IN (' . $pageList . ')' . t3lib_BEfunc::deleteClause($table), '', 'uid', '', 'uid');
368
369 $newList = array();
370 $pageIds = t3lib_div::intExplode(',', $pageList, TRUE);
371 foreach ($pageIds as $pageId) {
372 if (intval($pages[$pageId]['t3ver_move_id']) > 0) {
373 $newList[] = intval($pages[$pageId]['t3ver_move_id']);
374 } else {
375 $newList[] = $pageId;
376 }
377 }
378 $pageList = implode(',', $newList);
379 }
380 return $pageList;
381 }
382
383 /**
384 * Remove all records which are not permitted for the user
385 *
386 * @param array $recs
387 * @param string $table
388 * @return array
389 */
390 protected function filterPermittedElements($recs, $table) {
391 $checkField = ($table == 'pages') ? 'uid' : 'pid';
392 $permittedElements = array();
393 if (is_array($recs)) {
394 foreach ($recs as $rec) {
395 $page = t3lib_beFunc::getRecord('pages', $rec[$checkField], 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
396 if ($GLOBALS['BE_USER']->doesUserHaveAccess($page, 1)) {
397 $permittedElements[] = $rec;
398 }
399 }
400 }
401 return $permittedElements;
402 }
403 }
404
405
406 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php']) {
407 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php']);
408 }
409 ?>