Feature: #108762 - Introduce change_stage command for workspaces 

See forge#108762

Description 

A new top-level DataHandler command change_stage has been introduced for changing the stage of workspace records. Like the publish command, it is keyed by the uid of the workspace version, and its parameters are passed flat instead of being nested below an action .

The new command provides a cleaner API compared to the legacy version command with action => 'setStage' .

Example 

Changing the stage of a workspace record now uses a flat command structure:

Changing the stage of a workspace record
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\Utility\GeneralUtility;

// Change the stage of a workspace record using its versioned UID
$versionedUid = 123; // The UID of the workspace version
$commandMap = [
    'tt_content' => [
        $versionedUid => [
            'change_stage' => [
                'stage' => -10, // Target stage ID (e.g., -10 for "Ready to publish")
            ],
        ],
    ],
];

$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start([], $commandMap);
$dataHandler->process_cmdmap();
Copied!

Optional parameters can be passed to the change_stage command:

Changing stage with comment and notification recipients
$commandMap = [
    'tt_content' => [
        $versionedUid => [
            'change_stage' => [
                'stage' => -10,
                'comment' => 'Ready for review',
                'notificationAlternativeRecipients' => [
                    ['email' => 'editor@example.org', 'lang' => 'de'],
                    ['email' => 'reviewer@example.org'],
                ],
            ],
        ],
    ],
];
Copied!

Each recipient is an array with the key email . The keys lang and realName are optional. Notifications are only sent if the option Send notifications upon state changes is enabled in the workspace record. If notificationAlternativeRecipients is missing or empty, nobody is notified. The recipients that are configured for the stage are not used.

Comparison with the legacy command 

The legacy approach required nested parameters with an action key:

Legacy setStage command (still supported)
// Legacy: Nested structure with action parameter
$commandMap = [
    'tt_content' => [
        $versionedUid => [
            'version' => [
                'action' => 'setStage',
                'stageId' => -10,
                'comment' => 'Ready for review',
            ],
        ],
    ],
];
Copied!

The new command simplifies this with a flat structure:

New change_stage command
// New: Flat structure, consistent with publish command
$commandMap = [
    'tt_content' => [
        $versionedUid => [
            'change_stage' => [
                'stage' => -10,
                'comment' => 'Ready for review',
            ],
        ],
    ],
];
Copied!

Impact 

The new change_stage command simplifies the stage change workflow by:

  • Using a flat command structure instead of nested version.action.setStage
  • Providing API consistency with the publish command
  • Keeping the parameter naming simple ( stage instead of stageId )

A change_stage command without a valid stage is rejected and logged, the stage of the record is left untouched.

The legacy version command with action => 'setStage' continues to work for backward compatibility. Dependent records, such as inline children, are staged with the same command the parent record was requested with, so processCmdmap_* hooks always receive the command in the shape it was sent. If a command map contains both commands for the same record, change_stage takes precedence and the legacy command is dropped, even if change_stage is rejected for lacking a valid stage .

The workspaces backend module now sends stage changes as change_stage commands. Extensions implementing processCmdmap_preProcess , processCmdmap or processCmdmap_postProcess hooks that react to the legacy version command with action => 'setStage' must also handle change_stage to keep reacting to stage changes triggered in the backend module.