[FEATURE] Add additional configuration for external URLs
[Packages/TYPO3.CMS.git] / typo3 / sysext / workspaces / Classes / Service / StagesService.php
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
17 use TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
19 use TYPO3\CMS\Core\Database\Connection;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Localization\LanguageService;
22 use TYPO3\CMS\Core\SingletonInterface;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25 use TYPO3\CMS\Workspaces\Domain\Record\StageRecord;
26 use TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord;
27
28 /**
29 * Stages service
30 */
31 class StagesService implements SingletonInterface
32 {
33 const TABLE_STAGE = 'sys_workspace_stage';
34 // 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
35 const STAGE_PUBLISH_EXECUTE_ID = -20;
36 // ready to publish stage
37 const STAGE_PUBLISH_ID = -10;
38 const STAGE_EDIT_ID = 0;
39 const MODE_NOTIFY_SOMEONE = 0;
40 const MODE_NOTIFY_ALL = 1;
41 const MODE_NOTIFY_ALL_STRICT = 2;
42
43 /**
44 * Path to the locallang file
45 *
46 * @var string
47 */
48 private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf';
49
50 /**
51 * @var RecordService
52 */
53 protected $recordService;
54
55 /**
56 * Local cache to reduce number of database queries for stages, groups, etc.
57 *
58 * @var array
59 */
60 protected $workspaceStageCache = [];
61
62 /**
63 * @var array
64 */
65 protected $workspaceStageAllowedCache = [];
66
67 /**
68 * @var array
69 */
70 protected $fetchGroupsCache = [];
71
72 /**
73 * @var array
74 */
75 protected $userGroups = [];
76
77 /**
78 * Getter for current workspace id
79 *
80 * @return int Current workspace id
81 */
82 public function getWorkspaceId()
83 {
84 return $this->getBackendUser()->workspace;
85 }
86
87 /**
88 * Find the highest possible "previous" stage for all $byTableName
89 *
90 * @param array $workspaceItems
91 * @param array $byTableName
92 * @return array Current and next highest possible stage
93 */
94 public function getPreviousStageForElementCollection(
95 $workspaceItems,
96 array $byTableName = ['tt_content', 'pages']
97 ) {
98 $currentStage = [];
99 $previousStage = [];
100 $usedStages = [];
101 $found = false;
102 $availableStagesForWS = array_reverse($this->getStagesForWS());
103 $availableStagesForWSUser = $this->getStagesForWSUser();
104 $byTableName = array_flip($byTableName);
105 foreach ($workspaceItems as $tableName => $items) {
106 if (!array_key_exists($tableName, $byTableName)) {
107 continue;
108 }
109 foreach ($items as $item) {
110 $usedStages[$item['t3ver_stage']] = true;
111 }
112 }
113 foreach ($availableStagesForWS as $stage) {
114 if (isset($usedStages[$stage['uid']])) {
115 $currentStage = $stage;
116 $previousStage = $this->getPrevStage($stage['uid']);
117 break;
118 }
119 }
120 foreach ($availableStagesForWSUser as $userWS) {
121 if ($previousStage['uid'] == $userWS['uid']) {
122 $found = true;
123 break;
124 }
125 }
126 if ($found === false || !$this->isStageAllowedForUser($currentStage['uid'])) {
127 $previousStage = [];
128 }
129 return [
130 $currentStage,
131 $previousStage
132 ];
133 }
134
135 /**
136 * Retrieve the next stage based on the lowest stage given in the $workspaceItems record array.
137 *
138 * @param array $workspaceItems
139 * @param array $byTableName
140 * @return array Current and next possible stage.
141 */
142 public function getNextStageForElementCollection(
143 $workspaceItems,
144 array $byTableName = ['tt_content', 'pages']
145 ) {
146 $currentStage = [];
147 $usedStages = [];
148 $nextStage = [];
149 $availableStagesForWS = $this->getStagesForWS();
150 $availableStagesForWSUser = $this->getStagesForWSUser();
151 $byTableName = array_flip($byTableName);
152 $found = false;
153 foreach ($workspaceItems as $tableName => $items) {
154 if (!array_key_exists($tableName, $byTableName)) {
155 continue;
156 }
157 foreach ($items as $item) {
158 $usedStages[$item['t3ver_stage']] = true;
159 }
160 }
161 foreach ($availableStagesForWS as $stage) {
162 if (isset($usedStages[$stage['uid']])) {
163 $currentStage = $stage;
164 $nextStage = $this->getNextStage($stage['uid']);
165 break;
166 }
167 }
168 foreach ($availableStagesForWSUser as $userWS) {
169 if ($nextStage['uid'] == $userWS['uid']) {
170 $found = true;
171 break;
172 }
173 }
174 if ($found === false || !$this->isStageAllowedForUser($currentStage['uid'])) {
175 $nextStage = [];
176 }
177 return [
178 $currentStage,
179 $nextStage
180 ];
181 }
182
183 /**
184 * Building an array with all stage ids and titles related to the given workspace
185 *
186 * @return array id and title of the stages
187 */
188 public function getStagesForWS()
189 {
190 if (isset($this->workspaceStageCache[$this->getWorkspaceId()])) {
191 $stages = $this->workspaceStageCache[$this->getWorkspaceId()];
192 } elseif ($this->getWorkspaceId() === 0) {
193 $stages = [];
194 } else {
195 $stages = $this->prepareStagesArray($this->getWorkspaceRecord()->getStages());
196 $this->workspaceStageCache[$this->getWorkspaceId()] = $stages;
197 }
198 return $stages;
199 }
200
201 /**
202 * Returns an array of stages, the user is allowed to send to
203 *
204 * @return array id and title of stages
205 */
206 public function getStagesForWSUser()
207 {
208 if ($this->getBackendUser()->isAdmin()) {
209 return $this->getStagesForWS();
210 }
211
212 /** @var StageRecord[] $allowedStages */
213 $allowedStages = [];
214 $stageRecords = $this->getWorkspaceRecord()->getStages();
215
216 // Only use stages that are allowed for current backend user
217 foreach ($stageRecords as $stageRecord) {
218 if ($stageRecord->isAllowed()) {
219 $allowedStages[$stageRecord->getUid()] = $stageRecord;
220 }
221 }
222
223 // Add previous and next stages (even if they are not allowed!)
224 foreach ($allowedStages as $allowedStage) {
225 $previousStage = $allowedStage->getPrevious();
226 $nextStage = $allowedStage->getNext();
227 if ($previousStage !== null && !isset($allowedStages[$previousStage->getUid()])) {
228 $allowedStages[$previousStage->getUid()] = $previousStage;
229 }
230 if ($nextStage !== null && !isset($allowedStages[$nextStage->getUid()])) {
231 $allowedStages[$nextStage->getUid()] = $nextStage;
232 }
233 }
234
235 uasort($allowedStages, function (StageRecord $first, StageRecord $second) {
236 return $first->determineOrder($second);
237 });
238 return $this->prepareStagesArray($allowedStages);
239 }
240
241 /**
242 * Prepares simplified stages array
243 *
244 * @param StageRecord[] $stageRecords
245 * @return array
246 */
247 protected function prepareStagesArray(array $stageRecords)
248 {
249 $stagesArray = [];
250 foreach ($stageRecords as $stageRecord) {
251 $stage = [
252 'uid' => $stageRecord->getUid(),
253 'label' => $stageRecord->getTitle(),
254 ];
255 if (!$stageRecord->isExecuteStage()) {
256 $stage['title'] = $this->getLanguageService()->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $stageRecord->getTitle() . '"';
257 } else {
258 $stage['title'] = $this->getLanguageService()->sL($this->pathToLocallang . ':publish_execute_action_option');
259 }
260 $stagesArray[] = $stage;
261 }
262 return $stagesArray;
263 }
264
265 /**
266 * Gets the title of a stage.
267 *
268 * @param int $ver_stage
269 * @return string
270 */
271 public function getStageTitle($ver_stage)
272 {
273 switch ($ver_stage) {
274 case self::STAGE_PUBLISH_EXECUTE_ID:
275 $stageTitle = $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod_user_ws.xlf:stage_publish');
276 break;
277 case self::STAGE_PUBLISH_ID:
278 $stageTitle = $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_ready_to_publish');
279 break;
280 case self::STAGE_EDIT_ID:
281 $stageTitle = $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod_user_ws.xlf:stage_editing');
282 break;
283 default:
284 $stageTitle = $this->getPropertyOfCurrentWorkspaceStage($ver_stage, 'title');
285 if ($stageTitle == null) {
286 $stageTitle = $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.getStageTitle.stageNotFound');
287 }
288 }
289 return $stageTitle;
290 }
291
292 /**
293 * Gets a particular stage record.
294 *
295 * @param int $stageid
296 * @return array
297 */
298 public function getStageRecord($stageid)
299 {
300 return BackendUtility::getRecord('sys_workspace_stage', $stageid);
301 }
302
303 /**
304 * Gets next stage in process for given stage id
305 *
306 * @param int $stageId Id of the stage to fetch the next one for
307 * @return array The next stage (id + details)
308 * @throws \InvalidArgumentException
309 */
310 public function getNextStage($stageId)
311 {
312 if (!MathUtility::canBeInterpretedAsInteger($stageId)) {
313 throw new \InvalidArgumentException(
314 $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'),
315 1291109987
316 );
317 }
318 $nextStage = false;
319 $workspaceStageRecs = $this->getStagesForWS();
320 if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
321 reset($workspaceStageRecs);
322 while (key($workspaceStageRecs) !== null) {
323 $workspaceStageRec = current($workspaceStageRecs);
324 if ($workspaceStageRec['uid'] == $stageId) {
325 $nextStage = next($workspaceStageRecs);
326 break;
327 }
328 next($workspaceStageRecs);
329 }
330 }
331 if ($nextStage === false) {
332 $nextStage[] = [
333 'uid' => self::STAGE_EDIT_ID,
334 'title' => $this->getLanguageService()->sL($this->pathToLocallang . ':actionSendToStage') . ' "'
335 . $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod_user_ws.xlf:stage_editing') . '"'
336 ];
337 }
338 return $nextStage;
339 }
340
341 /**
342 * Recursive function to get all next stages for a record depending on user permissions
343 *
344 * @param array $nextStageArray Next stages
345 * @param int $stageId Current stage id of the record
346 * @return array Next stages
347 */
348 public function getNextStages(array &$nextStageArray, $stageId)
349 {
350 // Current stage is "Ready to publish" - there is no next stage
351 if ($stageId == self::STAGE_PUBLISH_ID) {
352 return $nextStageArray;
353 }
354 $nextStageRecord = $this->getNextStage($stageId);
355 if (empty($nextStageRecord) || !is_array($nextStageRecord)) {
356 // There is no next stage
357 return $nextStageArray;
358 }
359 // Check if the user has the permission to for the current stage
360 // If this next stage record is the first next stage after the current the user
361 // has always the needed permission
362 if ($this->isStageAllowedForUser($stageId)) {
363 $nextStageArray[] = $nextStageRecord;
364 return $this->getNextStages($nextStageArray, $nextStageRecord['uid']);
365 }
366 // He hasn't - return given next stage array
367 return $nextStageArray;
368 }
369
370 /**
371 * Get next stage in process for given stage id
372 *
373 * @param int $stageId Id of the stage to fetch the previous one for
374 * @return bool|array The previous stage or false
375 * @throws \InvalidArgumentException
376 */
377 public function getPrevStage($stageId)
378 {
379 if (!MathUtility::canBeInterpretedAsInteger($stageId)) {
380 throw new \InvalidArgumentException(
381 $this->getLanguageService()->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 (key($workspaceStageRecs) !== null) {
390 $workspaceStageRec = current($workspaceStageRecs);
391 if ($workspaceStageRec['uid'] == $stageId) {
392 $prevStage = prev($workspaceStageRecs);
393 break;
394 }
395 prev($workspaceStageRecs);
396 }
397 }
398
399 return $prevStage;
400 }
401
402 /**
403 * Recursive function to get all prev stages for a record depending on user permissions
404 *
405 * @param array $prevStageArray Prev stages
406 * @param int $stageId Current stage id of the record
407 * @return array prev stages
408 */
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
427 /**
428 * Gets all backend user records that are considered to be responsible
429 * for a particular stage or workspace.
430 *
431 * @param StageRecord|int $stageRecord Stage
432 * @param bool $selectDefaultUserField If field notification_defaults should be selected instead of responsible users
433 * @return array be_users with e-mail and name
434 */
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
457 /**
458 * Gets backend user ids from a mixed list of backend users
459 * and backend users groups. This is used for notifying persons
460 * responsible for a particular stage or workspace.
461 *
462 * @param string $stageRespValue Responsible_person value from stage record
463 * @return string List of backend user ids
464 */
465 public function getResponsibleUser($stageRespValue)
466 {
467 return implode(',', $this->resolveBackendUserIds($stageRespValue));
468 }
469
470 /**
471 * Resolves backend user ids from a mixed list of backend users
472 * and backend user groups (e.g. "be_users_1,be_groups_3,be_users_4,...")
473 *
474 * @param string $backendUserGroupList
475 * @return array
476 */
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
511 /**
512 * Gets backend user records from a given list of ids.
513 *
514 * @param string $backendUserList
515 * @return array
516 */
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, lang, uc',
526 'AND uid IN (' . $backendUserList . ')' . BackendUtility::BEenableFields('be_users')
527 );
528
529 if (empty($backendUsers)) {
530 $backendUsers = [];
531 }
532 return $backendUsers;
533 }
534
535 /**
536 * @param StageRecord $stageRecord
537 * @return array
538 */
539 public function getPreselectedRecipients(StageRecord $stageRecord)
540 {
541 if ($stageRecord->areEditorsPreselected()) {
542 return array_merge(
543 $stageRecord->getPreselectedRecipients(),
544 $this->getRecordService()->getCreateUserIds()
545 );
546 }
547 return $stageRecord->getPreselectedRecipients();
548 }
549
550 /**
551 * @return WorkspaceRecord
552 */
553 protected function getWorkspaceRecord()
554 {
555 return WorkspaceRecord::get($this->getWorkspaceId());
556 }
557
558 /**
559 * @param $grList
560 * @param string $idList
561 * @return array
562 */
563 private function fetchGroups($grList, $idList = '')
564 {
565 $cacheKey = md5($grList . $idList);
566 if (isset($this->fetchGroupsCache[$cacheKey])) {
567 return $this->fetchGroupsCache[$cacheKey];
568 }
569 if ($idList === '') {
570 // we're at the beginning of the recursion and therefore we need to reset the userGroups member
571 $this->userGroups = [];
572 }
573 $groupList = $this->fetchGroupsRecursive($grList);
574 $this->fetchGroupsCache[$cacheKey] = $groupList;
575 return $groupList;
576 }
577
578 /**
579 * @param array $groups
580 */
581 private function fetchGroupsFromDB(array $groups)
582 {
583 // The userGroups array is filled
584 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_groups');
585
586 $result = $queryBuilder
587 ->select('*')
588 ->from('be_groups')
589 ->where(
590 $queryBuilder->expr()->in(
591 'uid',
592 $queryBuilder->createNamedParameter($groups, Connection::PARAM_INT_ARRAY)
593 )
594 )
595 ->execute();
596
597 while ($row = $result->fetch()) {
598 $this->userGroups[$row['uid']] = $row;
599 }
600 }
601
602 /**
603 * Fetches particular groups recursively.
604 *
605 * @param $grList
606 * @param string $idList
607 * @return array
608 */
609 private function fetchGroupsRecursive($grList, $idList = '')
610 {
611 $requiredGroups = GeneralUtility::intExplode(',', $grList, true);
612 $existingGroups = array_keys($this->userGroups);
613 $missingGroups = array_diff($requiredGroups, $existingGroups);
614 if (!empty($missingGroups)) {
615 $this->fetchGroupsFromDB($missingGroups);
616 }
617 // Traversing records in the correct order
618 foreach ($requiredGroups as $uid) {
619 // traversing list
620 // Get row:
621 $row = $this->userGroups[$uid];
622 if (is_array($row) && !GeneralUtility::inList($idList, $uid)) {
623 // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
624 // If the localconf.php option isset the user of the sub- sub- groups will also be used
625 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['customStageShowRecipientRecursive'] == 1) {
626 // Include sub groups
627 if (trim($row['subgroup'])) {
628 // Make integer list
629 $theList = implode(',', GeneralUtility::intExplode(',', $row['subgroup']));
630 // Get the subgroups
631 $subGroups = $this->fetchGroups($theList, $idList . ',' . $uid);
632 // Merge the subgroups to the already existing userGroups array
633 $subUid = key($subGroups);
634 $this->userGroups[$subUid] = $subGroups[$subUid];
635 }
636 }
637 }
638 }
639 return $this->userGroups;
640 }
641
642 /**
643 * Gets a property of a workspaces stage.
644 *
645 * @param int $stageId
646 * @param string $property
647 * @return string
648 * @throws \InvalidArgumentException
649 */
650 public function getPropertyOfCurrentWorkspaceStage($stageId, $property)
651 {
652 $result = null;
653 if (!MathUtility::canBeInterpretedAsInteger($stageId)) {
654 throw new \InvalidArgumentException(
655 $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'),
656 1476048371
657 );
658 }
659 $workspaceStage = BackendUtility::getRecord(self::TABLE_STAGE, $stageId);
660 if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
661 $result = $workspaceStage[$property];
662 }
663 return $result;
664 }
665
666 /**
667 * Gets the position of the given workspace in the hole process
668 * f.e. 3 means step 3 of 20, by which 1 is edit and 20 is ready to publish
669 *
670 * @param int $stageId
671 * @return array position => 3, count => 20
672 */
673 public function getPositionOfCurrentStage($stageId)
674 {
675 $stagesOfWS = $this->getStagesForWS();
676 $countOfStages = count($stagesOfWS);
677 switch ($stageId) {
678 case self::STAGE_PUBLISH_ID:
679 $position = $countOfStages;
680 break;
681 case self::STAGE_EDIT_ID:
682 $position = 1;
683 break;
684 default:
685 $position = 1;
686 foreach ($stagesOfWS as $key => $stageInfoArray) {
687 $position++;
688 if ($stageId == $stageInfoArray['uid']) {
689 break;
690 }
691 }
692 }
693 return ['position' => $position, 'count' => $countOfStages];
694 }
695
696 /**
697 * Check if the user has access to the previous stage, relative to the given stage
698 *
699 * @param int $stageId
700 * @return bool
701 */
702 public function isPrevStageAllowedForUser($stageId)
703 {
704 $isAllowed = false;
705 try {
706 $prevStage = $this->getPrevStage($stageId);
707 // if there's no prev-stage the stageIds match,
708 // otherwise we've to check if the user is permitted to use the stage
709 if (!empty($prevStage) && $prevStage['uid'] != $stageId) {
710 // if the current stage is allowed for the user, the user is also allowed to send to prev
711 $isAllowed = $this->isStageAllowedForUser($stageId);
712 }
713 } catch (\Exception $e) {
714 }
715 return $isAllowed;
716 }
717
718 /**
719 * Check if the user has access to the next stage, relative to the given stage
720 *
721 * @param int $stageId
722 * @return bool
723 */
724 public function isNextStageAllowedForUser($stageId)
725 {
726 $isAllowed = false;
727 try {
728 $nextStage = $this->getNextStage($stageId);
729 // if there's no next-stage the stageIds match,
730 // otherwise we've to check if the user is permitted to use the stage
731 if (!empty($nextStage) && $nextStage['uid'] != $stageId) {
732 // if the current stage is allowed for the user, the user is also allowed to send to next
733 $isAllowed = $this->isStageAllowedForUser($stageId);
734 }
735 } catch (\Exception $e) {
736 }
737 return $isAllowed;
738 }
739
740 /**
741 * @param $stageId
742 * @return bool
743 */
744 protected function isStageAllowedForUser($stageId)
745 {
746 $cacheKey = $this->getWorkspaceId() . '_' . $stageId;
747 if (isset($this->workspaceStageAllowedCache[$cacheKey])) {
748 return $this->workspaceStageAllowedCache[$cacheKey];
749 }
750 $isAllowed = $this->getBackendUser()->workspaceCheckStageForCurrent($stageId);
751 $this->workspaceStageAllowedCache[$cacheKey] = $isAllowed;
752 return $isAllowed;
753 }
754
755 /**
756 * Determines whether a stage Id is valid.
757 *
758 * @param int $stageId The stage Id to be checked
759 * @return bool
760 */
761 public function isValid($stageId)
762 {
763 $isValid = false;
764 $stages = $this->getStagesForWS();
765 foreach ($stages as $stage) {
766 if ($stage['uid'] == $stageId) {
767 $isValid = true;
768 break;
769 }
770 }
771 return $isValid;
772 }
773
774 /**
775 * @return RecordService
776 */
777 public function getRecordService()
778 {
779 if (!isset($this->recordService)) {
780 $this->recordService = GeneralUtility::makeInstance(RecordService::class);
781 }
782 return $this->recordService;
783 }
784
785 /**
786 * @return BackendUserAuthentication
787 */
788 protected function getBackendUser()
789 {
790 return $GLOBALS['BE_USER'];
791 }
792
793 /**
794 * @return LanguageService|null
795 */
796 protected function getLanguageService(): ?LanguageService
797 {
798 return $GLOBALS['LANG'] ?? null;
799 }
800 }