70d6539743919f48cc25780cbdefe0ba31b677b3
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Service / Stages.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010-2011 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
34 class Tx_Workspaces_Service_Stages {
35 const TABLE_STAGE = 'sys_workspace_stage';
36 // if a record is in the "ready to publish" stage STAGE_PUBLISH_ID the nextStage is STAGE_PUBLISH_EXECUTE_ID, this id wont be saved at any time in db
37 const STAGE_PUBLISH_EXECUTE_ID = -20;
38 // ready to publish stage
39 const STAGE_PUBLISH_ID = -10;
40 const STAGE_EDIT_ID = 0;
41
42 /**
43 * Current workspace ID
44 * @var integer
45 */
46 private $workspaceId = NULL;
47
48 /**
49 * Path to the locallang file
50 * @var string
51 */
52 private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xml';
53
54 /**
55 * Local cache to reduce number of database queries for stages, groups, etc.
56 * @var array
57 */
58 protected $workspaceStageCache = array();
59
60 /**
61 * @var array
62 */
63 protected $workspaceStageAllowedCache = array();
64
65 /**
66 * @var array
67 */
68 protected $fetchGroupsCache = array();
69
70 /**
71 * @var array
72 */
73 protected $userGroups = array();
74
75 /**
76 * Getter for current workspace id
77 *
78 * @return int current workspace id
79 */
80 public function getWorkspaceId() {
81 if ($this->workspaceId == NULL) {
82 $this->setWorkspaceId($GLOBALS['BE_USER']->workspace);
83 }
84 return $this->workspaceId;
85 }
86
87 /**
88 * Setter for current workspace id
89 *
90 * @param int current workspace id
91 */
92 private function setWorkspaceId($wsid) {
93 $this->workspaceId = $wsid;
94 }
95
96 /**
97 * constructor for workspace library
98 *
99 * @param int current workspace id
100 */
101 public function __construct() {
102 $this->setWorkspaceId($GLOBALS['BE_USER']->workspace);
103 }
104
105 /**
106 * Building an array with all stage ids and titles related to the given workspace
107 *
108 * @return array id and title of the stages
109 */
110 public function getStagesForWS() {
111 $stages = array();
112
113 if (isset($this->workspaceStageCache[$this->getWorkspaceId()])) {
114 $stages = $this->workspaceStageCache[$this->getWorkspaceId()];
115 } else {
116 $stages[] = array(
117 'uid' => self::STAGE_EDIT_ID,
118 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing') . '"'
119 );
120
121 $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $this->getWorkspaceId());
122 if ($workspaceRec['custom_stages'] > 0) {
123 // Get all stage records for this workspace
124 $workspaceStageRecs = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
125 '*',
126 self::TABLE_STAGE,
127 'parentid=' . $this->getWorkspaceId() . ' AND parenttable=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('sys_workspace', self::TABLE_STAGE) . ' AND deleted=0',
128 '',
129 'sorting',
130 '',
131 'uid'
132 );
133 foreach($workspaceStageRecs as $stage) {
134 $stage['title'] = $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $stage['title'] . '"';
135 $stages[] = $stage;
136 }
137 }
138
139 $stages[] = array(
140 'uid' => self::STAGE_PUBLISH_ID,
141 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xml:stage_ready_to_publish') . '"'
142 );
143
144 $stages[] = array(
145 'uid' => self::STAGE_PUBLISH_EXECUTE_ID,
146 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':publish_execute_action_option')
147 );
148 $this->workspaceStageCache[$this->getWorkspaceId()] = $stages;
149 }
150
151 return $stages;
152 }
153
154 /**
155 * Returns an array of stages, the user is allowed to send to
156 * @return array id and title of stages
157 */
158 public function getStagesForWSUser() {
159
160 $stagesForWSUserData = array();
161 $allowedStages = array();
162 $orderedAllowedStages = array();
163
164 $workspaceStageRecs = $this->getStagesForWS();
165 if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
166 if ($GLOBALS['BE_USER']->isAdmin()) {
167 $orderedAllowedStages = $workspaceStageRecs;
168 } else {
169 foreach ($workspaceStageRecs as $workspaceStageRec) {
170 if ($this->isStageAllowedForUser($workspaceStageRec['uid'])) {
171 $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
172 } else if ($workspaceStageRec['uid'] == self::STAGE_PUBLISH_EXECUTE_ID && $GLOBALS['BE_USER']->workspacePublishAccess($this->getWorkspaceId())) {
173 $allowedStages[] = $workspaceStageRec;
174 $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
175 }
176 }
177
178 foreach ($stagesForWSUserData as $allowedStage) {
179 $nextStage = $this->getNextStage($allowedStage['uid']);
180 $prevStage = $this->getPrevStage($allowedStage['uid']);
181 if (isset($nextStage['uid'])) {
182 $allowedStages[$nextStage['uid']] = $nextStage;
183 }
184 if (isset($prevStage['uid'])) {
185 $allowedStages[$prevStage['uid']] = $prevStage;
186 }
187 }
188
189 $orderedAllowedStages = array_values($allowedStages);
190 }
191 }
192 return $orderedAllowedStages;
193 }
194
195 /**
196 * Check if given workspace has custom staging activated
197 *
198 * @return bool true or false
199 */
200 public function checkCustomStagingForWS() {
201 $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $this->getWorkspaceId());
202 return $workspaceRec['custom_stages'] > 0;
203 }
204
205 /**
206 * Gets the title of a stage.
207 *
208 * @param integer $ver_stage
209 * @return string
210 */
211 public function getStageTitle($ver_stage) {
212 global $LANG;
213 $stageTitle = '';
214
215 switch ($ver_stage) {
216 case self::STAGE_PUBLISH_EXECUTE_ID:
217 $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_publish');
218 break;
219 case self::STAGE_PUBLISH_ID:
220 $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xml:stage_ready_to_publish');
221 break;
222 case self::STAGE_EDIT_ID:
223 $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing');
224 break;
225 default:
226 $stageTitle = $this->getPropertyOfCurrentWorkspaceStage($ver_stage, 'title');
227
228 if ($stageTitle == null) {
229 $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.getStageTitle.stageNotFound');
230 }
231 break;
232 }
233
234 return $stageTitle;
235 }
236
237 /**
238 * Gets a particular stage record.
239 *
240 * @param integer $stageid
241 * @return array
242 */
243 public function getStageRecord($stageid) {
244 return t3lib_BEfunc::getRecord('sys_workspace_stage', $stageid);
245 }
246
247 /**
248 * Gets next stage in process for given stage id
249 *
250 * @param integer $stageid Id of the stage to fetch the next one for
251 * @return integer The next stage Id
252 */
253 public function getNextStage($stageId) {
254 if (!t3lib_div::testInt($stageId)) {
255 throw new InvalidArgumentException(
256 $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'),
257 1291109987
258 );
259 }
260
261 $nextStage = FALSE;
262 $workspaceStageRecs = $this->getStagesForWS();
263
264 if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
265 reset($workspaceStageRecs);
266 while (!is_null($workspaceStageRecKey = key($workspaceStageRecs))) {
267 $workspaceStageRec = current($workspaceStageRecs);
268 if ($workspaceStageRec['uid'] == $stageId) {
269 $nextStage = next($workspaceStageRecs);
270 break;
271 }
272 next($workspaceStageRecs);
273 }
274 } else {
275 // @todo consider to throw an exception here
276 }
277
278 if ($nextStage === FALSE) {
279 $nextStage[] = array(
280 'uid' => self::STAGE_EDIT_ID,
281 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing') . '"'
282 );
283 }
284
285 return $nextStage;
286 }
287
288 /**
289 * Recursive function to get all next stages for a record depending on user permissions
290 *
291 * @param array next stages
292 * @param int stage id
293 * @param int current stage id of the record
294 * @return array next stages
295 */
296 public function getNextStages(array &$nextStageArray, $stageId) {
297 // Current stage is "Ready to publish" - there is no next stage
298 if ($stageId == self::STAGE_PUBLISH_ID) {
299 return $nextStageArray;
300 } else {
301 $nextStageRecord = $this->getNextStage($stageId);
302 if (empty($nextStageRecord) || !is_array($nextStageRecord)) {
303 // There is no next stage
304 return $nextStageArray;
305 } else {
306 // Check if the user has the permission to for the current stage
307 // If this next stage record is the first next stage after the current the user
308 // has always the needed permission
309 if ($this->isStageAllowedForUser($stageId)) {
310 $nextStageArray[] = $nextStageRecord;
311 return $this->getNextStages($nextStageArray, $nextStageRecord['uid']);
312 } else {
313 // He hasn't - return given next stage array
314 return $nextStageArray;
315 }
316 }
317 }
318 }
319
320 /**
321 * Get next stage in process for given stage id
322 *
323 * @param int workspace id
324 * @param int stageid
325 * @return int id
326 */
327 public function getPrevStage($stageid) {
328
329 if (!t3lib_div::testInt($stageid)) {
330 throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
331 }
332
333 $prevStage = FALSE;
334 $workspaceStageRecs = $this->getStagesForWS();
335 if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
336 end($workspaceStageRecs);
337 while (!is_null($workspaceStageRecKey = key($workspaceStageRecs))) {
338 $workspaceStageRec = current($workspaceStageRecs);
339 if ($workspaceStageRec['uid'] == $stageid) {
340 $prevStage = prev($workspaceStageRecs);
341 break;
342 }
343 prev($workspaceStageRecs);
344 }
345 } else {
346 // @todo consider to throw an exception here
347 }
348 return $prevStage;
349 }
350
351 /**
352 * Recursive function to get all prev stages for a record depending on user permissions
353 *
354 * @param array prev stages
355 * @param int workspace id
356 * @param int stage id
357 * @param int current stage id of the record
358 * @return array prev stages
359 */
360 public function getPrevStages(array &$prevStageArray, $stageId) {
361 // Current stage is "Editing" - there is no prev stage
362 if ($stageId != self::STAGE_EDIT_ID) {
363 $prevStageRecord = $this->getPrevStage($stageId);
364
365 if (!empty($prevStageRecord) && is_array($prevStageRecord)) {
366 // Check if the user has the permission to switch to that stage
367 // If this prev stage record is the first previous stage before the current
368 // the user has always the needed permission
369 if ($this->isStageAllowedForUser($stageId)) {
370 $prevStageArray[] = $prevStageRecord;
371 $prevStageArray = $this->getPrevStages($prevStageArray, $prevStageRecord['uid']);
372
373 }
374 }
375 }
376 return $prevStageArray;
377 }
378
379 /**
380 * Get array of all responsilbe be_users for a stage
381 *
382 * @param int stage id
383 * @return array be_users with e-mail and name
384 */
385 public function getResponsibleBeUser($stageId) {
386 $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $this->getWorkspaceId());
387 $recipientArray = array();
388
389 switch ($stageId) {
390 case self::STAGE_PUBLISH_EXECUTE_ID:
391 case self::STAGE_PUBLISH_ID:
392 $userList = $this->getResponsibleUser($workspaceRec['adminusers']);
393 break;
394 case self::STAGE_EDIT_ID:
395 $userList = $this->getResponsibleUser($workspaceRec['members']);
396 break;
397 default:
398 $responsible_persons = $this->getPropertyOfCurrentWorkspaceStage($stageId, 'responsible_persons');
399 $userList = $this->getResponsibleUser($responsible_persons);
400 break;
401 }
402
403 if (!empty($userList)) {
404 $userRecords = t3lib_BEfunc::getUserNames('username, uid, email, realName',
405 'AND uid IN (' . $userList . ')');
406 }
407
408 if (!empty($userRecords) && is_array($userRecords)) {
409 foreach ($userRecords as $userUid => $userRecord) {
410 $recipientArray[$userUid] = $userRecord;
411 }
412 }
413 return $recipientArray;
414 }
415
416 /**
417 * Get uids of all responsilbe persons for a stage
418 *
419 * @param string responsible_persion value from stage record
420 * @return string uid list of responsible be_users
421 */
422 public function getResponsibleUser($stageRespValue) {
423 $stageValuesArray = array();
424 $stageValuesArray = t3lib_div::trimExplode(',', $stageRespValue);
425
426 $beuserUidArray = array();
427 $begroupUidArray = array();
428 $allBeUserArray = array();
429 $begroupUidList = array();
430
431 foreach ($stageValuesArray as $key => $uidvalue) {
432 if (strstr($uidvalue, 'be_users') !== FALSE) { // Current value is a uid of a be_user record
433 $beuserUidArray[] = str_replace('be_users_', '', $uidvalue);
434 } elseif (strstr($uidvalue, 'be_groups') !== FALSE) {
435 $begroupUidArray[] = str_replace('be_groups_', '', $uidvalue);
436 } else {
437 $beuserUidArray[] = $uidvalue;
438 }
439 }
440 if (!empty($begroupUidArray)) {
441 $allBeUserArray = t3lib_befunc::getUserNames();
442
443 $begroupUidList = implode(',', $begroupUidArray);
444
445 $this->userGroups = array();
446 $begroupUidArray = $this->fetchGroups($begroupUidList);
447
448 foreach ($begroupUidArray as $groupkey => $groupData) {
449 foreach ($allBeUserArray as $useruid => $userdata) {
450 if (t3lib_div::inList($userdata['usergroup'], $groupData['uid'])) {
451 $beuserUidArray[] = $useruid;
452 }
453 }
454 }
455 }
456
457 array_unique($beuserUidArray);
458 return implode(',', $beuserUidArray);
459 }
460
461
462 /**
463 * @param $grList
464 * @param string $idList
465 * @return array
466 */
467 private function fetchGroups($grList, $idList = '') {
468
469 $cacheKey = md5($grList . $idList);
470 $groupList = array();
471 if (isset($this->fetchGroupsCache[$cacheKey])) {
472 $groupList = $this->fetchGroupsCache[$cacheKey];
473 } else {
474 if ($idList === '') {
475 // we're at the beginning of the recursion and therefore we need to reset the userGroups member
476 $this->userGroups = array();
477 }
478 $groupList = $this->fetchGroupsRecursive($grList);
479 $this->fetchGroupsCache[$cacheKey] = $groupList;
480 }
481 return $groupList;
482 }
483
484 /**
485 * @param array $groups
486 * @return void
487 */
488 private function fetchGroupsFromDB(array $groups) {
489 $whereSQL = 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $groups) . ') ';
490 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'be_groups', $whereSQL);
491
492 // The userGroups array is filled
493 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
494 $this->userGroups[$row['uid']] = $row;
495 }
496 }
497
498 /**
499 * Fetches particular groups recursively.
500 *
501 * @param $grList
502 * @param string $idList
503 * @return array
504 */
505 private function fetchGroupsRecursive($grList, $idList = '') {
506 $requiredGroups = t3lib_div::intExplode(',', $grList, TRUE);
507 $existingGroups = array_keys($this->userGroups);
508 $missingGroups = array_diff($requiredGroups, $existingGroups);
509
510 if (count($missingGroups) > 0) {
511 $this->fetchGroupsFromDB($missingGroups);
512 }
513
514 // Traversing records in the correct order
515 foreach ($requiredGroups as $uid) { // traversing list
516 // Get row:
517 $row = $this->userGroups[$uid];
518 if (is_array($row) && !t3lib_div::inList($idList, $uid)) { // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
519 // If the localconf.php option isset the user of the sub- sub- groups will also be used
520 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['customStageShowRecipientRecursive'] == 1) {
521 // Include sub groups
522 if (trim($row['subgroup'])) {
523 // Make integer list
524 $theList = implode(',', t3lib_div::intExplode(',', $row['subgroup']));
525 // Get the subarray
526 $subbarray = $this->fetchGroups($theList, $idList . ',' . $uid);
527 list($subUid, $subArray) = each($subbarray);
528 // Merge the subarray to the already existing userGroups array
529 $this->userGroups[$subUid] = $subArray;
530 }
531 }
532 }
533 }
534
535 return $this->userGroups;
536 }
537
538 /**
539 * Gets a property of a workspaces stage.
540 *
541 * @param integer $stageId
542 * @param string $property
543 * @return string
544 */
545 public function getPropertyOfCurrentWorkspaceStage($stageId, $property) {
546 $result = NULL;
547
548 if (!t3lib_div::testInt($stageId)) {
549 throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
550 }
551
552 $workspaceStage = t3lib_BEfunc::getRecord(self::TABLE_STAGE, $stageId);
553
554 if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
555 $result = $workspaceStage[$property];
556 }
557
558 return $result;
559 }
560
561 /**
562 * Gets the position of the given workspace in the hole process f.e. 3 means step 3 of 20, by which 1 is edit and 20 is ready to publish
563 *
564 * @param integer $stageId
565 * @return array position => 3, count => 20
566 */
567 public function getPositionOfCurrentStage($stageId) {
568 $stagesOfWS = $this->getStagesForWS();
569 $countOfStages = count($stagesOfWS);
570
571 switch ($stageId) {
572 case self::STAGE_PUBLISH_ID:
573 $position = $countOfStages;
574 break;
575 case self::STAGE_EDIT_ID:
576 $position = 1;
577 break;
578 default:
579 $position = 1;
580 foreach ($stagesOfWS as $key => $stageInfoArray) {
581 $position++;
582 if ($stageId == $stageInfoArray['uid']) {
583 break;
584 }
585 }
586 break;
587 }
588 return array('position' => $position, 'count' => $countOfStages);
589 }
590
591 /**
592 * Check if the user has access to the previous stage, relative to the given stage
593 *
594 * @param integer $stageId
595 * @return bool
596 */
597 public function isPrevStageAllowedForUser($stageId) {
598 $isAllowed = FALSE;
599 try {
600 $prevStage = $this->getPrevStage($stageId);
601 // if there's no prev-stage the stageIds match,
602 // otherwise we've to check if the user is permitted to use the stage
603 if (!empty($prevStage) && $prevStage['uid'] != $stageId) {
604 // if the current stage is allowed for the user, the user is also allowed to send to prev
605 $isAllowed = $this->isStageAllowedForUser($stageId);
606 }
607 } catch (Exception $e) {
608 // Exception raised - we're not allowed to go this way
609 }
610
611 return $isAllowed;
612 }
613
614 /**
615 * Check if the user has access to the next stage, relative to the given stage
616 *
617 * @param integer $stageId
618 * @return bool
619 */
620 public function isNextStageAllowedForUser($stageId) {
621 $isAllowed = FALSE;
622 try {
623 $nextStage = $this->getNextStage($stageId);
624 // if there's no next-stage the stageIds match,
625 // otherwise we've to check if the user is permitted to use the stage
626 if (!empty($nextStage) && $nextStage['uid'] != $stageId) {
627 // if the current stage is allowed for the user, the user is also allowed to send to next
628 $isAllowed = $this->isStageAllowedForUser($stageId);
629 }
630 } catch (Exception $e) {
631 // Exception raised - we're not allowed to go this way
632 }
633
634 return $isAllowed;
635 }
636
637 /**
638 * @param $stageId
639 * @return bool
640 */
641 protected function isStageAllowedForUser($stageId) {
642 $cacheKey = $this->getWorkspaceId() . '_' . $stageId;
643 $isAllowed = FALSE;
644 if (isset($this->workspaceStageAllowedCache[$cacheKey])) {
645 $isAllowed = $this->workspaceStageAllowedCache[$cacheKey];
646 } else {
647 $isAllowed = $GLOBALS['BE_USER']->workspaceCheckStageForCurrent($stageId);
648 $this->workspaceStageAllowedCache[$cacheKey] = $isAllowed;
649 }
650 return $isAllowed;
651 }
652
653 /**
654 * Determines whether a stage Id is valid.
655 *
656 * @param integer $stageId The stage Id to be checked
657 * @return boolean
658 */
659 public function isValid($stageId) {
660 $isValid = FALSE;
661 $stages = $this->getStagesForWS();
662
663 foreach ($stages as $stage) {
664 if ($stage['uid'] == $stageId) {
665 $isValid = TRUE;
666 break;
667 }
668 }
669
670 return $isValid;
671 }
672 }
673
674
675 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Stages.php'])) {
676 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Stages.php']);
677 }
678 ?>