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