TYPO3  7.6
StagesService.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Workspaces\Service;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
22 
27 {
28  const TABLE_STAGE = 'sys_workspace_stage';
29  // 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
31  // ready to publish stage
32  const STAGE_PUBLISH_ID = -10;
33  const STAGE_EDIT_ID = 0;
35  const MODE_NOTIFY_ALL = 1;
37 
43  private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf';
44 
48  protected $recordService;
49 
55  protected $workspaceStageCache = array();
56 
60  protected $workspaceStageAllowedCache = array();
61 
65  protected $fetchGroupsCache = array();
66 
70  protected $userGroups = array();
71 
77  public function getWorkspaceId()
78  {
79  return $this->getBackendUser()->workspace;
80  }
81 
90  $workspaceItems,
91  array $byTableName = array('tt_content', 'pages', 'pages_language_overlay')
92  ) {
93  $currentStage = array();
94  $previousStage = array();
95  $usedStages = array();
96  $found = false;
97  $availableStagesForWS = array_reverse($this->getStagesForWS());
98  $availableStagesForWSUser = $this->getStagesForWSUser();
99  $byTableName = array_flip($byTableName);
100  foreach ($workspaceItems as $tableName => $items) {
101  if (!array_key_exists($tableName, $byTableName)) {
102  continue;
103  }
104  foreach ($items as $item) {
105  $usedStages[$item['t3ver_stage']] = true;
106  }
107  }
108  foreach ($availableStagesForWS as $stage) {
109  if (isset($usedStages[$stage['uid']])) {
110  $currentStage = $stage;
111  $previousStage = $this->getPrevStage($stage['uid']);
112  break;
113  }
114  }
115  foreach ($availableStagesForWSUser as $userWS) {
116  if ($previousStage['uid'] == $userWS['uid']) {
117  $found = true;
118  break;
119  }
120  }
121  if ($found === false) {
122  $previousStage = array();
123  }
124  return array(
125  $currentStage,
126  $previousStage
127  );
128  }
129 
138  $workspaceItems,
139  array $byTableName = array('tt_content', 'pages', 'pages_language_overlay')
140  ) {
141  $currentStage = array();
142  $usedStages = array();
143  $nextStage = array();
144  $availableStagesForWS = $this->getStagesForWS();
145  $availableStagesForWSUser = $this->getStagesForWSUser();
146  $byTableName = array_flip($byTableName);
147  $found = false;
148  foreach ($workspaceItems as $tableName => $items) {
149  if (!array_key_exists($tableName, $byTableName)) {
150  continue;
151  }
152  foreach ($items as $item) {
153  $usedStages[$item['t3ver_stage']] = true;
154  }
155  }
156  foreach ($availableStagesForWS as $stage) {
157  if (isset($usedStages[$stage['uid']])) {
158  $currentStage = $stage;
159  $nextStage = $this->getNextStage($stage['uid']);
160  break;
161  }
162  }
163  foreach ($availableStagesForWSUser as $userWS) {
164  if ($nextStage['uid'] == $userWS['uid']) {
165  $found = true;
166  break;
167  }
168  }
169  if ($found === false) {
170  $nextStage = array();
171  }
172  return array(
173  $currentStage,
174  $nextStage
175  );
176  }
177 
183  public function getStagesForWS()
184  {
185  if (isset($this->workspaceStageCache[$this->getWorkspaceId()])) {
186  $stages = $this->workspaceStageCache[$this->getWorkspaceId()];
187  } elseif ($this->getWorkspaceId() === 0) {
188  $stages = array();
189  } else {
190  $stages = $this->prepareStagesArray($this->getWorkspaceRecord()->getStages());
191  $this->workspaceStageCache[$this->getWorkspaceId()] = $stages;
192  }
193  return $stages;
194  }
195 
201  public function getStagesForWSUser()
202  {
203  if ($GLOBALS['BE_USER']->isAdmin()) {
204  return $this->getStagesForWS();
205  }
206 
208  $allowedStages = array();
209  $stageRecords = $this->getWorkspaceRecord()->getStages();
210 
211  // Only use stages that are allowed for current backend user
212  foreach ($stageRecords as $stageRecord) {
213  if ($stageRecord->isAllowed()) {
214  $allowedStages[$stageRecord->getUid()] = $stageRecord;
215  }
216  }
217 
218  // Add previous and next stages (even if they are not allowed!)
219  foreach ($allowedStages as $allowedStage) {
220  $previousStage = $allowedStage->getPrevious();
221  $nextStage = $allowedStage->getNext();
222  if ($previousStage !== null && !isset($allowedStages[$previousStage->getUid()])) {
223  $allowedStages[$previousStage->getUid()] = $previousStage;
224  }
225  if ($nextStage !== null && !isset($allowedStages[$nextStage->getUid()])) {
226  $allowedStages[$nextStage->getUid()] = $nextStage;
227  }
228  }
229 
230  uasort($allowedStages, function (StageRecord $first, StageRecord $second) { return $first->determineOrder($second); });
231  return $this->prepareStagesArray($allowedStages);
232  }
233 
240  protected function prepareStagesArray(array $stageRecords)
241  {
242  $stagesArray = array();
243  foreach ($stageRecords as $stageRecord) {
244  $stage = array(
245  'uid' => $stageRecord->getUid(),
246  'label' => $stageRecord->getTitle(),
247  );
248  if (!$stageRecord->isExecuteStage()) {
249  $stage['title'] = $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $stageRecord->getTitle() . '"';
250  } else {
251  $stage['title'] = $GLOBALS['LANG']->sL($this->pathToLocallang . ':publish_execute_action_option');
252  }
253  $stagesArray[] = $stage;
254  }
255  return $stagesArray;
256  }
257 
264  public function getStageTitle($ver_stage)
265  {
266  $stageTitle = '';
267  switch ($ver_stage) {
268  case self::STAGE_PUBLISH_EXECUTE_ID:
269  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xlf:stage_publish');
270  break;
271  case self::STAGE_PUBLISH_ID:
272  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_ready_to_publish');
273  break;
274  case self::STAGE_EDIT_ID:
275  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xlf:stage_editing');
276  break;
277  default:
278  $stageTitle = $this->getPropertyOfCurrentWorkspaceStage($ver_stage, 'title');
279  if ($stageTitle == null) {
280  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.getStageTitle.stageNotFound');
281  }
282  }
283  return $stageTitle;
284  }
285 
292  public function getStageRecord($stageid)
293  {
294  return BackendUtility::getRecord('sys_workspace_stage', $stageid);
295  }
296 
304  public function getNextStage($stageId)
305  {
307  throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'), 1291109987);
308  }
309  $nextStage = false;
310  $workspaceStageRecs = $this->getStagesForWS();
311  if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
312  reset($workspaceStageRecs);
313  while (!is_null(($workspaceStageRecKey = key($workspaceStageRecs)))) {
314  $workspaceStageRec = current($workspaceStageRecs);
315  if ($workspaceStageRec['uid'] == $stageId) {
316  $nextStage = next($workspaceStageRecs);
317  break;
318  }
319  next($workspaceStageRecs);
320  }
321  } else {
322  }
323  if ($nextStage === false) {
324  $nextStage[] = array(
325  'uid' => self::STAGE_EDIT_ID,
326  'title' => $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "'
327  . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xlf:stage_editing') . '"'
328  );
329  }
330  return $nextStage;
331  }
332 
340  public function getNextStages(array &$nextStageArray, $stageId)
341  {
342  // Current stage is "Ready to publish" - there is no next stage
343  if ($stageId == self::STAGE_PUBLISH_ID) {
344  return $nextStageArray;
345  } else {
346  $nextStageRecord = $this->getNextStage($stageId);
347  if (empty($nextStageRecord) || !is_array($nextStageRecord)) {
348  // There is no next stage
349  return $nextStageArray;
350  } else {
351  // Check if the user has the permission to for the current stage
352  // If this next stage record is the first next stage after the current the user
353  // has always the needed permission
354  if ($this->isStageAllowedForUser($stageId)) {
355  $nextStageArray[] = $nextStageRecord;
356  return $this->getNextStages($nextStageArray, $nextStageRecord['uid']);
357  } else {
358  // He hasn't - return given next stage array
359  return $nextStageArray;
360  }
361  }
362  }
363  }
364 
372  public function getPrevStage($stageId)
373  {
375  throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'));
376  }
377  $prevStage = false;
378  $workspaceStageRecs = $this->getStagesForWS();
379  if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
380  end($workspaceStageRecs);
381  while (!is_null(($workspaceStageRecKey = key($workspaceStageRecs)))) {
382  $workspaceStageRec = current($workspaceStageRecs);
383  if ($workspaceStageRec['uid'] == $stageId) {
384  $prevStage = prev($workspaceStageRecs);
385  break;
386  }
387  prev($workspaceStageRecs);
388  }
389  } else {
390  }
391  return $prevStage;
392  }
393 
401  public function getPrevStages(array &$prevStageArray, $stageId)
402  {
403  // Current stage is "Editing" - there is no prev stage
404  if ($stageId != self::STAGE_EDIT_ID) {
405  $prevStageRecord = $this->getPrevStage($stageId);
406  if (!empty($prevStageRecord) && is_array($prevStageRecord)) {
407  // Check if the user has the permission to switch to that stage
408  // If this prev stage record is the first previous stage before the current
409  // the user has always the needed permission
410  if ($this->isStageAllowedForUser($stageId)) {
411  $prevStageArray[] = $prevStageRecord;
412  $prevStageArray = $this->getPrevStages($prevStageArray, $prevStageRecord['uid']);
413  }
414  }
415  }
416  return $prevStageArray;
417  }
418 
427  public function getResponsibleBeUser($stageRecord, $selectDefaultUserField = false)
428  {
429  if (!$stageRecord instanceof StageRecord) {
430  $stageRecord = $this->getWorkspaceRecord()->getStage($stageRecord);
431  }
432 
433  $recipientArray = array();
434 
435  if (!$selectDefaultUserField) {
436  $backendUserIds = $stageRecord->getAllRecipients();
437  } else {
438  $backendUserIds = $stageRecord->getDefaultRecipients();
439  }
440 
441  $userList = implode(',', $backendUserIds);
442  $userRecords = $this->getBackendUsers($userList);
443  foreach ($userRecords as $userUid => $userRecord) {
444  $recipientArray[$userUid] = $userRecord;
445  }
446  return $recipientArray;
447  }
448 
457  public function getResponsibleUser($stageRespValue)
458  {
459  return implode(',', $this->resolveBackendUserIds($stageRespValue));
460  }
461 
469  public function resolveBackendUserIds($backendUserGroupList)
470  {
471  $elements = GeneralUtility::trimExplode(',', $backendUserGroupList, true);
472  $backendUserIds = array();
473  $backendGroupIds = array();
474 
475  foreach ($elements as $element) {
476  if (strpos($element, 'be_users_') === 0) {
477  // Current value is a uid of a be_user record
478  $backendUserIds[] = str_replace('be_users_', '', $element);
479  } elseif (strpos($element, 'be_groups_') === 0) {
480  $backendGroupIds[] = str_replace('be_groups_', '', $element);
481  } elseif ((int)$element) {
482  $backendUserIds[] = (int)$element;
483  }
484  }
485 
486  if (!empty($backendGroupIds)) {
487  $allBeUserArray = BackendUtility::getUserNames();
488  $backendGroupList = implode(',', $backendGroupIds);
489  $this->userGroups = array();
490  $backendGroups = $this->fetchGroups($backendGroupList);
491  foreach ($backendGroups as $backendGroup) {
492  foreach ($allBeUserArray as $backendUserId => $backendUser) {
493  if (GeneralUtility::inList($backendUser['usergroup_cached_list'], $backendGroup['uid'])) {
494  $backendUserIds[] = $backendUserId;
495  }
496  }
497  }
498  }
499 
500  return array_unique($backendUserIds);
501  }
502 
509  public function getBackendUsers($backendUserList)
510  {
511  if (empty($backendUserList)) {
512  return array();
513  }
514 
515  $backendUserList = $this->getDatabaseConnection()->cleanIntList($backendUserList);
516  $backendUsers = BackendUtility::getUserNames(
517  'username, uid, email, realName',
518  'AND uid IN (' . $backendUserList . ')' . BackendUtility::BEenableFields('be_users')
519  );
520 
521  if (empty($backendUsers)) {
522  $backendUsers = array();
523  }
524  return $backendUsers;
525  }
526 
531  public function getPreselectedRecipients(StageRecord $stageRecord)
532  {
533  if ($stageRecord->areEditorsPreselected()) {
534  return array_merge(
535  $stageRecord->getPreselectedRecipients(),
536  $this->getRecordService()->getCreateUserIds()
537  );
538  } else {
539  return $stageRecord->getPreselectedRecipients();
540  }
541  }
542 
546  protected function getWorkspaceRecord()
547  {
548  return WorkspaceRecord::get($this->getWorkspaceId());
549  }
550 
556  private function fetchGroups($grList, $idList = '')
557  {
558  $cacheKey = md5($grList . $idList);
559  $groupList = array();
560  if (isset($this->fetchGroupsCache[$cacheKey])) {
561  $groupList = $this->fetchGroupsCache[$cacheKey];
562  } else {
563  if ($idList === '') {
564  // we're at the beginning of the recursion and therefore we need to reset the userGroups member
565  $this->userGroups = array();
566  }
567  $groupList = $this->fetchGroupsRecursive($grList);
568  $this->fetchGroupsCache[$cacheKey] = $groupList;
569  }
570  return $groupList;
571  }
572 
577  private function fetchGroupsFromDB(array $groups)
578  {
579  $whereSQL = 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $GLOBALS['TYPO3_DB']->cleanIntArray($groups)) . ') ';
580  $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'be_groups', $whereSQL);
581  // The userGroups array is filled
582  while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
583  $this->userGroups[$row['uid']] = $row;
584  }
585  $GLOBALS['TYPO3_DB']->sql_free_result($res);
586  }
587 
595  private function fetchGroupsRecursive($grList, $idList = '')
596  {
597  $requiredGroups = GeneralUtility::intExplode(',', $grList, true);
598  $existingGroups = array_keys($this->userGroups);
599  $missingGroups = array_diff($requiredGroups, $existingGroups);
600  if (!empty($missingGroups)) {
601  $this->fetchGroupsFromDB($missingGroups);
602  }
603  // Traversing records in the correct order
604  foreach ($requiredGroups as $uid) {
605  // traversing list
606  // Get row:
607  $row = $this->userGroups[$uid];
608  if (is_array($row) && !GeneralUtility::inList($idList, $uid)) {
609  // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
610  // If the localconf.php option isset the user of the sub- sub- groups will also be used
611  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['customStageShowRecipientRecursive'] == 1) {
612  // Include sub groups
613  if (trim($row['subgroup'])) {
614  // Make integer list
615  $theList = implode(',', GeneralUtility::intExplode(',', $row['subgroup']));
616  // Get the subarray
617  $subbarray = $this->fetchGroups($theList, $idList . ',' . $uid);
618  list($subUid, $subArray) = each($subbarray);
619  // Merge the subarray to the already existing userGroups array
620  $this->userGroups[$subUid] = $subArray;
621  }
622  }
623  }
624  }
625  return $this->userGroups;
626  }
627 
636  public function getPropertyOfCurrentWorkspaceStage($stageId, $property)
637  {
638  $result = null;
640  throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'));
641  }
642  $workspaceStage = BackendUtility::getRecord(self::TABLE_STAGE, $stageId);
643  if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
644  $result = $workspaceStage[$property];
645  }
646  return $result;
647  }
648 
656  public function getPositionOfCurrentStage($stageId)
657  {
658  $stagesOfWS = $this->getStagesForWS();
659  $countOfStages = count($stagesOfWS);
660  switch ($stageId) {
661  case self::STAGE_PUBLISH_ID:
662  $position = $countOfStages;
663  break;
664  case self::STAGE_EDIT_ID:
665  $position = 1;
666  break;
667  default:
668  $position = 1;
669  foreach ($stagesOfWS as $key => $stageInfoArray) {
670  $position++;
671  if ($stageId == $stageInfoArray['uid']) {
672  break;
673  }
674  }
675  }
676  return array('position' => $position, 'count' => $countOfStages);
677  }
678 
685  public function isPrevStageAllowedForUser($stageId)
686  {
687  $isAllowed = false;
688  try {
689  $prevStage = $this->getPrevStage($stageId);
690  // if there's no prev-stage the stageIds match,
691  // otherwise we've to check if the user is permitted to use the stage
692  if (!empty($prevStage) && $prevStage['uid'] != $stageId) {
693  // if the current stage is allowed for the user, the user is also allowed to send to prev
694  $isAllowed = $this->isStageAllowedForUser($stageId);
695  }
696  } catch (\Exception $e) {
697  }
698  return $isAllowed;
699  }
700 
707  public function isNextStageAllowedForUser($stageId)
708  {
709  $isAllowed = false;
710  try {
711  $nextStage = $this->getNextStage($stageId);
712  // if there's no next-stage the stageIds match,
713  // otherwise we've to check if the user is permitted to use the stage
714  if (!empty($nextStage) && $nextStage['uid'] != $stageId) {
715  // if the current stage is allowed for the user, the user is also allowed to send to next
716  $isAllowed = $this->isStageAllowedForUser($stageId);
717  }
718  } catch (\Exception $e) {
719  }
720  return $isAllowed;
721  }
722 
727  protected function isStageAllowedForUser($stageId)
728  {
729  $cacheKey = $this->getWorkspaceId() . '_' . $stageId;
730  $isAllowed = false;
731  if (isset($this->workspaceStageAllowedCache[$cacheKey])) {
732  $isAllowed = $this->workspaceStageAllowedCache[$cacheKey];
733  } else {
734  $isAllowed = $GLOBALS['BE_USER']->workspaceCheckStageForCurrent($stageId);
735  $this->workspaceStageAllowedCache[$cacheKey] = $isAllowed;
736  }
737  return $isAllowed;
738  }
739 
746  public function isValid($stageId)
747  {
748  $isValid = false;
749  $stages = $this->getStagesForWS();
750  foreach ($stages as $stage) {
751  if ($stage['uid'] == $stageId) {
752  $isValid = true;
753  break;
754  }
755  }
756  return $isValid;
757  }
758 
771  public function getNotificationMode($stageId)
772  {
774  throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'));
775  }
776  switch ($stageId) {
777  case self::STAGE_PUBLISH_EXECUTE_ID:
778 
779  case self::STAGE_PUBLISH_ID:
780  $workspaceRecord = BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
781  return $workspaceRecord['publish_notification_mode'];
782  break;
783  case self::STAGE_EDIT_ID:
784  $workspaceRecord = BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
785  return $workspaceRecord['edit_notification_mode'];
786  break;
787  default:
788  $workspaceStage = BackendUtility::getRecord(self::TABLE_STAGE, $stageId);
789  if (is_array($workspaceStage) && isset($workspaceStage['notification_mode'])) {
790  return $workspaceStage['notification_mode'];
791  }
792  }
793  }
794 
798  public function getRecordService()
799  {
800  if (!isset($this->recordService)) {
801  $this->recordService = GeneralUtility::makeInstance(RecordService::class);
802  }
803  return $this->recordService;
804  }
805 
809  protected function getBackendUser()
810  {
811  return $GLOBALS['BE_USER'];
812  }
813 
817  protected function getDatabaseConnection()
818  {
819  return $GLOBALS['TYPO3_DB'];
820  }
821 }