TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 
24 
29 {
30  const TABLE_STAGE = 'sys_workspace_stage';
31  // 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
33  // ready to publish stage
34  const STAGE_PUBLISH_ID = -10;
35  const STAGE_EDIT_ID = 0;
37  const MODE_NOTIFY_ALL = 1;
39 
45  private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf';
46 
50  protected $recordService;
51 
57  protected $workspaceStageCache = [];
58 
63 
67  protected $fetchGroupsCache = [];
68 
72  protected $userGroups = [];
73 
79  public function getWorkspaceId()
80  {
81  return $this->getBackendUser()->workspace;
82  }
83 
92  $workspaceItems,
93  array $byTableName = ['tt_content', 'pages', 'pages_language_overlay']
94  ) {
95  $currentStage = [];
96  $previousStage = [];
97  $usedStages = [];
98  $found = false;
99  $availableStagesForWS = array_reverse($this->getStagesForWS());
100  $availableStagesForWSUser = $this->getStagesForWSUser();
101  $byTableName = array_flip($byTableName);
102  foreach ($workspaceItems as $tableName => $items) {
103  if (!array_key_exists($tableName, $byTableName)) {
104  continue;
105  }
106  foreach ($items as $item) {
107  $usedStages[$item['t3ver_stage']] = true;
108  }
109  }
110  foreach ($availableStagesForWS as $stage) {
111  if (isset($usedStages[$stage['uid']])) {
112  $currentStage = $stage;
113  $previousStage = $this->getPrevStage($stage['uid']);
114  break;
115  }
116  }
117  foreach ($availableStagesForWSUser as $userWS) {
118  if ($previousStage['uid'] == $userWS['uid']) {
119  $found = true;
120  break;
121  }
122  }
123  if ($found === false || !$this->isStageAllowedForUser($currentStage['uid'])) {
124  $previousStage = [];
125  }
126  return [
127  $currentStage,
128  $previousStage
129  ];
130  }
131 
140  $workspaceItems,
141  array $byTableName = ['tt_content', 'pages', 'pages_language_overlay']
142  ) {
143  $currentStage = [];
144  $usedStages = [];
145  $nextStage = [];
146  $availableStagesForWS = $this->getStagesForWS();
147  $availableStagesForWSUser = $this->getStagesForWSUser();
148  $byTableName = array_flip($byTableName);
149  $found = false;
150  foreach ($workspaceItems as $tableName => $items) {
151  if (!array_key_exists($tableName, $byTableName)) {
152  continue;
153  }
154  foreach ($items as $item) {
155  $usedStages[$item['t3ver_stage']] = true;
156  }
157  }
158  foreach ($availableStagesForWS as $stage) {
159  if (isset($usedStages[$stage['uid']])) {
160  $currentStage = $stage;
161  $nextStage = $this->getNextStage($stage['uid']);
162  break;
163  }
164  }
165  foreach ($availableStagesForWSUser as $userWS) {
166  if ($nextStage['uid'] == $userWS['uid']) {
167  $found = true;
168  break;
169  }
170  }
171  if ($found === false || !$this->isStageAllowedForUser($currentStage['uid'])) {
172  $nextStage = [];
173  }
174  return [
175  $currentStage,
176  $nextStage
177  ];
178  }
179 
185  public function getStagesForWS()
186  {
187  if (isset($this->workspaceStageCache[$this->getWorkspaceId()])) {
188  $stages = $this->workspaceStageCache[$this->getWorkspaceId()];
189  } elseif ($this->getWorkspaceId() === 0) {
190  $stages = [];
191  } else {
192  $stages = $this->prepareStagesArray($this->getWorkspaceRecord()->getStages());
193  $this->workspaceStageCache[$this->getWorkspaceId()] = $stages;
194  }
195  return $stages;
196  }
197 
203  public function getStagesForWSUser()
204  {
205  if ($GLOBALS['BE_USER']->isAdmin()) {
206  return $this->getStagesForWS();
207  }
208 
210  $allowedStages = [];
211  $stageRecords = $this->getWorkspaceRecord()->getStages();
212 
213  // Only use stages that are allowed for current backend user
214  foreach ($stageRecords as $stageRecord) {
215  if ($stageRecord->isAllowed()) {
216  $allowedStages[$stageRecord->getUid()] = $stageRecord;
217  }
218  }
219 
220  // Add previous and next stages (even if they are not allowed!)
221  foreach ($allowedStages as $allowedStage) {
222  $previousStage = $allowedStage->getPrevious();
223  $nextStage = $allowedStage->getNext();
224  if ($previousStage !== null && !isset($allowedStages[$previousStage->getUid()])) {
225  $allowedStages[$previousStage->getUid()] = $previousStage;
226  }
227  if ($nextStage !== null && !isset($allowedStages[$nextStage->getUid()])) {
228  $allowedStages[$nextStage->getUid()] = $nextStage;
229  }
230  }
231 
232  uasort($allowedStages, function (StageRecord $first, StageRecord $second) {
233  return $first->determineOrder($second);
234  });
235  return $this->prepareStagesArray($allowedStages);
236  }
237 
244  protected function prepareStagesArray(array $stageRecords)
245  {
246  $stagesArray = [];
247  foreach ($stageRecords as $stageRecord) {
248  $stage = [
249  'uid' => $stageRecord->getUid(),
250  'label' => $stageRecord->getTitle(),
251  ];
252  if (!$stageRecord->isExecuteStage()) {
253  $stage['title'] = $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $stageRecord->getTitle() . '"';
254  } else {
255  $stage['title'] = $GLOBALS['LANG']->sL($this->pathToLocallang . ':publish_execute_action_option');
256  }
257  $stagesArray[] = $stage;
258  }
259  return $stagesArray;
260  }
261 
268  public function getStageTitle($ver_stage)
269  {
270  switch ($ver_stage) {
271  case self::STAGE_PUBLISH_EXECUTE_ID:
272  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_user_ws.xlf:stage_publish');
273  break;
274  case self::STAGE_PUBLISH_ID:
275  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_ready_to_publish');
276  break;
277  case self::STAGE_EDIT_ID:
278  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_user_ws.xlf:stage_editing');
279  break;
280  default:
281  $stageTitle = $this->getPropertyOfCurrentWorkspaceStage($ver_stage, 'title');
282  if ($stageTitle == null) {
283  $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.getStageTitle.stageNotFound');
284  }
285  }
286  return $stageTitle;
287  }
288 
295  public function getStageRecord($stageid)
296  {
297  return BackendUtility::getRecord('sys_workspace_stage', $stageid);
298  }
299 
307  public function getNextStage($stageId)
308  {
310  throw new \InvalidArgumentException(
311  $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'),
312  1291109987
313  );
314  }
315  $nextStage = false;
316  $workspaceStageRecs = $this->getStagesForWS();
317  if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
318  reset($workspaceStageRecs);
319  while (!is_null(($workspaceStageRecKey = key($workspaceStageRecs)))) {
320  $workspaceStageRec = current($workspaceStageRecs);
321  if ($workspaceStageRec['uid'] == $stageId) {
322  $nextStage = next($workspaceStageRecs);
323  break;
324  }
325  next($workspaceStageRecs);
326  }
327  }
328  if ($nextStage === false) {
329  $nextStage[] = [
330  'uid' => self::STAGE_EDIT_ID,
331  'title' => $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "'
332  . $GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_user_ws.xlf:stage_editing') . '"'
333  ];
334  }
335  return $nextStage;
336  }
337 
345  public function getNextStages(array &$nextStageArray, $stageId)
346  {
347  // Current stage is "Ready to publish" - there is no next stage
348  if ($stageId == self::STAGE_PUBLISH_ID) {
349  return $nextStageArray;
350  } else {
351  $nextStageRecord = $this->getNextStage($stageId);
352  if (empty($nextStageRecord) || !is_array($nextStageRecord)) {
353  // There is no next stage
354  return $nextStageArray;
355  } else {
356  // Check if the user has the permission to for the current stage
357  // If this next stage record is the first next stage after the current the user
358  // has always the needed permission
359  if ($this->isStageAllowedForUser($stageId)) {
360  $nextStageArray[] = $nextStageRecord;
361  return $this->getNextStages($nextStageArray, $nextStageRecord['uid']);
362  } else {
363  // He hasn't - return given next stage array
364  return $nextStageArray;
365  }
366  }
367  }
368  }
369 
377  public function getPrevStage($stageId)
378  {
380  throw new \InvalidArgumentException(
381  $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'),
382  1476048351
383  );
384  }
385  $prevStage = false;
386  $workspaceStageRecs = $this->getStagesForWS();
387  if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
388  end($workspaceStageRecs);
389  while (!is_null(($workspaceStageRecKey = key($workspaceStageRecs)))) {
390  $workspaceStageRec = current($workspaceStageRecs);
391  if ($workspaceStageRec['uid'] == $stageId) {
392  $prevStage = prev($workspaceStageRecs);
393  break;
394  }
395  prev($workspaceStageRecs);
396  }
397  } else {
398  }
399  return $prevStage;
400  }
401 
409  public function getPrevStages(array &$prevStageArray, $stageId)
410  {
411  // Current stage is "Editing" - there is no prev stage
412  if ($stageId != self::STAGE_EDIT_ID) {
413  $prevStageRecord = $this->getPrevStage($stageId);
414  if (!empty($prevStageRecord) && is_array($prevStageRecord)) {
415  // Check if the user has the permission to switch to that stage
416  // If this prev stage record is the first previous stage before the current
417  // the user has always the needed permission
418  if ($this->isStageAllowedForUser($stageId)) {
419  $prevStageArray[] = $prevStageRecord;
420  $prevStageArray = $this->getPrevStages($prevStageArray, $prevStageRecord['uid']);
421  }
422  }
423  }
424  return $prevStageArray;
425  }
426 
435  public function getResponsibleBeUser($stageRecord, $selectDefaultUserField = false)
436  {
437  if (!$stageRecord instanceof StageRecord) {
438  $stageRecord = $this->getWorkspaceRecord()->getStage($stageRecord);
439  }
440 
441  $recipientArray = [];
442 
443  if (!$selectDefaultUserField) {
444  $backendUserIds = $stageRecord->getAllRecipients();
445  } else {
446  $backendUserIds = $stageRecord->getDefaultRecipients();
447  }
448 
449  $userList = implode(',', $backendUserIds);
450  $userRecords = $this->getBackendUsers($userList);
451  foreach ($userRecords as $userUid => $userRecord) {
452  $recipientArray[$userUid] = $userRecord;
453  }
454  return $recipientArray;
455  }
456 
465  public function getResponsibleUser($stageRespValue)
466  {
467  return implode(',', $this->resolveBackendUserIds($stageRespValue));
468  }
469 
477  public function resolveBackendUserIds($backendUserGroupList)
478  {
479  $elements = GeneralUtility::trimExplode(',', $backendUserGroupList, true);
480  $backendUserIds = [];
481  $backendGroupIds = [];
482 
483  foreach ($elements as $element) {
484  if (strpos($element, 'be_users_') === 0) {
485  // Current value is a uid of a be_user record
486  $backendUserIds[] = str_replace('be_users_', '', $element);
487  } elseif (strpos($element, 'be_groups_') === 0) {
488  $backendGroupIds[] = str_replace('be_groups_', '', $element);
489  } elseif ((int)$element) {
490  $backendUserIds[] = (int)$element;
491  }
492  }
493 
494  if (!empty($backendGroupIds)) {
495  $allBeUserArray = BackendUtility::getUserNames();
496  $backendGroupList = implode(',', $backendGroupIds);
497  $this->userGroups = [];
498  $backendGroups = $this->fetchGroups($backendGroupList);
499  foreach ($backendGroups as $backendGroup) {
500  foreach ($allBeUserArray as $backendUserId => $backendUser) {
501  if (GeneralUtility::inList($backendUser['usergroup_cached_list'], $backendGroup['uid'])) {
502  $backendUserIds[] = $backendUserId;
503  }
504  }
505  }
506  }
507 
508  return array_unique($backendUserIds);
509  }
510 
517  public function getBackendUsers($backendUserList)
518  {
519  if (empty($backendUserList)) {
520  return [];
521  }
522 
523  $backendUserList = implode(',', GeneralUtility::intExplode(',', $backendUserList));
524  $backendUsers = BackendUtility::getUserNames(
525  'username, uid, email, realName',
526  'AND uid IN (' . $backendUserList . ')' . BackendUtility::BEenableFields('be_users')
527  );
528 
529  if (empty($backendUsers)) {
530  $backendUsers = [];
531  }
532  return $backendUsers;
533  }
534 
539  public function getPreselectedRecipients(StageRecord $stageRecord)
540  {
541  if ($stageRecord->areEditorsPreselected()) {
542  return array_merge(
543  $stageRecord->getPreselectedRecipients(),
544  $this->getRecordService()->getCreateUserIds()
545  );
546  } else {
547  return $stageRecord->getPreselectedRecipients();
548  }
549  }
550 
554  protected function getWorkspaceRecord()
555  {
556  return WorkspaceRecord::get($this->getWorkspaceId());
557  }
558 
564  private function fetchGroups($grList, $idList = '')
565  {
566  $cacheKey = md5($grList . $idList);
567  $groupList = [];
568  if (isset($this->fetchGroupsCache[$cacheKey])) {
569  $groupList = $this->fetchGroupsCache[$cacheKey];
570  } else {
571  if ($idList === '') {
572  // we're at the beginning of the recursion and therefore we need to reset the userGroups member
573  $this->userGroups = [];
574  }
575  $groupList = $this->fetchGroupsRecursive($grList);
576  $this->fetchGroupsCache[$cacheKey] = $groupList;
577  }
578  return $groupList;
579  }
580 
585  private function fetchGroupsFromDB(array $groups)
586  {
587  // The userGroups array is filled
588  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_groups');
589 
590  $result = $queryBuilder
591  ->select('*')
592  ->from('be_groups')
593  ->where(
594  $queryBuilder->expr()->in(
595  'uid',
596  $queryBuilder->createNamedParameter($groups, Connection::PARAM_INT_ARRAY)
597  )
598  )
599  ->execute();
600 
601  while ($row = $result->fetch()) {
602  $this->userGroups[$row['uid']] = $row;
603  }
604  }
605 
613  private function fetchGroupsRecursive($grList, $idList = '')
614  {
615  $requiredGroups = GeneralUtility::intExplode(',', $grList, true);
616  $existingGroups = array_keys($this->userGroups);
617  $missingGroups = array_diff($requiredGroups, $existingGroups);
618  if (!empty($missingGroups)) {
619  $this->fetchGroupsFromDB($missingGroups);
620  }
621  // Traversing records in the correct order
622  foreach ($requiredGroups as $uid) {
623  // traversing list
624  // Get row:
625  $row = $this->userGroups[$uid];
626  if (is_array($row) && !GeneralUtility::inList($idList, $uid)) {
627  // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
628  // If the localconf.php option isset the user of the sub- sub- groups will also be used
629  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['customStageShowRecipientRecursive'] == 1) {
630  // Include sub groups
631  if (trim($row['subgroup'])) {
632  // Make integer list
633  $theList = implode(',', GeneralUtility::intExplode(',', $row['subgroup']));
634  // Get the subarray
635  $subbarray = $this->fetchGroups($theList, $idList . ',' . $uid);
636  list($subUid, $subArray) = each($subbarray);
637  // Merge the subarray to the already existing userGroups array
638  $this->userGroups[$subUid] = $subArray;
639  }
640  }
641  }
642  }
643  return $this->userGroups;
644  }
645 
654  public function getPropertyOfCurrentWorkspaceStage($stageId, $property)
655  {
656  $result = null;
658  throw new \InvalidArgumentException(
659  $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'),
660  1476048371
661  );
662  }
663  $workspaceStage = BackendUtility::getRecord(self::TABLE_STAGE, $stageId);
664  if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
665  $result = $workspaceStage[$property];
666  }
667  return $result;
668  }
669 
677  public function getPositionOfCurrentStage($stageId)
678  {
679  $stagesOfWS = $this->getStagesForWS();
680  $countOfStages = count($stagesOfWS);
681  switch ($stageId) {
682  case self::STAGE_PUBLISH_ID:
683  $position = $countOfStages;
684  break;
685  case self::STAGE_EDIT_ID:
686  $position = 1;
687  break;
688  default:
689  $position = 1;
690  foreach ($stagesOfWS as $key => $stageInfoArray) {
691  $position++;
692  if ($stageId == $stageInfoArray['uid']) {
693  break;
694  }
695  }
696  }
697  return ['position' => $position, 'count' => $countOfStages];
698  }
699 
706  public function isPrevStageAllowedForUser($stageId)
707  {
708  $isAllowed = false;
709  try {
710  $prevStage = $this->getPrevStage($stageId);
711  // if there's no prev-stage the stageIds match,
712  // otherwise we've to check if the user is permitted to use the stage
713  if (!empty($prevStage) && $prevStage['uid'] != $stageId) {
714  // if the current stage is allowed for the user, the user is also allowed to send to prev
715  $isAllowed = $this->isStageAllowedForUser($stageId);
716  }
717  } catch (\Exception $e) {
718  }
719  return $isAllowed;
720  }
721 
728  public function isNextStageAllowedForUser($stageId)
729  {
730  $isAllowed = false;
731  try {
732  $nextStage = $this->getNextStage($stageId);
733  // if there's no next-stage the stageIds match,
734  // otherwise we've to check if the user is permitted to use the stage
735  if (!empty($nextStage) && $nextStage['uid'] != $stageId) {
736  // if the current stage is allowed for the user, the user is also allowed to send to next
737  $isAllowed = $this->isStageAllowedForUser($stageId);
738  }
739  } catch (\Exception $e) {
740  }
741  return $isAllowed;
742  }
743 
748  protected function isStageAllowedForUser($stageId)
749  {
750  $cacheKey = $this->getWorkspaceId() . '_' . $stageId;
751  $isAllowed = false;
752  if (isset($this->workspaceStageAllowedCache[$cacheKey])) {
753  $isAllowed = $this->workspaceStageAllowedCache[$cacheKey];
754  } else {
755  $isAllowed = $GLOBALS['BE_USER']->workspaceCheckStageForCurrent($stageId);
756  $this->workspaceStageAllowedCache[$cacheKey] = $isAllowed;
757  }
758  return $isAllowed;
759  }
760 
767  public function isValid($stageId)
768  {
769  $isValid = false;
770  $stages = $this->getStagesForWS();
771  foreach ($stages as $stage) {
772  if ($stage['uid'] == $stageId) {
773  $isValid = true;
774  break;
775  }
776  }
777  return $isValid;
778  }
779 
783  public function getRecordService()
784  {
785  if (!isset($this->recordService)) {
786  $this->recordService = GeneralUtility::makeInstance(RecordService::class);
787  }
788  return $this->recordService;
789  }
790 
794  protected function getBackendUser()
795  {
796  return $GLOBALS['BE_USER'];
797  }
798 }
getPropertyOfCurrentWorkspaceStage($stageId, $property)
getNextStages(array &$nextStageArray, $stageId)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
getPreviousStageForElementCollection($workspaceItems, array $byTableName=['tt_content', 'pages', 'pages_language_overlay'])
getPrevStages(array &$prevStageArray, $stageId)
getResponsibleBeUser($stageRecord, $selectDefaultUserField=false)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
getPreselectedRecipients(StageRecord $stageRecord)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
static getUserNames($fields= 'username, usergroup, usergroup_cached_list, uid', $where= '')
getNextStageForElementCollection($workspaceItems, array $byTableName=['tt_content', 'pages', 'pages_language_overlay'])
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)