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