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)
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;
43 * Current workspace ID
46 private $workspaceId = NULL;
49 * Path to the locallang file
52 private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xml';
55 * Local cache to reduce number of database queries for stages, groups, etc.
58 protected $workspaceStageCache = array();
63 protected $workspaceStageAllowedCache = array();
68 protected $fetchGroupsCache = array();
73 protected $userGroups = array();
76 * Getter for current workspace id
78 * @return int current workspace id
80 public function getWorkspaceId() {
81 if ($this->workspaceId
== NULL) {
82 $this->setWorkspaceId($GLOBALS['BE_USER']->workspace
);
84 return $this->workspaceId
;
88 * Setter for current workspace id
90 * @param int current workspace id
92 private function setWorkspaceId($wsid) {
93 $this->workspaceId
= $wsid;
97 * constructor for workspace library
99 * @param int current workspace id
101 public function __construct() {
102 $this->setWorkspaceId($GLOBALS['BE_USER']->workspace
);
106 * Building an array with all stage ids and titles related to the given workspace
108 * @return array id and title of the stages
110 public function getStagesForWS() {
113 if (isset($this->workspaceStageCache
[$this->getWorkspaceId()])) {
114 $stages = $this->workspaceStageCache
[$this->getWorkspaceId()];
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') . '"'
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(
127 'parentid=' . $this->getWorkspaceId() . ' AND parenttable=' . $GLOBALS['TYPO3_DB']->fullQuoteStr('sys_workspace', self
::TABLE_STAGE
) . ' AND deleted=0',
133 foreach($workspaceStageRecs as $stage) {
134 $stage['title'] = $GLOBALS['LANG']->sL($this->pathToLocallang
. ':actionSendToStage') . ' "' . $stage['title'] . '"';
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') . '"'
145 'uid' => self
::STAGE_PUBLISH_EXECUTE_ID
,
146 'title' => $GLOBALS['LANG']->sL($this->pathToLocallang
. ':publish_execute_action_option')
148 $this->workspaceStageCache
[$this->getWorkspaceId()] = $stages;
155 * Returns an array of stages, the user is allowed to send to
156 * @return array id and title of stages
158 public function getStagesForWSUser() {
160 $stagesForWSUserData = array();
161 $allowedStages = array();
162 $orderedAllowedStages = array();
164 $workspaceStageRecs = $this->getStagesForWS();
165 if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
166 if ($GLOBALS['BE_USER']->isAdmin()) {
167 $orderedAllowedStages = $workspaceStageRecs;
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;
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;
184 if (isset($prevStage['uid'])) {
185 $allowedStages[$prevStage['uid']] = $prevStage;
189 $orderedAllowedStages = array_values($allowedStages);
192 return $orderedAllowedStages;
196 * Check if given workspace has custom staging activated
198 * @return bool true or false
200 public function checkCustomStagingForWS() {
201 $workspaceRec = t3lib_BEfunc
::getRecord('sys_workspace', $this->getWorkspaceId());
202 return $workspaceRec['custom_stages'] > 0;
206 * Gets the title of a stage.
208 * @param integer $ver_stage
211 public function getStageTitle($ver_stage) {
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');
219 case self
::STAGE_PUBLISH_ID
:
220 $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xml:stage_ready_to_publish');
222 case self
::STAGE_EDIT_ID
:
223 $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing');
226 $stageTitle = $this->getPropertyOfCurrentWorkspaceStage($ver_stage, 'title');
228 if ($stageTitle == null) {
229 $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.getStageTitle.stageNotFound');
238 * Gets a particular stage record.
240 * @param integer $stageid
243 public function getStageRecord($stageid) {
244 return t3lib_BEfunc
::getRecord('sys_workspace_stage', $stageid);
248 * Gets next stage in process for given stage id
250 * @param integer $stageid Id of the stage to fetch the next one for
251 * @return integer The next stage Id
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'),
262 $workspaceStageRecs = $this->getStagesForWS();
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);
272 next($workspaceStageRecs);
275 // @todo consider to throw an exception here
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') . '"'
289 * Recursive function to get all next stages for a record depending on user permissions
291 * @param array next stages
292 * @param int stage id
293 * @param int current stage id of the record
294 * @return array next stages
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;
301 $nextStageRecord = $this->getNextStage($stageId);
302 if (empty($nextStageRecord) ||
!is_array($nextStageRecord)) {
303 // There is no next stage
304 return $nextStageArray;
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']);
313 // He hasn't - return given next stage array
314 return $nextStageArray;
321 * Get next stage in process for given stage id
323 * @param int workspace id
327 public function getPrevStage($stageid) {
329 if (!t3lib_div
::testInt($stageid)) {
330 throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
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);
343 prev($workspaceStageRecs);
346 // @todo consider to throw an exception here
352 * Recursive function to get all prev stages for a record depending on user permissions
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
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);
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']);
376 return $prevStageArray;
380 * Get array of all responsilbe be_users for a stage
382 * @param int stage id
383 * @return array be_users with e-mail and name
385 public function getResponsibleBeUser($stageId) {
386 $workspaceRec = t3lib_BEfunc
::getRecord('sys_workspace', $this->getWorkspaceId());
387 $recipientArray = array();
390 case self
::STAGE_PUBLISH_EXECUTE_ID
:
391 case self
::STAGE_PUBLISH_ID
:
392 $userList = $this->getResponsibleUser($workspaceRec['adminusers']);
394 case self
::STAGE_EDIT_ID
:
395 $userList = $this->getResponsibleUser($workspaceRec['members']);
398 $responsible_persons = $this->getPropertyOfCurrentWorkspaceStage($stageId, 'responsible_persons');
399 $userList = $this->getResponsibleUser($responsible_persons);
403 if (!empty($userList)) {
404 $userRecords = t3lib_BEfunc
::getUserNames('username, uid, email, realName',
405 'AND uid IN (' . $userList . ')');
408 if (!empty($userRecords) && is_array($userRecords)) {
409 foreach ($userRecords as $userUid => $userRecord) {
410 $recipientArray[$userUid] = $userRecord;
413 return $recipientArray;
417 * Get uids of all responsilbe persons for a stage
419 * @param string responsible_persion value from stage record
420 * @return string uid list of responsible be_users
422 public function getResponsibleUser($stageRespValue) {
423 $stageValuesArray = array();
424 $stageValuesArray = t3lib_div
::trimExplode(',', $stageRespValue);
426 $beuserUidArray = array();
427 $begroupUidArray = array();
428 $allBeUserArray = array();
429 $begroupUidList = array();
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);
437 $beuserUidArray[] = $uidvalue;
440 if (!empty($begroupUidArray)) {
441 $allBeUserArray = t3lib_befunc
::getUserNames();
443 $begroupUidList = implode(',', $begroupUidArray);
445 $this->userGroups
= array();
446 $begroupUidArray = $this->fetchGroups($begroupUidList);
448 foreach ($begroupUidArray as $groupkey => $groupData) {
449 foreach ($allBeUserArray as $useruid => $userdata) {
450 if (t3lib_div
::inList($userdata['usergroup'], $groupData['uid'])) {
451 $beuserUidArray[] = $useruid;
457 array_unique($beuserUidArray);
458 return implode(',', $beuserUidArray);
464 * @param string $idList
467 private function fetchGroups($grList, $idList = '') {
469 $cacheKey = md5($grList . $idList);
470 $groupList = array();
471 if (isset($this->fetchGroupsCache
[$cacheKey])) {
472 $groupList = $this->fetchGroupsCache
[$cacheKey];
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();
478 $groupList = $this->fetchGroupsRecursive($grList);
479 $this->fetchGroupsCache
[$cacheKey] = $groupList;
485 * @param array $groups
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);
492 // The userGroups array is filled
493 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
494 $this->userGroups
[$row['uid']] = $row;
499 * Fetches particular groups recursively.
502 * @param string $idList
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);
510 if (count($missingGroups) > 0) {
511 $this->fetchGroupsFromDB($missingGroups);
514 // Traversing records in the correct order
515 foreach ($requiredGroups as $uid) { // traversing list
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'])) {
524 $theList = implode(',', t3lib_div
::intExplode(',', $row['subgroup']));
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;
535 return $this->userGroups
;
539 * Gets a property of a workspaces stage.
541 * @param integer $stageId
542 * @param string $property
545 public function getPropertyOfCurrentWorkspaceStage($stageId, $property) {
548 if (!t3lib_div
::testInt($stageId)) {
549 throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
552 $workspaceStage = t3lib_BEfunc
::getRecord(self
::TABLE_STAGE
, $stageId);
554 if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
555 $result = $workspaceStage[$property];
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
564 * @param integer $stageId
565 * @return array position => 3, count => 20
567 public function getPositionOfCurrentStage($stageId) {
568 $stagesOfWS = $this->getStagesForWS();
569 $countOfStages = count($stagesOfWS);
572 case self
::STAGE_PUBLISH_ID
:
573 $position = $countOfStages;
575 case self
::STAGE_EDIT_ID
:
580 foreach ($stagesOfWS as $key => $stageInfoArray) {
582 if ($stageId == $stageInfoArray['uid']) {
588 return array('position' => $position, 'count' => $countOfStages);
592 * Check if the user has access to the previous stage, relative to the given stage
594 * @param integer $stageId
597 public function isPrevStageAllowedForUser($stageId) {
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);
607 } catch (Exception
$e) {
608 // Exception raised - we're not allowed to go this way
615 * Check if the user has access to the next stage, relative to the given stage
617 * @param integer $stageId
620 public function isNextStageAllowedForUser($stageId) {
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);
630 } catch (Exception
$e) {
631 // Exception raised - we're not allowed to go this way
641 protected function isStageAllowedForUser($stageId) {
642 $cacheKey = $this->getWorkspaceId() . '_' . $stageId;
644 if (isset($this->workspaceStageAllowedCache
[$cacheKey])) {
645 $isAllowed = $this->workspaceStageAllowedCache
[$cacheKey];
647 $isAllowed = $GLOBALS['BE_USER']->workspaceCheckStageForCurrent($stageId);
648 $this->workspaceStageAllowedCache
[$cacheKey] = $isAllowed;
654 * Determines whether a stage Id is valid.
656 * @param integer $stageId The stage Id to be checked
659 public function isValid($stageId) {
661 $stages = $this->getStagesForWS();
663 foreach ($stages as $stage) {
664 if ($stage['uid'] == $stageId) {
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']);