ActionHandler.php 29.7 KB
Newer Older
1
<?php
2

3
4
declare(strict_types=1);

5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
9
10
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
11
 *
12
13
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
16
 * The TYPO3 project - inspiring people to share!
 */
17

18
19
namespace TYPO3\CMS\Workspaces\Controller\Remote;

20
use TYPO3\CMS\Backend\Utility\BackendUtility;
21
use TYPO3\CMS\Backend\View\BackendViewFactory;
22
23
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\DataHandling\DataHandler;
24
use TYPO3\CMS\Core\Http\ServerRequest;
25
use TYPO3\CMS\Core\Localization\LanguageService;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Core\Utility\MathUtility;
28
use TYPO3\CMS\Workspaces\Domain\Record\StageRecord;
29
use TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord;
30
use TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder;
31
use TYPO3\CMS\Workspaces\Service\StagesService;
32
use TYPO3\CMS\Workspaces\Service\WorkspaceService;
33

34
/**
35
 * @internal This is a specific Backend Controller implementation and is not considered part of the Public TYPO3 API.
36
 */
37
class ActionHandler
38
{
39
40
41
42
43
    public function __construct(
        protected readonly StagesService $stagesService,
        protected readonly WorkspaceService $workspaceService,
        protected readonly BackendViewFactory $backendViewFactory,
    ) {
44
45
46
47
48
49
50
51
52
53
    }

    /**
     * Generates a workspace preview link.
     *
     * @param int $uid The ID of the record to be linked
     * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
     */
    public function generateWorkspacePreviewLink($uid)
    {
54
        return GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForPage((int)$uid, 0);
55
56
57
58
59
60
61
62
63
64
    }

    /**
     * Generates workspace preview links for all available languages of a page.
     *
     * @param int $uid
     * @return array
     */
    public function generateWorkspacePreviewLinksForAllLanguages($uid)
    {
65
        return GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUrisForAllLanguagesOfPage((int)$uid);
66
67
68
    }

    /**
69
     * Publishes a single record.
70
71
72
73
     *
     * @param string $table
     * @param int $t3ver_oid
     * @param int $orig_uid
74
     * @todo What about reporting errors back to the interface? /olly/
75
     */
76
    public function publishSingleRecord($table, $t3ver_oid, $orig_uid)
77
    {
78
        $cmd = [];
79
        $cmd[$table][$t3ver_oid]['version'] = [
80
            'action' => 'publish',
81
            'swapWith' => $orig_uid,
82
        ];
83
84
85
86
87
88
89
90
        $this->processTcaCmd($cmd);
    }

    /**
     * Deletes a single record.
     *
     * @param string $table
     * @param int $uid
91
     * @todo What about reporting errors back to the interface? /olly/
92
93
94
     */
    public function deleteSingleRecord($table, $uid)
    {
95
        $cmd = [];
96
        $cmd[$table][$uid]['version'] = [
97
            'action' => 'clearWSID',
98
        ];
99
100
101
102
103
104
105
106
107
108
109
110
        $this->processTcaCmd($cmd);
    }

    /**
     * Generates a view link for a page.
     *
     * @param string $table
     * @param string $uid
     * @return string
     */
    public function viewSingleRecord($table, $uid)
    {
111
        return GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, (int)$uid);
112
113
114
    }

    /**
115
     * Executes an action (publish, discard) to a selection set.
116
117
118
119
120
121
     *
     * @param \stdClass $parameter
     * @return array
     */
    public function executeSelectionAction($parameter)
    {
122
        $result = [];
123
124
125
126
127
128

        if (empty($parameter->action) || empty($parameter->selection)) {
            $result['error'] = 'No action or record selection given';
            return $result;
        }

129
        $commands = [];
130
131
        if ($parameter->action === 'publish') {
            $commands = $this->getPublishCommands($parameter->selection);
132
133
134
135
136
137
138
139
140
141
        } elseif ($parameter->action === 'discard') {
            $commands = $this->getFlushCommands($parameter->selection);
        }

        $result = $this->processTcaCmd($commands);
        $result['total'] = count($commands);
        return $result;
    }

    /**
142
     * Get publish commands
143
144
145
146
     *
     * @param array|\stdClass[] $selection
     * @return array
     */
147
    protected function getPublishCommands(array $selection)
148
    {
149
        $commands = [];
150
        foreach ($selection as $record) {
151
            $commands[$record->table][$record->liveId]['version'] = [
152
                'action' => 'publish',
153
                'swapWith' => $record->versionId,
154
            ];
155
156
157
158
159
160
161
162
163
164
165
166
        }
        return $commands;
    }

    /**
     * Get flush commands
     *
     * @param array|\stdClass[] $selection
     * @return array
     */
    protected function getFlushCommands(array $selection)
    {
167
        $commands = [];
168
        foreach ($selection as $record) {
169
            $commands[$record->table][$record->versionId]['version'] = [
170
                'action' => 'clearWSID',
171
            ];
172
173
174
175
176
177
178
        }
        return $commands;
    }

    /**
     * Saves the selected columns to be shown to the preferences of the current backend user.
     *
179
     * @param array<\stdClass> $model
180
181
182
     */
    public function saveColumnModel($model)
    {
183
        $data = [];
184
        foreach ($model as $column) {
185
            $data[$column->column] = [
186
                'position' => $column->position,
187
                'hidden' => $column->hidden,
188
            ];
189
        }
190
191
        $this->getBackendUser()->uc['moduleData']['Workspaces'][$this->getBackendUser()->workspace]['columns'] = $data;
        $this->getBackendUser()->writeUC();
192
193
194
195
    }

    public function loadColumnModel()
    {
196
197
        if (is_array($this->getBackendUser()->uc['moduleData']['Workspaces'][$this->getBackendUser()->workspace]['columns'])) {
            return $this->getBackendUser()->uc['moduleData']['Workspaces'][$this->getBackendUser()->workspace]['columns'];
198
        }
199
        return [];
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
    }

    /**
     * Gets the dialog window to be displayed before a record can be sent to the next stage.
     *
     * @param int $uid
     * @param string $table
     * @param int $t3ver_oid
     * @return array
     */
    public function sendToNextStageWindow($uid, $table, $t3ver_oid)
    {
        $elementRecord = BackendUtility::getRecord($table, $uid);
        if (is_array($elementRecord)) {
            $workspaceRecord = WorkspaceRecord::get($elementRecord['t3ver_wsid']);
            $nextStageRecord = $workspaceRecord->getNextStage($elementRecord['t3ver_stage']);
            if ($nextStageRecord !== null) {
217
                $this->stagesService->getRecordService()->add($table, $uid);
218
                $result = $this->getSentToStageWindow($nextStageRecord);
219
                $result['affects'] = [
220
221
222
                    'table' => $table,
                    'nextStage' => $nextStageRecord->getUid(),
                    't3ver_oid' => $t3ver_oid,
223
                    'uid' => $uid,
224
                ];
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
            } else {
                $result = $this->getErrorResponse('error.stageId.invalid', 1291111644);
            }
        } else {
            $result = $this->getErrorResponse('error.sendToNextStage.noRecordFound', 1287264776);
        }
        return $result;
    }

    /**
     * Gets the dialog window to be displayed before a record can be sent to the previous stage.
     *
     * @param int $uid
     * @param string $table
     * @return array
     */
    public function sendToPrevStageWindow($uid, $table)
    {
        $elementRecord = BackendUtility::getRecord($table, $uid);
        if (is_array($elementRecord)) {
            $workspaceRecord = WorkspaceRecord::get($elementRecord['t3ver_wsid']);
            $stageRecord = $workspaceRecord->getStage($elementRecord['t3ver_stage']);

            if ($stageRecord !== null) {
                if (!$stageRecord->isEditStage()) {
250
                    $this->stagesService->getRecordService()->add($table, $uid);
251
                    $previousStageRecord = $stageRecord->getPrevious();
252
253
254
                    if ($previousStageRecord === null) {
                        return $this->getErrorResponse('error.sendToPrevStage.noPreviousStage', 1287264747);
                    }
255
                    $result = $this->getSentToStageWindow($previousStageRecord);
256
                    $result['affects'] = [
257
258
                        'table' => $table,
                        'uid' => $uid,
259
                        'nextStage' => $previousStageRecord->getUid(),
260
                    ];
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
                } else {
                    // element is already in edit stage, there is no prev stage - return an error message
                    $result = $this->getErrorResponse('error.sendToPrevStage.noPreviousStage', 1287264746);
                }
            } else {
                $result = $this->getErrorResponse('error.stageId.invalid', 1291111644);
            }
        } else {
            $result = $this->getErrorResponse('error.sendToNextStage.noRecordFound', 1287264765);
        }
        return $result;
    }

    /**
     * Gets the dialog window to be displayed before a record can be sent to a specific stage.
     *
     * @param int $nextStageId
     * @param array|\stdClass[] $elements
     * @return array
     */
    public function sendToSpecificStageWindow($nextStageId, array $elements)
    {
        foreach ($elements as $element) {
284
            $this->stagesService->getRecordService()->add(
285
286
287
288
289
290
                $element->table,
                $element->uid
            );
        }

        $result = $this->getSentToStageWindow($nextStageId);
291
        $result['affects'] = [
292
            'nextStage' => $nextStageId,
293
        ];
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
        return $result;
    }

    /**
     * Gets a merged variant of recipient defined by uid and custom ones.
     *
     * @param array $uidOfRecipients list of recipients
     * @param string $additionalRecipients given user string of additional recipients
     * @param int $stageId stage id
     * @return array
     * @throws \InvalidArgumentException
     */
    public function getRecipientList(array $uidOfRecipients, $additionalRecipients, $stageId)
    {
        $stageRecord = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($stageId);

        if ($stageRecord === null) {
311
            throw new \InvalidArgumentException(
312
                $this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'),
313
314
                1476044776
            );
315
316
        }

317
318
        $recipients = [];
        $finalRecipients = [];
319
320
321
322
323
324
325
326
        $backendUserIds = $stageRecord->getAllRecipients();
        foreach ($uidOfRecipients as $userUid) {
            // Ensure that only configured backend users are considered
            if (!in_array($userUid, $backendUserIds)) {
                continue;
            }
            $beUserRecord = BackendUtility::getRecord('be_users', (int)$userUid);
            if (is_array($beUserRecord) && $beUserRecord['email'] !== '') {
327
                $recipients[$beUserRecord['email']] = [
328
                    'email' => $beUserRecord['email'],
329
                    'lang' => $beUserRecord['lang'],
330
                ];
331
332
333
334
            }
        }

        if ($stageRecord->hasPreselection() && !$stageRecord->isPreselectionChangeable()) {
335
336
            $preselectedBackendUsers = $this->stagesService->getBackendUsers(
                implode(',', $this->stagesService->getPreselectedRecipients($stageRecord))
337
338
339
340
341
342
343
            );

            foreach ($preselectedBackendUsers as $preselectedBackendUser) {
                if (empty($preselectedBackendUser['email']) || !GeneralUtility::validEmail($preselectedBackendUser['email'])) {
                    continue;
                }
                if (!isset($recipients[$preselectedBackendUser['email']])) {
344
                    $uc = (!empty($preselectedBackendUser['uc']) ? unserialize($preselectedBackendUser['uc'], ['allowed_classes' => false]) : []);
345
                    $recipients[$preselectedBackendUser['email']] = [
346
                        'email' => $preselectedBackendUser['email'],
347
                        'lang' => $uc['lang'] ?? $preselectedBackendUser['lang'],
348
                    ];
349
350
351
352
353
354
                }
            }
        }

        if ($additionalRecipients !== '') {
            $emails = GeneralUtility::trimExplode(LF, $additionalRecipients, true);
355
            $additionalRecipients = [];
356
            foreach ($emails as $email) {
357
                $additionalRecipients[$email] = ['email' => $email];
358
359
            }
        } else {
360
            $additionalRecipients = [];
361
362
363
364
365
366
367
        }
        // We merge $recipients on top of $additionalRecipients because $recipients
        // possibly is more complete with a user language. Furthermore, the list of
        // recipients is automatically unique since we indexed $additionalRecipients
        // and $recipients with the email address
        $allRecipients = array_merge($additionalRecipients, $recipients);
        foreach ($allRecipients as $email => $recipientInformation) {
368
            if (GeneralUtility::validEmail((string)$email)) {
369
370
371
372
373
374
375
376
377
378
379
380
381
382
                $finalRecipients[] = $recipientInformation;
            }
        }
        return $finalRecipients;
    }

    /**
     * Discard all items from given page id.
     *
     * @param int $pageId
     * @return array
     */
    public function discardStagesFromPage($pageId)
    {
383
        $cmdMapArray = [];
384
        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
385
            $this->stagesService->getWorkspaceId(),
386
            -99,
387
            $pageId,
388
389
            0,
            'tables_modify'
390
        );
391
392
393
394
395
396
        foreach ($workspaceItemsArray as $tableName => $items) {
            foreach ($items as $item) {
                $cmdMapArray[$tableName][$item['uid']]['version']['action'] = 'clearWSID';
            }
        }
        $this->processTcaCmd($cmdMapArray);
397
        return [
398
            'success' => true,
399
        ];
400
401
402
403
404
405
406
407
408
    }

    /**
     * Push the given element collection to the next workspace stage.
     *
     * <code>
     * $parameters->additional = your@mail.com
     * $parameters->affects->__TABLENAME__
     * $parameters->comments
409
     * $parameters->recipients
410
411
412
     * $parameters->stageId
     * </code>
     *
413
     * @param \stdClass $parameters
414
415
416
417
     * @return array
     */
    public function sentCollectionToStage(\stdClass $parameters)
    {
418
        $cmdMapArray = [];
419
420
        $comment = $parameters->comments;
        $stageId = $parameters->stageId;
421
        if (MathUtility::canBeInterpretedAsInteger($stageId) === false) {
422
423
424
425
426
            throw new \InvalidArgumentException('Missing "stageId" in $parameters array.', 1319488194);
        }
        if (!is_object($parameters->affects) || empty($parameters->affects)) {
            throw new \InvalidArgumentException('Missing "affected items" in $parameters array.', 1319488195);
        }
427
        $recipients = $this->getRecipientList((array)($parameters->recipients ?? []), (string)($parameters->additional ?? ''), $stageId);
428
429
430
431
        foreach ($parameters->affects as $tableName => $items) {
            foreach ($items as $item) {
                // Publishing uses live id in command map
                if ($stageId == StagesService::STAGE_PUBLISH_EXECUTE_ID) {
432
                    $cmdMapArray[$tableName][$item->t3ver_oid]['version']['action'] = 'publish';
433
434
435
436
                    $cmdMapArray[$tableName][$item->t3ver_oid]['version']['swapWith'] = $item->uid;
                    $cmdMapArray[$tableName][$item->t3ver_oid]['version']['comment'] = $comment;
                    $cmdMapArray[$tableName][$item->t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
                } else {
437
                    // Setting stage uses version id in command map
438
439
440
441
442
443
444
445
                    $cmdMapArray[$tableName][$item->uid]['version']['action'] = 'setStage';
                    $cmdMapArray[$tableName][$item->uid]['version']['stageId'] = $stageId;
                    $cmdMapArray[$tableName][$item->uid]['version']['comment'] = $comment;
                    $cmdMapArray[$tableName][$item->uid]['version']['notificationAlternativeRecipients'] = $recipients;
                }
            }
        }
        $this->processTcaCmd($cmdMapArray);
446
        return [
447
448
            'success' => true,
            // force refresh after publishing changes
449
            'refreshLivePanel' => $parameters->stageId == -20,
450
        ];
451
452
453
454
455
456
457
458
459
460
    }

    /**
     * Process TCA command map array.
     *
     * @param array $cmdMapArray
     * @return array
     */
    protected function processTcaCmd(array $cmdMapArray)
    {
461
        $result = [];
462
463
464
465
466
467

        if (empty($cmdMapArray)) {
            $result['error'] = 'No commands given to be processed';
            return $result;
        }

468
        $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
469
        $dataHandler->start([], $cmdMapArray);
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
        $dataHandler->process_cmdmap();

        if ($dataHandler->errorLog) {
            $result['error'] = implode('<br/>', $dataHandler->errorLog);
        }

        return $result;
    }

    /**
     * Gets an object with this structure:
     *
     * affects: object
     * table
     * t3ver_oid
     * nextStage
     * uid
487
     * recipients: array with uids
488
489
490
     * additional: string
     * comments: string
     *
491
     * @param \stdClass $parameters
492
493
494
495
     * @return array
     */
    public function sendToNextStageExecute(\stdClass $parameters)
    {
496
        $cmdArray = [];
497
        $setStageId = (int)$parameters->affects->nextStage;
498
499
500
501
502
        $comments = $parameters->comments;
        $table = $parameters->affects->table;
        $uid = $parameters->affects->uid;
        $t3ver_oid = $parameters->affects->t3ver_oid;

503
        $recipients = $this->getRecipientList((array)($parameters->recipients ?? []), (string)($parameters->additional ?? ''), $setStageId);
504
        if ($setStageId === StagesService::STAGE_PUBLISH_EXECUTE_ID) {
505
            $cmdArray[$table][$t3ver_oid]['version']['action'] = 'publish';
506
507
508
509
510
511
512
513
514
515
            $cmdArray[$table][$t3ver_oid]['version']['swapWith'] = $uid;
            $cmdArray[$table][$t3ver_oid]['version']['comment'] = $comments;
            $cmdArray[$table][$t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
        } else {
            $cmdArray[$table][$uid]['version']['action'] = 'setStage';
            $cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
            $cmdArray[$table][$uid]['version']['comment'] = $comments;
            $cmdArray[$table][$uid]['version']['notificationAlternativeRecipients'] = $recipients;
        }
        $this->processTcaCmd($cmdArray);
516
        $result = [
517
            'success' => true,
518
        ];
519
520
521
522
523
524
525
526
527
528
529

        return $result;
    }

    /**
     * Gets an object with this structure:
     *
     * affects: object
     * table
     * t3ver_oid
     * nextStage
530
     * recipients: array with uids
531
532
533
     * additional: string
     * comments: string
     *
534
     * @param \stdClass $parameters
535
536
537
538
     * @return array
     */
    public function sendToPrevStageExecute(\stdClass $parameters)
    {
539
        $cmdArray = [];
540
541
542
543
544
        $setStageId = $parameters->affects->nextStage;
        $comments = $parameters->comments;
        $table = $parameters->affects->table;
        $uid = $parameters->affects->uid;

545
        $recipients = $this->getRecipientList((array)($parameters->recipients ?? []), (string)($parameters->additional ?? ''), $setStageId);
546
547
548
549
550
        $cmdArray[$table][$uid]['version']['action'] = 'setStage';
        $cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
        $cmdArray[$table][$uid]['version']['comment'] = $comments;
        $cmdArray[$table][$uid]['version']['notificationAlternativeRecipients'] = $recipients;
        $this->processTcaCmd($cmdArray);
551
        $result = [
552
            'success' => true,
553
        ];
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580

        return $result;
    }

    /**
     * Gets an object with this structure:
     *
     * affects: object
     * elements: array
     * 0: object
     * table
     * t3ver_oid
     * uid
     * 1: object
     * table
     * t3ver_oid
     * uid
     * nextStage
     * recipients: array with uids
     * additional: string
     * comments: string
     *
     * @param \stdClass $parameters
     * @return array
     */
    public function sendToSpecificStageExecute(\stdClass $parameters)
    {
581
        $cmdArray = [];
582
        $setStageId = (int)$parameters->affects->nextStage;
583
584
        $comments = $parameters->comments;
        $elements = $parameters->affects->elements;
585
        $recipients = $this->getRecipientList((array)($parameters->recipients ?? []), (string)($parameters->additional ?? ''), $setStageId);
586
587
588
589
590
591
592
        foreach ($elements as $element) {
            // Avoid any action on records that have already been published to live
            $elementRecord = BackendUtility::getRecord($element->table, $element->uid);
            if ((int)$elementRecord['t3ver_wsid'] === 0) {
                continue;
            }

593
            if ($setStageId === StagesService::STAGE_PUBLISH_EXECUTE_ID) {
594
                $cmdArray[$element->table][$element->t3ver_oid]['version']['action'] = 'publish';
595
596
597
598
599
600
601
602
603
604
605
                $cmdArray[$element->table][$element->t3ver_oid]['version']['swapWith'] = $element->uid;
                $cmdArray[$element->table][$element->t3ver_oid]['version']['comment'] = $comments;
                $cmdArray[$element->table][$element->t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
            } else {
                $cmdArray[$element->table][$element->uid]['version']['action'] = 'setStage';
                $cmdArray[$element->table][$element->uid]['version']['stageId'] = $setStageId;
                $cmdArray[$element->table][$element->uid]['version']['comment'] = $comments;
                $cmdArray[$element->table][$element->uid]['version']['notificationAlternativeRecipients'] = $recipients;
            }
        }
        $this->processTcaCmd($cmdArray);
606
        $result = [
607
            'success' => true,
608
        ];
609
610
611
612
613
614
        return $result;
    }

    /**
     * Gets the dialog window to be displayed before a record can be sent to a stage.
     *
615
     * @param StageRecord|int $nextStage
616
617
618
619
620
621
622
623
     * @return array
     */
    protected function getSentToStageWindow($nextStage)
    {
        if (!$nextStage instanceof StageRecord) {
            $nextStage = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($nextStage);
        }

624
        $result = [];
625
        // TODO: $nextStage might be null, error ignored in phpstan.neon
626
        if ($nextStage->isDialogEnabled()) {
627
            $result['sendMailTo'] = $this->getRecipientsOfStage($nextStage);
628
629
            $result['additional'] = [
                'type' => 'textarea',
630
                'value' => '',
631
            ];
632
        }
633
634
        $result['comments'] = [
            'type' => 'textarea',
635
            'value' => $nextStage->isInternal() ? '' : $nextStage->getDefaultComment(),
636
        ];
637
638
639
640
641
642
643
644
645
646

        return $result;
    }

    /**
     * Gets all assigned recipients of a particular stage.
     *
     * @param StageRecord|int $stageRecord
     * @return array
     */
647
    protected function getRecipientsOfStage($stageRecord)
648
649
650
651
652
    {
        if (!$stageRecord instanceof StageRecord) {
            $stageRecord = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($stageRecord);
        }

653
        $result = [];
654
655
        $allRecipients = $this->stagesService->getResponsibleBeUser($stageRecord);
        $preselectedRecipients = $this->stagesService->getPreselectedRecipients($stageRecord);
656
657
658
659
660
661
662
663
664
665
666
        $isPreselectionChangeable = $stageRecord->isPreselectionChangeable();

        foreach ($allRecipients as $backendUserId => $backendUser) {
            if (empty($backendUser['email']) || !GeneralUtility::validEmail($backendUser['email'])) {
                continue;
            }

            $name = (!empty($backendUser['realName']) ? $backendUser['realName'] : $backendUser['username']);
            $checked = in_array($backendUserId, $preselectedRecipients);
            $disabled = ($checked && !$isPreselectionChangeable);

667
            $result[] = [
668
669
670
                'label' => sprintf('%s (%s)', $name, $backendUser['email']),
                'value' => $backendUserId,
                'name' => 'recipients-' . $backendUserId,
671
                'checked' => $checked,
672
                'disabled' => $disabled,
673
            ];
674
675
676
677
678
679
680
681
682
683
684
685
686
        }

        return $result;
    }

    /**
     * Gets the default comment of a particular stage.
     *
     * @param int $stage
     * @return string
     */
    protected function getDefaultCommentOfStage($stage)
    {
687
        $result = $this->stagesService->getPropertyOfCurrentWorkspaceStage($stage, 'default_mailcomment');
688
689
690
691
692
693
694
695
696
697
698
        return $result;
    }

    /**
     * Send all available workspace records to the previous stage.
     *
     * @param int $id Current page id to process items to previous stage.
     * @return array
     */
    public function sendPageToPreviousStage($id)
    {
699
        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
700
            $this->stagesService->getWorkspaceId(),
701
            -99,
702
            $id,
703
704
            0,
            'tables_modify'
705
        );
706
        [$currentStage, $previousStage] = $this->stagesService->getPreviousStageForElementCollection($workspaceItemsArray);
707
        // get only the relevant items for processing
708
        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
709
            $this->stagesService->getWorkspaceId(),
710
711
            $currentStage['uid'],
            $id,
712
713
            0,
            'tables_modify'
714
        );
715
716
        $stageFormFields = $this->getSentToStageWindow($previousStage['uid']);
        $result = array_merge($stageFormFields, [
717
718
719
            'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $previousStage['title'],
            'items' => $this->getSentToStageWindow($previousStage['uid']),
            'affects' => $workspaceItemsArray,
720
            'stageId' => $previousStage['uid'],
721
722
        ]);
        return $result;
723
724
725
726
727
728
729
730
    }

    /**
     * @param int $id Current Page id to select Workspace items from.
     * @return array
     */
    public function sendPageToNextStage($id)
    {
731
        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
732
            $this->stagesService->getWorkspaceId(),
733
            -99,
734
            $id,
735
736
            0,
            'tables_modify'
737
        );
738
        [$currentStage, $nextStage] = $this->stagesService->getNextStageForElementCollection($workspaceItemsArray);
739
        // get only the relevant items for processing
740
        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
741
            $this->stagesService->getWorkspaceId(),
742
743
            $currentStage['uid'],
            $id,
744
745
            0,
            'tables_modify'
746
        );
747
748
        $stageFormFields = $this->getSentToStageWindow($nextStage['uid']);
        $result = array_merge($stageFormFields, [
749
750
            'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $nextStage['title'],
            'affects' => $workspaceItemsArray,
751
            'stageId' => $nextStage['uid'],
752
753
        ]);
        return $result;
754
755
756
    }

    /**
757
758
     * Fetch the current label and visible state of the stage buttons.
     * Used when records have been pushed to different stages in the preview module to update the button phalanx.
759
     */
760
    public function updateStageChangeButtons(int $id): string
761
    {
762
        // Fetch next and previous stage
763
        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
764
            $this->stagesService->getWorkspaceId(),
765
            -99,
766
            $id,
767
768
            0,
            'tables_modify'
769
        );
770
771
772
        [, $nextStage] = $this->stagesService->getNextStageForElementCollection($workspaceItemsArray);
        [, $previousStage] = $this->stagesService->getPreviousStageForElementCollection($workspaceItemsArray);
        // @todo: It would of course be better if AjaxDispatcher could hand over $request to the method ...
773
        $view = $this->backendViewFactory->create(new ServerRequest(), ['typo3/cms-workspaces']);
774
775
776
        $view->assignMultiple([
            'enablePreviousStageButton' => is_array($previousStage) && !empty($previousStage),
            'enableNextStageButton' => is_array($nextStage) && !empty($nextStage),
777
778
779
780
781
            'enableDiscardStageButton' => (is_array($nextStage) && !empty($nextStage)) || (is_array($previousStage) && !empty($previousStage)),
            'nextStage' => $nextStage['title'] ?? '',
            'nextStageId' => $nextStage['uid'] ?? 0,
            'prevStage' => $previousStage['title'] ?? '',
            'prevStageId' => $previousStage['uid'] ?? 0,
782
        ]);
783
        return $view->render('Preview/Ajax/StageButtons');
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
    }

    /**
     * Gets an error response to be shown in the grid component.
     *
     * @param string $errorLabel Name of the label in the locallang.xlf file
     * @param int $errorCode The error code to be used
     * @param bool $successFlagValue Value of the success flag to be delivered back (might be FALSE in most cases)
     * @return array
     */
    protected function getErrorResponse($errorLabel, $errorCode = 0, $successFlagValue = false)
    {
        $localLangFile = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf';
        $response = [
            'error' => [
                'code' => $errorCode,
800
                'message' => $this->getLanguageService()->sL($localLangFile . ':' . $errorLabel),
801
            ],
802
            'success' => $successFlagValue,
803
804
805
806
807
808
809
810
811
812
813
814
815
        ];
        return $response;
    }

    /**
     * Gets the current workspace ID.
     *
     * @return int The current workspace ID
     */
    protected function getCurrentWorkspace()
    {
        return $this->workspaceService->getCurrentWorkspace();
    }
816
817
818
819
820
821
822
823
824
825

    protected function getBackendUser(): BackendUserAuthentication
    {
        return $GLOBALS['BE_USER'];
    }

    protected function getLanguageService(): LanguageService
    {
        return $GLOBALS['LANG'];
    }
826
}