‪TYPO3CMS  ‪main
StagesService.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
28 
30 {
31  public const ‪TABLE_STAGE = 'sys_workspace_stage';
32  // 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  public const ‪STAGE_PUBLISH_EXECUTE_ID = -20;
34  // ready to publish stage
35  public const ‪STAGE_PUBLISH_ID = -10;
36  public const ‪STAGE_EDIT_ID = 0;
37 
38  private string ‪$pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf';
39 
41 
45  protected array ‪$workspaceStageCache = [];
46  protected array ‪$workspaceStageAllowedCache = [];
47 
51  public function ‪getWorkspaceId(): int
52  {
53  return $this->‪getBackendUser()->workspace;
54  }
55 
62  array $workspaceItems,
63  array $byTableName = ['tt_content', 'pages']
64  ): array {
65  $currentStage = [];
66  $previousStage = [];
67  $usedStages = [];
68  $found = false;
69  $availableStagesForWS = array_reverse($this->‪getAllStagesOfWorkspace());
70  $availableStagesForWSUser = $this->‪getStagesForWSUser();
71  $byTableName = array_flip($byTableName);
72  foreach ($workspaceItems as $tableName => $items) {
73  if (!array_key_exists($tableName, $byTableName)) {
74  continue;
75  }
76  foreach ($items as $item) {
77  $usedStages[$item['t3ver_stage'] ?? 0] = true;
78  }
79  }
80  foreach ($availableStagesForWS as $stage) {
81  if (isset($usedStages[$stage['uid']])) {
82  $currentStage = $stage;
83  $previousStage = $this->‪getPrevStage($stage['uid']);
84  break;
85  }
86  }
87  foreach ($availableStagesForWSUser as $userWS) {
88  if ($previousStage && $previousStage['uid'] == $userWS['uid']) {
89  $found = true;
90  break;
91  }
92  }
93  if ($found === false || !$this->‪isStageAllowedForUser($currentStage['uid'])) {
94  $previousStage = [];
95  }
96  return [
97  $currentStage,
98  $previousStage,
99  ];
100  }
101 
108  array $workspaceItems,
109  array $byTableName = ['tt_content', 'pages']
110  ): array {
111  $currentStage = [];
112  $usedStages = [];
113  $nextStage = [];
114  $availableStagesForWS = $this->‪getAllStagesOfWorkspace();
115  $availableStagesForWSUser = $this->‪getStagesForWSUser();
116  $byTableName = array_flip($byTableName);
117  $found = false;
118  foreach ($workspaceItems as $tableName => $items) {
119  if (!array_key_exists($tableName, $byTableName)) {
120  continue;
121  }
122  foreach ($items as $item) {
123  $usedStages[$item['t3ver_stage'] ?? 0] = true;
124  }
125  }
126  foreach ($availableStagesForWS as $stage) {
127  if (isset($usedStages[$stage['uid']])) {
128  $currentStage = $stage;
129  $nextStage = $this->‪getNextStage($stage['uid']);
130  break;
131  }
132  }
133  foreach ($availableStagesForWSUser as $userWS) {
134  if ($nextStage && $nextStage['uid'] == $userWS['uid']) {
135  $found = true;
136  break;
137  }
138  }
139  if ($found === false || !$this->‪isStageAllowedForUser($currentStage['uid'])) {
140  $nextStage = [];
141  }
142  return [
143  $currentStage,
144  $nextStage,
145  ];
146  }
147 
153  public function ‪getAllStagesOfWorkspace(): array
154  {
155  if (isset($this->workspaceStageCache[$this->‪getWorkspaceId()])) {
156  $stages = $this->workspaceStageCache[$this->‪getWorkspaceId()];
157  } elseif ($this->‪getWorkspaceId() === 0) {
158  $stages = [];
159  } else {
160  $stages = $this->‪prepareStagesArray($this->‪getWorkspaceRecord()->getStages());
161  $this->workspaceStageCache[$this->‪getWorkspaceId()] = $stages;
162  }
163  return $stages;
164  }
165 
171  public function ‪getStagesForWSUser(): array
172  {
173  if ($this->‪getBackendUser()->isAdmin()) {
174  return $this->‪getAllStagesOfWorkspace();
175  }
176  // The LIVE workspace has no stages
177  if ($this->‪getWorkspaceId() === 0) {
178  return [];
179  }
180 
182  $allowedStages = [];
183  $stageRecords = $this->‪getWorkspaceRecord()->getStages();
184 
185  // Only use stages that are allowed for current backend user
186  foreach ($stageRecords as $stageRecord) {
187  if ($stageRecord->isAllowed()) {
188  $allowedStages[$stageRecord->getUid()] = $stageRecord;
189  }
190  }
191 
192  // Add previous and next stages (even if they are not allowed!)
193  foreach ($allowedStages as $allowedStage) {
194  $previousStage = $allowedStage->getPrevious();
195  $nextStage = $allowedStage->getNext();
196  if ($previousStage !== null && !isset($allowedStages[$previousStage->getUid()])) {
197  $allowedStages[$previousStage->getUid()] = $previousStage;
198  }
199  if ($nextStage !== null && !isset($allowedStages[$nextStage->getUid()])) {
200  $allowedStages[$nextStage->getUid()] = $nextStage;
201  }
202  }
203 
204  uasort($allowedStages, static function (‪StageRecord $first, ‪StageRecord $second) {
205  return $first->‪determineOrder($second);
206  });
207  return $this->‪prepareStagesArray($allowedStages);
208  }
209 
215  protected function ‪prepareStagesArray(array $stageRecords): array
216  {
217  $stagesArray = [];
218  foreach ($stageRecords as $stageRecord) {
219  $stage = [
220  'uid' => $stageRecord->getUid(),
221  'label' => $stageRecord->getTitle(),
222  ];
223  if (!$stageRecord->isExecuteStage()) {
224  $stage['title'] = $this->‪getLanguageService()->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $stageRecord->getTitle() . '"';
225  } else {
226  $stage['title'] = $this->‪getLanguageService()->sL($this->pathToLocallang . ':publish_execute_action_option');
227  }
228  $stagesArray[] = $stage;
229  }
230  return $stagesArray;
231  }
232 
236  public function ‪getStageTitle(int $stageId): string
237  {
238  switch ($stageId) {
240  $stageTitle = $this->‪getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_publish');
241  break;
243  $stageTitle = $this->‪getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_ready_to_publish');
244  break;
246  $stageTitle = $this->‪getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_editing');
247  break;
248  default:
249  $stageTitle = $this->‪getPropertyOfCurrentWorkspaceStage($stageId, 'title');
250  if ($stageTitle === null) {
251  $stageTitle = $this->‪getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.getStageTitle.stageNotFound');
252  }
253  }
254  return $stageTitle;
255  }
256 
263  public function ‪getNextStage(int $stageId): array
264  {
265  $nextStage = false;
266  $workspaceStageRecs = $this->‪getAllStagesOfWorkspace();
267  if ($workspaceStageRecs !== []) {
268  reset($workspaceStageRecs);
269  while (key($workspaceStageRecs) !== null) {
270  $workspaceStageRec = current($workspaceStageRecs);
271  if ($workspaceStageRec['uid'] == $stageId) {
272  $nextStage = next($workspaceStageRecs);
273  break;
274  }
275  next($workspaceStageRecs);
276  }
277  }
278  if ($nextStage === false) {
279  $nextStage = [
280  [
281  'uid' => ‪self::STAGE_EDIT_ID,
282  'title' => $this->‪getLanguageService()->sL($this->pathToLocallang . ':actionSendToStage') . ' "'
283  . $this->‪getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_editing') . '"',
284  ],
285  ];
286  }
287  return $nextStage;
288  }
289 
297  protected function ‪getNextStages(array &$nextStageArray, int $stageId): array
298  {
299  // Current stage is "Ready to publish" - there is no next stage
300  if ($stageId == self::STAGE_PUBLISH_ID) {
301  return $nextStageArray;
302  }
303  $nextStageRecord = $this->‪getNextStage($stageId);
304  if ($nextStageRecord === []) {
305  // There is no next stage
306  return $nextStageArray;
307  }
308  // Check if the user has the permission to for the current stage
309  // If this next stage record is the first next stage after the current the user
310  // has always the needed permission
311  if ($this->‪isStageAllowedForUser($stageId)) {
312  $nextStageArray[] = $nextStageRecord;
313  return $this->‪getNextStages($nextStageArray, (int)$nextStageRecord['uid']);
314  }
315  // He hasn't - return given next stage array
316  return $nextStageArray;
317  }
318 
325  public function ‪getPrevStage(int $stageId): false|array
326  {
327  $prevStage = false;
328  $workspaceStageRecs = $this->‪getAllStagesOfWorkspace();
329  if ($workspaceStageRecs !== []) {
330  end($workspaceStageRecs);
331  while (key($workspaceStageRecs) !== null) {
332  $workspaceStageRec = current($workspaceStageRecs);
333  if ($workspaceStageRec['uid'] == $stageId) {
334  $prevStage = prev($workspaceStageRecs);
335  break;
336  }
337  prev($workspaceStageRecs);
338  }
339  }
340 
341  return $prevStage;
342  }
343 
351  protected function ‪getPrevStages(array &$prevStageArray, int $stageId): array
352  {
353  // Current stage is "Editing" - there is no prev stage
354  if ($stageId === self::STAGE_EDIT_ID) {
355  return $prevStageArray;
356  }
357  $prevStageRecord = $this->‪getPrevStage($stageId);
358  if (!empty($prevStageRecord) && is_array($prevStageRecord)) {
359  // Check if the user has the permission to switch to that stage
360  // If this prev stage record is the first previous stage before the current
361  // the user has always the needed permission
362  if ($this->‪isStageAllowedForUser($stageId)) {
363  $prevStageArray[] = $prevStageRecord;
364  $prevStageArray = $this->‪getPrevStages($prevStageArray, $prevStageRecord['uid']);
365  }
366  }
367  return $prevStageArray;
368  }
369 
377  public function ‪getResponsibleBeUser(‪StageRecord|int $stageRecord, bool $selectDefaultUserField = false): array
378  {
379  if (!$stageRecord instanceof ‪StageRecord) {
380  $stageRecord = $this->‪getWorkspaceRecord()->getStage($stageRecord);
381  }
382 
383  if (!$selectDefaultUserField) {
384  $backendUserIds = $stageRecord->getAllRecipients();
385  } else {
386  $backendUserIds = $stageRecord->getDefaultRecipients();
387  }
388 
389  $userRecords = $this->‪getBackendUsers($backendUserIds);
390  $recipientArray = [];
391  foreach ($userRecords as $userUid => $userRecord) {
392  $recipientArray[$userUid] = $userRecord;
393  }
394  return $recipientArray;
395  }
396 
401  public function ‪resolveBackendUserIds(string $backendUserGroupList): array
402  {
403  $elements = GeneralUtility::trimExplode(',', $backendUserGroupList, true);
404  $backendUserIds = [];
405  $backendGroupIds = [];
406 
407  foreach ($elements as $element) {
408  if (str_starts_with($element, 'be_users_')) {
409  // Current value is a uid of a be_user record
410  $backendUserIds[] = str_replace('be_users_', '', $element);
411  } elseif (str_starts_with($element, 'be_groups_')) {
412  $backendGroupIds[] = (int)str_replace('be_groups_', '', $element);
413  } elseif ((int)$element) {
414  $backendUserIds[] = (int)$element;
415  }
416  }
417 
418  if (!empty($backendGroupIds)) {
419  $groupResolver = GeneralUtility::makeInstance(GroupResolver::class);
420  $backendUsersInGroups = $groupResolver->findAllUsersInGroups($backendGroupIds, 'be_groups', 'be_users');
421  foreach ($backendUsersInGroups as $backendUsers) {
422  $backendUserIds[] = (int)$backendUsers['uid'];
423  }
424  }
425 
426  return array_unique($backendUserIds);
427  }
428 
432  public function ‪getBackendUsers(array $backendUserIds): array
433  {
434  if ($backendUserIds === []) {
435  return [];
436  }
437  $backendUserList = implode(',', GeneralUtility::intExplode(',', implode(',', $backendUserIds)));
438  return BackendUtility::getUserNames(
439  'username, uid, email, realName, lang, uc',
440  'AND uid IN (' . $backendUserList . ')' . BackendUtility::BEenableFields('be_users')
441  );
442  }
443 
444  public function ‪getPreselectedRecipients(‪StageRecord $stageRecord): array
445  {
446  if ($stageRecord->‪areEditorsPreselected()) {
447  return array_merge(
448  $stageRecord->‪getPreselectedRecipients(),
449  $this->getRecordService()->getCreateUserIds()
450  );
451  }
452  return $stageRecord->‪getPreselectedRecipients();
453  }
454 
456  {
458  }
459 
463  public function ‪getPropertyOfCurrentWorkspaceStage(int $stageId, string $property): ?string
464  {
465  $result = null;
466  $workspaceStage = BackendUtility::getRecord(self::TABLE_STAGE, $stageId);
467  if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
468  $result = $workspaceStage[$property];
469  }
470  return $result;
471  }
472 
479  public function ‪getPositionOfCurrentStage(int $stageId): array
480  {
481  $stagesOfWS = $this->‪getAllStagesOfWorkspace();
482  $countOfStages = count($stagesOfWS);
483  switch ($stageId) {
485  $position = $countOfStages;
486  break;
488  $position = 1;
489  break;
490  default:
491  $position = 1;
492  foreach ($stagesOfWS as $stageInfoArray) {
493  $position++;
494  if ($stageId === (int)$stageInfoArray['uid']) {
495  break;
496  }
497  }
498  }
499  return ['position' => $position, 'count' => $countOfStages];
500  }
501 
505  public function ‪isPrevStageAllowedForUser(int $stageId): bool
506  {
507  $isAllowed = false;
508  try {
509  $prevStage = $this->‪getPrevStage($stageId);
510  // if there's no prev-stage the stageIds match,
511  // otherwise we've to check if the user is permitted to use the stage
512  if (!empty($prevStage) && $prevStage['uid'] != $stageId) {
513  // if the current stage is allowed for the user, the user is also allowed to send to prev
514  $isAllowed = $this->‪isStageAllowedForUser($stageId);
515  }
516  } catch (\Exception $e) {
517  }
518  return $isAllowed;
519  }
520 
524  public function ‪isNextStageAllowedForUser(int $stageId): bool
525  {
526  $isAllowed = false;
527  try {
528  $nextStage = $this->‪getNextStage($stageId);
529  // if there's no next-stage the stageIds match,
530  // otherwise we've to check if the user is permitted to use the stage
531  if (!empty($nextStage) && $nextStage['uid'] != $stageId) {
532  // if the current stage is allowed for the user, the user is also allowed to send to next
533  $isAllowed = $this->‪isStageAllowedForUser($stageId);
534  }
535  } catch (\Exception $e) {
536  }
537  return $isAllowed;
538  }
539 
540  protected function ‪isStageAllowedForUser(int $stageId): bool
541  {
542  $cacheKey = $this->‪getWorkspaceId() . '_' . $stageId;
543  if (isset($this->workspaceStageAllowedCache[$cacheKey])) {
544  return $this->workspaceStageAllowedCache[$cacheKey];
545  }
546  $isAllowed = $this->‪getBackendUser()->workspaceCheckStageForCurrent($stageId);
547  $this->workspaceStageAllowedCache[$cacheKey] = $isAllowed;
548  return $isAllowed;
549  }
550 
554  public function ‪isValid(int $stageId): bool
555  {
556  $isValid = false;
557  $stages = $this->‪getAllStagesOfWorkspace();
558  foreach ($stages as $stage) {
559  if ($stage['uid'] == $stageId) {
560  $isValid = true;
561  break;
562  }
563  }
564  return $isValid;
565  }
566 
568  {
569  if (!isset($this->recordService)) {
570  $this->recordService = GeneralUtility::makeInstance(RecordService::class);
571  }
573  }
574 
576  {
577  return ‪$GLOBALS['BE_USER'];
578  }
579 
581  {
582  return ‪$GLOBALS['LANG'] ?? null;
583  }
584 }
‪TYPO3\CMS\Workspaces\Service\StagesService\getPositionOfCurrentStage
‪array getPositionOfCurrentStage(int $stageId)
Definition: StagesService.php:479
‪TYPO3\CMS\Workspaces\Service\StagesService\getWorkspaceRecord
‪getWorkspaceRecord()
Definition: StagesService.php:455
‪TYPO3\CMS\Workspaces\Service\StagesService\$workspaceStageAllowedCache
‪array $workspaceStageAllowedCache
Definition: StagesService.php:46
‪TYPO3\CMS\Workspaces\Service
‪TYPO3\CMS\Workspaces\Service\StagesService\isStageAllowedForUser
‪isStageAllowedForUser(int $stageId)
Definition: StagesService.php:540
‪TYPO3\CMS\Workspaces\Domain\Record\StageRecord\determineOrder
‪determineOrder(StageRecord $stageRecord)
Definition: StageRecord.php:70
‪TYPO3\CMS\Workspaces\Service\StagesService\getPropertyOfCurrentWorkspaceStage
‪getPropertyOfCurrentWorkspaceStage(int $stageId, string $property)
Definition: StagesService.php:463
‪TYPO3\CMS\Workspaces\Service\StagesService\getRecordService
‪getRecordService()
Definition: StagesService.php:567
‪TYPO3\CMS\Workspaces\Service\StagesService\getPrevStage
‪false array getPrevStage(int $stageId)
Definition: StagesService.php:325
‪TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord
Definition: WorkspaceRecord.php:29
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_EXECUTE_ID
‪const STAGE_PUBLISH_EXECUTE_ID
Definition: StagesService.php:33
‪TYPO3\CMS\Workspaces\Domain\Record\StageRecord\getPreselectedRecipients
‪int[] getPreselectedRecipients()
Definition: StageRecord.php:240
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_PUBLISH_ID
‪const STAGE_PUBLISH_ID
Definition: StagesService.php:35
‪TYPO3\CMS\Workspaces\Service\StagesService\isNextStageAllowedForUser
‪isNextStageAllowedForUser(int $stageId)
Definition: StagesService.php:524
‪TYPO3\CMS\Workspaces\Service\StagesService\getAllStagesOfWorkspace
‪array getAllStagesOfWorkspace()
Definition: StagesService.php:153
‪TYPO3\CMS\Workspaces\Service\StagesService\getBackendUser
‪getBackendUser()
Definition: StagesService.php:575
‪TYPO3\CMS\Workspaces\Service\StagesService\getStagesForWSUser
‪array getStagesForWSUser()
Definition: StagesService.php:171
‪TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord\get
‪static get(int $uid, array $record=null)
Definition: WorkspaceRecord.php:59
‪TYPO3\CMS\Workspaces\Domain\Record\StageRecord
Definition: StageRecord.php:25
‪TYPO3\CMS\Workspaces\Service\StagesService\resolveBackendUserIds
‪resolveBackendUserIds(string $backendUserGroupList)
Definition: StagesService.php:401
‪TYPO3\CMS\Workspaces\Service\StagesService\isValid
‪isValid(int $stageId)
Definition: StagesService.php:554
‪TYPO3\CMS\Workspaces\Service\StagesService\getPreviousStageForElementCollection
‪array getPreviousStageForElementCollection(array $workspaceItems, array $byTableName=['tt_content', 'pages'])
Definition: StagesService.php:61
‪TYPO3\CMS\Workspaces\Service\StagesService\getPreselectedRecipients
‪getPreselectedRecipients(StageRecord $stageRecord)
Definition: StagesService.php:444
‪TYPO3\CMS\Workspaces\Service\StagesService\isPrevStageAllowedForUser
‪isPrevStageAllowedForUser(int $stageId)
Definition: StagesService.php:505
‪TYPO3\CMS\Workspaces\Service\StagesService\$recordService
‪RecordService $recordService
Definition: StagesService.php:40
‪TYPO3\CMS\Workspaces\Service\StagesService\getPrevStages
‪array getPrevStages(array &$prevStageArray, int $stageId)
Definition: StagesService.php:351
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:60
‪TYPO3\CMS\Workspaces\Service\StagesService\$pathToLocallang
‪string $pathToLocallang
Definition: StagesService.php:38
‪TYPO3\CMS\Workspaces\Service\StagesService\getLanguageService
‪getLanguageService()
Definition: StagesService.php:580
‪TYPO3\CMS\Workspaces\Domain\Record\StageRecord\areEditorsPreselected
‪areEditorsPreselected()
Definition: StageRecord.php:168
‪TYPO3\CMS\Core\Authentication\GroupResolver
Definition: GroupResolver.php:36
‪TYPO3\CMS\Workspaces\Service\StagesService\prepareStagesArray
‪prepareStagesArray(array $stageRecords)
Definition: StagesService.php:215
‪TYPO3\CMS\Workspaces\Service\StagesService\STAGE_EDIT_ID
‪const STAGE_EDIT_ID
Definition: StagesService.php:36
‪TYPO3\CMS\Workspaces\Service\StagesService
Definition: StagesService.php:30
‪TYPO3\CMS\Workspaces\Service\StagesService\getNextStage
‪array getNextStage(int $stageId)
Definition: StagesService.php:263
‪TYPO3\CMS\Workspaces\Service\StagesService\$workspaceStageCache
‪array $workspaceStageCache
Definition: StagesService.php:45
‪TYPO3\CMS\Workspaces\Service\StagesService\getNextStages
‪array getNextStages(array &$nextStageArray, int $stageId)
Definition: StagesService.php:297
‪TYPO3\CMS\Workspaces\Service\StagesService\getResponsibleBeUser
‪array getResponsibleBeUser(StageRecord|int $stageRecord, bool $selectDefaultUserField=false)
Definition: StagesService.php:377
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Workspaces\Service\StagesService\getWorkspaceId
‪getWorkspaceId()
Definition: StagesService.php:51
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Workspaces\Service\StagesService\getStageTitle
‪getStageTitle(int $stageId)
Definition: StagesService.php:236
‪TYPO3\CMS\Workspaces\Service\StagesService\TABLE_STAGE
‪const TABLE_STAGE
Definition: StagesService.php:31
‪TYPO3\CMS\Workspaces\Service\RecordService
Definition: RecordService.php:26
‪TYPO3\CMS\Workspaces\Service\StagesService\getNextStageForElementCollection
‪array getNextStageForElementCollection(array $workspaceItems, array $byTableName=['tt_content', 'pages'])
Definition: StagesService.php:107
‪TYPO3\CMS\Workspaces\Service\StagesService\getBackendUsers
‪getBackendUsers(array $backendUserIds)
Definition: StagesService.php:432