diff --git a/composer.json b/composer.json
index d987be06496390e7060baa04f93d380dfd851f14..62a4f1b859905a7ff0e6d0599ed9661feb0fe74f 100644
--- a/composer.json
+++ b/composer.json
@@ -77,7 +77,6 @@
 				"typo3/sysext/fluid/Migrations/Code/ClassAliasMap.php",
 				"typo3/sysext/info/Migrations/Code/ClassAliasMap.php",
 				"typo3/sysext/lowlevel/Migrations/Code/ClassAliasMap.php",
-				"typo3/sysext/version/Migrations/Code/ClassAliasMap.php",
 				"typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php"
 			]
 		},
@@ -170,7 +169,6 @@
 			"TYPO3\\CMS\\T3editor\\": "typo3/sysext/t3editor/Classes/",
 			"TYPO3\\CMS\\Taskcenter\\": "typo3/sysext/taskcenter/Classes/",
 			"TYPO3\\CMS\\Tstemplate\\": "typo3/sysext/tstemplate/Classes/",
-			"TYPO3\\CMS\\Version\\": "typo3/sysext/version/Classes/",
 			"TYPO3\\CMS\\Viewpage\\": "typo3/sysext/viewpage/Classes/",
 			"TYPO3\\CMS\\Workspaces\\": "typo3/sysext/workspaces/Classes/"
 		},
diff --git a/composer.lock b/composer.lock
index d90f8d8ccca29e029d6d20a12e0e62b2d350c094..772b4029db10349dc633054e2b7ce1d03199fbf4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "content-hash": "0e757db5e2ea6409800d3e505af4f744",
+    "content-hash": "b603bc5f3ec745ae1db133cca1fc6d46",
     "packages": [
         {
             "name": "cogpowered/finediff",
diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
index 02faf4a9050f48fd447f8dd163cddf01fdb41c51..1c11d1070f1565b3387f6e586db1a766f2886a33 100644
--- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php
+++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
@@ -3841,7 +3841,7 @@ class BackendUtility
      */
     public static function fixVersioningPid($table, &$rr, $ignoreWorkspaceMatch = false)
     {
-        if (!ExtensionManagementUtility::isLoaded('version')) {
+        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
             return;
         }
         // Check that the input record is an offline version from a table that supports versioning:
@@ -3903,7 +3903,7 @@ class BackendUtility
      */
     public static function workspaceOL($table, &$row, $wsid = -99, $unsetMovePointers = false)
     {
-        if (!ExtensionManagementUtility::isLoaded('version')) {
+        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
             return;
         }
         // If this is FALSE the placeholder is shown raw in the backend.
@@ -4021,7 +4021,7 @@ class BackendUtility
      */
     public static function getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields = '*')
     {
-        if (ExtensionManagementUtility::isLoaded('version')) {
+        if (ExtensionManagementUtility::isLoaded('workspaces')) {
             if ($workspace !== 0 && $GLOBALS['TCA'][$table] && self::isTableWorkspaceEnabled($table)) {
 
                 // Select workspace version of record:
@@ -4084,7 +4084,7 @@ class BackendUtility
      */
     public static function getLiveVersionIdOfRecord($table, $uid)
     {
-        if (!ExtensionManagementUtility::isLoaded('version')) {
+        if (!ExtensionManagementUtility::isLoaded('workspaces')) {
             return null;
         }
         $liveVersionId = null;
diff --git a/typo3/sysext/backend/Classes/View/PageLayoutView.php b/typo3/sysext/backend/Classes/View/PageLayoutView.php
index 8a548010f449253fdb48420e8c7e6735bf24759a..7df137cb510970cdfe92e5108c205c438d6b1d1d 100644
--- a/typo3/sysext/backend/Classes/View/PageLayoutView.php
+++ b/typo3/sysext/backend/Classes/View/PageLayoutView.php
@@ -3875,9 +3875,8 @@ class PageLayoutView implements LoggerAwareInterface
                     if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
                         $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
                     }
-                    if (ExtensionManagementUtility::isLoaded(
-                            'version'
-                        ) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                    if (ExtensionManagementUtility::isLoaded('workspaces')
+                        && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
                         $fieldListArr[] = 't3ver_id';
                         $fieldListArr[] = 't3ver_state';
                         $fieldListArr[] = 't3ver_wsid';
diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index 3a383d097a0d80d9d25202d55799b0d88d7b2a17..f3a3e1f86dd4eab29d1adf45c5ccb5654f91b5a8 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -9113,11 +9113,11 @@ class DataHandler implements LoggerAwareInterface
      */
     protected function createRelationHandlerInstance()
     {
-        $isVersionLoaded = ExtensionManagementUtility::isLoaded('version');
+        $isWorkspacesLoaded = ExtensionManagementUtility::isLoaded('workspaces');
         $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
         $relationHandler->setWorkspaceId($this->BE_USER->workspace);
-        $relationHandler->setUseLiveReferenceIds($isVersionLoaded);
-        $relationHandler->setUseLiveParentIds($isVersionLoaded);
+        $relationHandler->setUseLiveReferenceIds($isWorkspacesLoaded);
+        $relationHandler->setUseLiveParentIds($isWorkspacesLoaded);
         return $relationHandler;
     }
 
diff --git a/typo3/sysext/core/Classes/DataHandling/PlainDataResolver.php b/typo3/sysext/core/Classes/DataHandling/PlainDataResolver.php
index ba335d32335ba3946f9096431cfcffcde95e9e4a..cbe90cf8784bc708ea65a416367d5edb7d27ea9b 100644
--- a/typo3/sysext/core/Classes/DataHandling/PlainDataResolver.php
+++ b/typo3/sysext/core/Classes/DataHandling/PlainDataResolver.php
@@ -372,7 +372,7 @@ class PlainDataResolver
      */
     protected function isWorkspaceEnabled()
     {
-        if (ExtensionManagementUtility::isLoaded('version')) {
+        if (ExtensionManagementUtility::isLoaded('workspaces')) {
             return BackendUtility::isTableWorkspaceEnabled($this->tableName);
         }
         return false;
diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index debe42abe12994b226b8fb3bb7df5e7608c60902..b4989b44b4ed169ae8a9985f7d0ebfe46855fe60 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -112,6 +112,11 @@ class ExtensionManagementUtility
      */
     public static function isLoaded($key, $exitOnError = false)
     {
+        // safety net for extensions checking for "EXT:version", can be removed in TYPO3 v10.
+        if ($key === 'version') {
+            trigger_error('EXT:version has been moved into EXT:workspaces, you should check against "workspaces", as this might lead to unexpected behaviour in the future.', E_USER_DEPRECATED);
+            $key = 'workspaces';
+        }
         $isLoaded = static::$packageManager->isPackageActive($key);
         if ($exitOnError && !$isLoaded) {
             throw new \BadFunctionCallException('TYPO3 Fatal Error: Extension "' . $key . '" is not loaded!', 1270853910);
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-82896-SystemExtensionVersionMigratedIntoWorkspaces.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-82896-SystemExtensionVersionMigratedIntoWorkspaces.rst
new file mode 100644
index 0000000000000000000000000000000000000000..e864148c660b464bd4cfe2d4ccc07178f503deb2
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-82896-SystemExtensionVersionMigratedIntoWorkspaces.rst
@@ -0,0 +1,54 @@
+.. include:: ../../Includes.txt
+
+========================================================================
+Breaking: #82896 - System extension "version" migrated into "workspaces"
+========================================================================
+
+See :issue:`82896`
+
+Description
+===========
+
+The basic functionality of versioning records, previously located within the "version" system
+extension was moved into the "workspaces" extension, which not only enhances the versioning with
+workflows and workflow stages, but also adds a Backend module to configure and to publish versioned
+records within a workspace.
+
+The extensions' deeply coupled logic is now moved into one system extension, providing the same
+functionality still.
+
+
+Impact
+======
+
+Using the versioning functionality of TYPO3 is now coupled with the workspace and workflow logic,
+and cannot be used separately for custom versioning strategies not supported by TYPO3 Core.
+
+Additionally, third-party extensions checking for the previously available "version" extensions
+will trigger a deprecation warning.
+
+
+Affected Installations
+======================
+
+Any installation solely providing versioning functionality based on the "version" extension,
+but not using "workspaces".
+
+
+Migration
+=========
+
+Adapt your changes to check for "workspaces" instead of the "version" extension.
+
+.. code-block:: php
+
+	# old
+	if (ExtensionManagementUtility::isLoaded('version')) { ... }
+
+	# new
+	if (ExtensionManagementUtility::isLoaded('workspaces')) { ... }
+
+If you built custom functionality built on "version" without "workspaces", ensure to adapt
+your settings and old class names to use the workspace PHP namespaces.
+
+.. index:: PHP-API, NotScanned
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/ext_emconf.php
index a3b5e1c64826757f9ff3f04230ea9c5cb5a64d44..d0daa7cbb102ac721388f793e97d262bc5190a5e 100644
--- a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/ext_emconf.php
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/irre_tutorial/ext_emconf.php
@@ -15,7 +15,6 @@ $EM_CONF[$_EXTKEY] = [
         'depends' => [
             'typo3' => '4.5.0-0.0.0',
             'workspaces' => '0.0.0-',
-            'version' => '0.0.0-',
         ],
         'conflicts' => [
         ],
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler/ext_emconf.php
index e5ce8ffff6ba1c8fbe54c68433d4fbb346b283df..abfcffc004c62e4a2ccc5216db022261316c7161 100644
--- a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler/ext_emconf.php
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_datahandler/ext_emconf.php
@@ -15,7 +15,6 @@ $EM_CONF[$_EXTKEY] = [
         'depends' => [
             'typo3' => '6.0.0-0.0.0',
             'workspaces' => '0.0.0-',
-            'version' => '0.0.0-',
         ],
         'conflicts' => [
         ],
diff --git a/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php
index 381b521a842307fedf78ad49a91a7ca45c2714ce..e3c79fec27d03419efadc2cd5f4475b96605d861 100644
--- a/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php
+++ b/typo3/sysext/recordlist/Classes/RecordList/AbstractDatabaseRecordList.php
@@ -1252,7 +1252,7 @@ class AbstractDatabaseRecordList extends AbstractRecordList
                     if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
                         $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
                     }
-                    if (ExtensionManagementUtility::isLoaded('version') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                    if (ExtensionManagementUtility::isLoaded('workspaces') && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
                         $fieldListArr[] = 't3ver_id';
                         $fieldListArr[] = 't3ver_state';
                         $fieldListArr[] = 't3ver_wsid';
diff --git a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
index 686403a5b8f6c755cd3696d0866f2cdbfe675c1d..3d5f0c7ccce8970c51b1f613157fdb74f936d9a3 100644
--- a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
+++ b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php
@@ -1842,7 +1842,7 @@ class DatabaseRecordList
     {
         $module = $this->getModule();
         $rowUid = $row['uid'];
-        if (ExtensionManagementUtility::isLoaded('version') && isset($row['_ORIG_uid'])) {
+        if (ExtensionManagementUtility::isLoaded('workspaces') && isset($row['_ORIG_uid'])) {
             $rowUid = $row['_ORIG_uid'];
         }
         $cells = [
@@ -3650,7 +3650,7 @@ class DatabaseRecordList
                         $fieldListArr[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
                     }
                     if (ExtensionManagementUtility::isLoaded(
-                            'version'
+                            'workspaces'
                         ) && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
                         $fieldListArr[] = 't3ver_id';
                         $fieldListArr[] = 't3ver_state';
diff --git a/typo3/sysext/version/Classes/Hook/DataHandlerHook.php b/typo3/sysext/version/Classes/Hook/DataHandlerHook.php
deleted file mode 100644
index 65547869f36f2ab4f89efbc07d215c13a64d6bba..0000000000000000000000000000000000000000
--- a/typo3/sysext/version/Classes/Hook/DataHandlerHook.php
+++ /dev/null
@@ -1,1592 +0,0 @@
-<?php
-namespace TYPO3\CMS\Version\Hook;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * 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.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use Doctrine\DBAL\DBALException;
-use Doctrine\DBAL\Platforms\SQLServerPlatform;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Database\Connection;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
-use TYPO3\CMS\Core\Database\ReferenceIndex;
-use TYPO3\CMS\Core\DataHandling\DataHandler;
-use TYPO3\CMS\Core\Localization\LanguageService;
-use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
-use TYPO3\CMS\Core\Utility\ArrayUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Versioning\VersionState;
-
-/**
- * Contains some parts for staging, versioning and workspaces
- * to interact with the TYPO3 Core Engine
- */
-class DataHandlerHook
-{
-    /**
-     * For accumulating information about workspace stages raised
-     * on elements so a single mail is sent as notification.
-     * previously called "accumulateForNotifEmail" in DataHandler
-     *
-     * @var array
-     */
-    protected $notificationEmailInfo = [];
-
-    /**
-     * Contains remapped IDs.
-     *
-     * @var array
-     */
-    protected $remappedIds = [];
-
-    /**
-     * @var \TYPO3\CMS\Workspaces\Service\WorkspaceService
-     */
-    protected $workspaceService;
-
-    /****************************
-     *****  Cmdmap  Hooks  ******
-     ****************************/
-    /**
-     * hook that is called before any cmd of the commandmap is executed
-     *
-     * @param DataHandler $dataHandler reference to the main DataHandler object
-     */
-    public function processCmdmap_beforeStart(DataHandler $dataHandler)
-    {
-        // Reset notification array
-        $this->notificationEmailInfo = [];
-        // Resolve dependencies of version/workspaces actions:
-        $dataHandler->cmdmap = $this->getCommandMap($dataHandler)->process()->get();
-    }
-
-    /**
-     * hook that is called when no prepared command was found
-     *
-     * @param string $command the command to be executed
-     * @param string $table the table of the record
-     * @param int $id the ID of the record
-     * @param mixed $value the value containing the data
-     * @param bool $commandIsProcessed can be set so that other hooks or
-     * @param DataHandler $dataHandler reference to the main DataHandler object
-     */
-    public function processCmdmap($command, $table, $id, $value, &$commandIsProcessed, DataHandler $dataHandler)
-    {
-        // custom command "version"
-        if ($command === 'version') {
-            $commandIsProcessed = true;
-            $action = (string)$value['action'];
-            $comment = !empty($value['comment']) ? $value['comment'] : '';
-            $notificationAlternativeRecipients = (isset($value['notificationAlternativeRecipients'])) && is_array($value['notificationAlternativeRecipients']) ? $value['notificationAlternativeRecipients'] : [];
-            switch ($action) {
-                case 'new':
-                    $dataHandler->versionizeRecord($table, $id, $value['label']);
-                    break;
-                case 'swap':
-                    $this->version_swap(
-                        $table,
-                        $id,
-                        $value['swapWith'],
-                        $value['swapIntoWS'],
-                        $dataHandler,
-                        $comment,
-                        true,
-                        $notificationAlternativeRecipients
-                    );
-                    break;
-                case 'clearWSID':
-                    $this->version_clearWSID($table, $id, false, $dataHandler);
-                    break;
-                case 'flush':
-                    $this->version_clearWSID($table, $id, true, $dataHandler);
-                    break;
-                case 'setStage':
-                    $elementIds = GeneralUtility::trimExplode(',', $id, true);
-                    foreach ($elementIds as $elementId) {
-                        $this->version_setStage(
-                            $table,
-                            $elementId,
-                            $value['stageId'],
-                                $comment,
-                                true,
-                                $dataHandler,
-                                $notificationAlternativeRecipients
-                            );
-                    }
-                    break;
-                default:
-                    // Do nothing
-            }
-        }
-    }
-
-    /**
-     * hook that is called AFTER all commands of the commandmap was
-     * executed
-     *
-     * @param DataHandler $dataHandler reference to the main DataHandler object
-     */
-    public function processCmdmap_afterFinish(DataHandler $dataHandler)
-    {
-        // Empty accumulation array:
-        foreach ($this->notificationEmailInfo as $notifItem) {
-            $this->notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $dataHandler, $notifItem['alternativeRecipients']);
-        }
-        // Reset notification array
-        $this->notificationEmailInfo = [];
-        // Reset remapped IDs
-        $this->remappedIds = [];
-    }
-
-    /**
-     * hook that is called when an element shall get deleted
-     *
-     * @param string $table the table of the record
-     * @param int $id the ID of the record
-     * @param array $record The accordant database record
-     * @param bool $recordWasDeleted can be set so that other hooks or
-     * @param DataHandler $dataHandler reference to the main DataHandler object
-     */
-    public function processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $dataHandler)
-    {
-        // only process the hook if it wasn't processed
-        // by someone else before
-        if ($recordWasDeleted) {
-            return;
-        }
-        $recordWasDeleted = true;
-        // For Live version, try if there is a workspace version because if so, rather "delete" that instead
-        // Look, if record is an offline version, then delete directly:
-        if ($record['pid'] != -1) {
-            if ($wsVersion = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $id)) {
-                $record = $wsVersion;
-                $id = $record['uid'];
-            }
-        }
-        $recordVersionState = VersionState::cast($record['t3ver_state']);
-        // Look, if record is an offline version, then delete directly:
-        if ($record['pid'] == -1) {
-            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
-                // In Live workspace, delete any. In other workspaces there must be match.
-                if ($dataHandler->BE_USER->workspace == 0 || (int)$record['t3ver_wsid'] == $dataHandler->BE_USER->workspace) {
-                    $liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
-                    // Processing can be skipped if a delete placeholder shall be swapped/published
-                    // during the current request. Thus it will be deleted later on...
-                    $liveRecordVersionState = VersionState::cast($liveRec['t3ver_state']);
-                    if ($recordVersionState->equals(VersionState::DELETE_PLACEHOLDER) && !empty($liveRec['uid'])
-                        && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'])
-                        && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'])
-                        && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap'
-                        && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id
-                    ) {
-                        return null;
-                    }
-
-                    if ($record['t3ver_wsid'] > 0 && $recordVersionState->equals(VersionState::DEFAULT_STATE)) {
-                        // Change normal versioned record to delete placeholder
-                        // Happens when an edited record is deleted
-                        GeneralUtility::makeInstance(ConnectionPool::class)
-                            ->getConnectionForTable($table)
-                            ->update(
-                                $table,
-                                [
-                                    't3ver_label' => 'DELETED!',
-                                    't3ver_state' => 2,
-                                ],
-                                ['uid' => $id]
-                            );
-
-                        // Delete localization overlays:
-                        $dataHandler->deleteL10nOverlayRecords($table, $id);
-                    } elseif ($record['t3ver_wsid'] == 0 || !$liveRecordVersionState->indicatesPlaceholder()) {
-                        // Delete those in WS 0 + if their live records state was not "Placeholder".
-                        $dataHandler->deleteEl($table, $id);
-                        // Delete move-placeholder if current version record is a move-to-pointer
-                        if ($recordVersionState->equals(VersionState::MOVE_POINTER)) {
-                            $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid', $record['t3ver_wsid']);
-                            if (!empty($movePlaceholder)) {
-                                $dataHandler->deleteEl($table, $movePlaceholder['uid']);
-                            }
-                        }
-                    } else {
-                        // If live record was placeholder (new/deleted), rather clear
-                        // it from workspace (because it clears both version and placeholder).
-                        $this->version_clearWSID($table, $id, false, $dataHandler);
-                    }
-                } else {
-                    $dataHandler->newlog('Tried to delete record from another workspace', 1);
-                }
-            } else {
-                $dataHandler->newlog('Versioning not enabled for record with PID = -1!', 2);
-            }
-        } elseif ($res = $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) {
-            // Look, if record is "online" or in a versionized branch, then delete directly.
-            if ($res > 0) {
-                $dataHandler->deleteEl($table, $id);
-            } else {
-                $dataHandler->newlog('Stage of root point did not allow for deletion', 1);
-            }
-        } elseif ($recordVersionState->equals(VersionState::MOVE_PLACEHOLDER)) {
-            // Placeholders for moving operations are deletable directly.
-            // Get record which its a placeholder for and reset the t3ver_state of that:
-            if ($wsRec = BackendUtility::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) {
-                // Clear the state flag of the workspace version of the record
-                // Setting placeholder state value for version (so it can know it is currently a new version...)
-
-                GeneralUtility::makeInstance(ConnectionPool::class)
-                    ->getConnectionForTable($table)
-                    ->update(
-                        $table,
-                        [
-                            't3ver_state' => (string)new VersionState(VersionState::DEFAULT_STATE)
-                        ],
-                        ['uid' => (int)$wsRec['uid']]
-                    );
-            }
-            $dataHandler->deleteEl($table, $id);
-        } else {
-            // Otherwise, try to delete by versioning:
-            $copyMappingArray = $dataHandler->copyMappingArray;
-            $dataHandler->versionizeRecord($table, $id, 'DELETED!', true);
-            // Determine newly created versions:
-            // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
-            $versionizedElements = ArrayUtility::arrayDiffAssocRecursive($dataHandler->copyMappingArray, $copyMappingArray);
-            // Delete localization overlays:
-            foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
-                foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
-                    $dataHandler->deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
-                }
-            }
-        }
-    }
-
-    /**
-     * Hook for \TYPO3\CMS\Core\DataHandling\DataHandler::moveRecord that cares about
-     * moving records that are *not* in the live workspace
-     *
-     * @param string $table the table of the record
-     * @param int $uid the ID of the record
-     * @param int $destPid Position to move to: $destPid: >=0 then it points to
-     * @param array $propArr Record properties, like header and pid (includes workspace overlay)
-     * @param array $moveRec Record properties, like header and pid (without workspace overlay)
-     * @param int $resolvedPid The final page ID of the record
-     * @param bool $recordWasMoved can be set so that other hooks or
-     * @param DataHandler $dataHandler
-     */
-    public function moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $dataHandler)
-    {
-        // Only do something in Draft workspace
-        if ($dataHandler->BE_USER->workspace === 0) {
-            return;
-        }
-        if ($destPid < 0) {
-            // Fetch move placeholder, since it might point to a new page in the current workspace
-            $movePlaceHolder = BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid,pid');
-            if ($movePlaceHolder !== false) {
-                $resolvedPid = $movePlaceHolder['pid'];
-            }
-        }
-        $recordWasMoved = true;
-        $moveRecVersionState = VersionState::cast($moveRec['t3ver_state']);
-        // Get workspace version of the source record, if any:
-        $WSversion = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
-        // Handle move-placeholders if the current record is not one already
-        if (
-            BackendUtility::isTableWorkspaceEnabled($table)
-            && !$moveRecVersionState->equals(VersionState::MOVE_PLACEHOLDER)
-        ) {
-            // Create version of record first, if it does not exist
-            if (empty($WSversion['uid'])) {
-                $dataHandler->versionizeRecord($table, $uid, 'MovePointer');
-                $WSversion = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
-                $this->moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
-            } elseif ($dataHandler->isRecordCopied($table, $uid) && (int)$dataHandler->copyMappingArray[$table][$uid] === (int)$WSversion['uid']) {
-                // If the record has been versioned before (e.g. cascaded parent-child structure), create only the move-placeholders
-                $this->moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
-            }
-        }
-        // Check workspace permissions:
-        $workspaceAccessBlocked = [];
-        // Element was in "New/Deleted/Moved" so it can be moved...
-        $recIsNewVersion = $moveRecVersionState->indicatesPlaceholder();
-        $destRes = $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table);
-        $canMoveRecord = ($recIsNewVersion || BackendUtility::isTableWorkspaceEnabled($table));
-        // Workspace source check:
-        if (!$recIsNewVersion) {
-            $errorCode = $dataHandler->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid);
-            if ($errorCode) {
-                $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
-            } elseif (!$canMoveRecord && $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($moveRec['pid'], $table) <= 0) {
-                $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "' . $moveRec['pid'] . '" ';
-            }
-        }
-        // Workspace destination check:
-        // All records can be inserted if $destRes is greater than zero.
-        // Only new versions can be inserted if $destRes is FALSE.
-        // NO RECORDS can be inserted if $destRes is negative which indicates a stage
-        //  not allowed for use. If "versioningWS" is version 2, moving can take place of versions.
-        // since TYPO3 CMS 7, version2 is the default and the only option
-        if (!($destRes > 0 || $canMoveRecord && !$destRes)) {
-            $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
-        } elseif ($destRes == 1 && $WSversion['uid']) {
-            $workspaceAccessBlocked['dest2'] = 'Could not insert other versions in destination PID ';
-        }
-        if (empty($workspaceAccessBlocked)) {
-            // If the move operation is done on a versioned record, which is
-            // NOT new/deleted placeholder and versioningWS is in version 2, then...
-            // since TYPO3 CMS 7, version2 is the default and the only option
-            if ($WSversion['uid'] && !$recIsNewVersion && BackendUtility::isTableWorkspaceEnabled($table)) {
-                $this->moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $dataHandler);
-            } else {
-                // moving not needed, just behave like in live workspace
-                $recordWasMoved = false;
-            }
-        } else {
-            $dataHandler->newlog('Move attempt failed due to workspace restrictions: ' . implode(' // ', $workspaceAccessBlocked), 1);
-        }
-    }
-
-    /**
-     * Processes fields of a moved record and follows references.
-     *
-     * @param DataHandler $dataHandler Calling DataHandler instance
-     * @param int $resolvedPageId Resolved real destination page id
-     * @param string $table Name of parent table
-     * @param int $uid UID of the parent record
-     */
-    protected function moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
-    {
-        $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid);
-        if (empty($versionedRecord)) {
-            return;
-        }
-        foreach ($versionedRecord as $field => $value) {
-            if (empty($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
-                continue;
-            }
-            $this->moveRecord_processFieldValue(
-                $dataHandler,
-                $resolvedPageId,
-                $table,
-                $uid,
-                $field,
-                $value,
-                $GLOBALS['TCA'][$table]['columns'][$field]['config']
-            );
-        }
-    }
-
-    /**
-     * Processes a single field of a moved record and follows references.
-     *
-     * @param DataHandler $dataHandler Calling DataHandler instance
-     * @param int $resolvedPageId Resolved real destination page id
-     * @param string $table Name of parent table
-     * @param int $uid UID of the parent record
-     * @param string $field Name of the field of the parent record
-     * @param string $value Value of the field of the parent record
-     * @param array $configuration TCA field configuration of the parent record
-     */
-    protected function moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $field, $value, array $configuration)
-    {
-        $inlineFieldType = $dataHandler->getInlineFieldType($configuration);
-        $inlineProcessing = (
-            ($inlineFieldType === 'list' || $inlineFieldType === 'field')
-            && BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])
-            && (!isset($configuration['behaviour']['disableMovingChildrenWithParent']) || !$configuration['behaviour']['disableMovingChildrenWithParent'])
-        );
-
-        if ($inlineProcessing) {
-            if ($table === 'pages') {
-                // If the inline elements are related to a page record,
-                // make sure they reside at that page and not at its parent
-                $resolvedPageId = $uid;
-            }
-
-            $dbAnalysis = $this->createRelationHandlerInstance();
-            $dbAnalysis->start($value, $configuration['foreign_table'], '', $uid, $table, $configuration);
-
-            // Moving records to a positive destination will insert each
-            // record at the beginning, thus the order is reversed here:
-            foreach ($dbAnalysis->itemArray as $item) {
-                $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $item['table'], $item['id'], 'uid,t3ver_state');
-                if (empty($versionedRecord) || VersionState::cast($versionedRecord['t3ver_state'])->indicatesPlaceholder()) {
-                    continue;
-                }
-                $dataHandler->moveRecord($item['table'], $item['id'], $resolvedPageId);
-            }
-        }
-    }
-
-    /****************************
-     *****  Notifications  ******
-     ****************************/
-    /**
-     * Send an email notification to users in workspace
-     *
-     * @param array $stat Workspace access array from \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::checkWorkspace()
-     * @param int $stageId New Stage number: 0 = editing, 1= just ready for review, 10 = ready for publication, -1 = rejected!
-     * @param string $table Table name of element (or list of element names if $id is zero)
-     * @param int $id Record uid of element (if zero, then $table is used as reference to element(s) alone)
-     * @param string $comment User comment sent along with action
-     * @param DataHandler $dataHandler DataHandler object
-     * @param array $notificationAlternativeRecipients List of recipients to notify instead of be_users selected by sys_workspace, list is generated by workspace extension module
-     */
-    protected function notifyStageChange(array $stat, $stageId, $table, $id, $comment, DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
-    {
-        $workspaceRec = BackendUtility::getRecord('sys_workspace', $stat['uid']);
-        // So, if $id is not set, then $table is taken to be the complete element name!
-        $elementName = $id ? $table . ':' . $id : $table;
-        if (!is_array($workspaceRec)) {
-            return;
-        }
-
-        // Get the new stage title from workspaces library, if workspaces extension is installed
-        if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
-            $stageService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
-            $newStage = $stageService->getStageTitle((int)$stageId);
-        } else {
-            // @todo CONSTANTS SHOULD BE USED - tx_service_workspace_workspaces
-            // @todo use localized labels
-            // Compile label:
-            switch ((int)$stageId) {
-                case 1:
-                    $newStage = 'Ready for review';
-                    break;
-                case 10:
-                    $newStage = 'Ready for publishing';
-                    break;
-                case -1:
-                    $newStage = 'Element was rejected!';
-                    break;
-                case 0:
-                    $newStage = 'Rejected element was noticed and edited';
-                    break;
-                default:
-                    $newStage = 'Unknown state change!?';
-            }
-        }
-        if (empty($notificationAlternativeRecipients)) {
-            // Compile list of recipients:
-            $emails = [];
-            switch ((int)$stat['stagechg_notification']) {
-                case 1:
-                    switch ((int)$stageId) {
-                        case 1:
-                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']);
-                            break;
-                        case 10:
-                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
-                            break;
-                        case -1:
-                            // List of elements to reject:
-                            $allElements = explode(',', $elementName);
-                            // Traverse them, and find the history of each
-                            foreach ($allElements as $elRef) {
-                                list($eTable, $eUid) = explode(':', $elRef);
-
-                                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                                    ->getQueryBuilderForTable('sys_log');
-
-                                $queryBuilder->getRestrictions()->removeAll();
-
-                                $result = $queryBuilder
-                                    ->select('log_data', 'tstamp', 'userid')
-                                    ->from('sys_log')
-                                    ->where(
-                                        $queryBuilder->expr()->eq(
-                                            'action',
-                                            $queryBuilder->createNamedParameter(6, \PDO::PARAM_INT)
-                                        ),
-                                        $queryBuilder->expr()->eq(
-                                            'details_nr',
-                                            $queryBuilder->createNamedParameter(30, \PDO::PARAM_INT)
-                                        ),
-                                        $queryBuilder->expr()->eq(
-                                            'tablename',
-                                            $queryBuilder->createNamedParameter($eTable, \PDO::PARAM_STR)
-                                        ),
-                                        $queryBuilder->expr()->eq(
-                                            'recuid',
-                                            $queryBuilder->createNamedParameter($eUid, \PDO::PARAM_INT)
-                                        )
-                                    )
-                                    ->orderBy('uid', 'DESC')
-                                    ->execute();
-
-                                // Find all implicated since the last stage-raise from editing to review:
-                                while ($dat = $result->fetch()) {
-                                    $data = unserialize($dat['log_data']);
-                                    $emails = $this->getEmailsForStageChangeNotification($dat['userid'], true) + $emails;
-                                    if ($data['stage'] == 1) {
-                                        break;
-                                    }
-                                }
-                            }
-                            break;
-                        case 0:
-                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']);
-                            break;
-                        default:
-                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
-                    }
-                    break;
-                case 10:
-                    $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
-                    $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']) + $emails;
-                    $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']) + $emails;
-                    break;
-                default:
-                    // Do nothing
-            }
-        } else {
-            $emails = $notificationAlternativeRecipients;
-        }
-        // prepare and then send the emails
-        if (!empty($emails)) {
-            // Path to record is found:
-            list($elementTable, $elementUid) = explode(':', $elementName);
-            $elementUid = (int)$elementUid;
-            $elementRecord = BackendUtility::getRecord($elementTable, $elementUid);
-            $recordTitle = BackendUtility::getRecordTitle($elementTable, $elementRecord);
-            if ($elementTable === 'pages') {
-                $pageUid = $elementUid;
-            } else {
-                BackendUtility::fixVersioningPid($elementTable, $elementRecord);
-                $pageUid = ($elementUid = $elementRecord['pid']);
-            }
-
-            // new way, options are
-            // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject
-            // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject
-            $pageTsConfig = BackendUtility::getPagesTSconfig($pageUid);
-            $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.'];
-            $markers = [
-                '###RECORD_TITLE###' => $recordTitle,
-                '###RECORD_PATH###' => BackendUtility::getRecordPath($elementUid, '', 20),
-                '###SITE_NAME###' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
-                '###SITE_URL###' => GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir,
-                '###WORKSPACE_TITLE###' => $workspaceRec['title'],
-                '###WORKSPACE_UID###' => $workspaceRec['uid'],
-                '###ELEMENT_NAME###' => $elementName,
-                '###NEXT_STAGE###' => $newStage,
-                '###COMMENT###' => $comment,
-                // See: #30212 - keep both markers for compatibility
-                '###USER_REALNAME###' => $dataHandler->BE_USER->user['realName'],
-                '###USER_FULLNAME###' => $dataHandler->BE_USER->user['realName'],
-                '###USER_USERNAME###' => $dataHandler->BE_USER->user['username']
-            ];
-            // add marker for preview links if workspace extension is loaded
-            if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('workspaces')) {
-                $this->workspaceService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
-                // only generate the link if the marker is in the template - prevents database from getting to much entries
-                if (GeneralUtility::isFirstPartOfStr($emailConfig['message'], 'LLL:')) {
-                    $tempEmailMessage = $this->getLanguageService()->sL($emailConfig['message']);
-                } else {
-                    $tempEmailMessage = $emailConfig['message'];
-                }
-                if (strpos($tempEmailMessage, '###PREVIEW_LINK###') !== false) {
-                    $markers['###PREVIEW_LINK###'] = $this->workspaceService->generateWorkspacePreviewLink($elementUid);
-                }
-                unset($tempEmailMessage);
-                $markers['###SPLITTED_PREVIEW_LINK###'] = $this->workspaceService->generateWorkspaceSplittedPreviewLink($elementUid, true);
-            }
-            // Hook for preprocessing of the content for formmails:
-            if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'])) {
-                foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'] as $className) {
-                    $_procObj = GeneralUtility::makeInstance($className);
-                    $markers = $_procObj->postModifyMarkers($markers, $this);
-                }
-            }
-            // send an email to each individual user, to ensure the
-            // multilanguage version of the email
-            $emailRecipients = [];
-            // an array of language objects that are needed
-            // for emails with different languages
-            $languageObjects = [
-                $this->getLanguageService()->lang => $this->getLanguageService()
-            ];
-            // loop through each recipient and send the email
-            foreach ($emails as $recipientData) {
-                // don't send an email twice
-                if (isset($emailRecipients[$recipientData['email']])) {
-                    continue;
-                }
-                $emailSubject = $emailConfig['subject'];
-                $emailMessage = $emailConfig['message'];
-                $emailRecipients[$recipientData['email']] = $recipientData['email'];
-                // check if the email needs to be localized
-                // in the users' language
-                if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:') || GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
-                    $recipientLanguage = $recipientData['lang'] ? $recipientData['lang'] : 'default';
-                    if (!isset($languageObjects[$recipientLanguage])) {
-                        // a LANG object in this language hasn't been
-                        // instantiated yet, so this is done here
-                        /** @var $languageObject \TYPO3\CMS\Core\Localization\LanguageService */
-                        $languageObject = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
-                        $languageObject->init($recipientLanguage);
-                        $languageObjects[$recipientLanguage] = $languageObject;
-                    } else {
-                        $languageObject = $languageObjects[$recipientLanguage];
-                    }
-                    if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:')) {
-                        $emailSubject = $languageObject->sL($emailSubject);
-                    }
-                    if (GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
-                        $emailMessage = $languageObject->sL($emailMessage);
-                    }
-                }
-                $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
-                $emailSubject = $templateService->substituteMarkerArray($emailSubject, $markers, '', true, true);
-                $emailMessage = $templateService->substituteMarkerArray($emailMessage, $markers, '', true, true);
-                // Send an email to the recipient
-                /** @var $mail \TYPO3\CMS\Core\Mail\MailMessage */
-                $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
-                if (!empty($recipientData['realName'])) {
-                    $recipient = [$recipientData['email'] => $recipientData['realName']];
-                } else {
-                    $recipient = $recipientData['email'];
-                }
-                $mail->setTo($recipient)
-                    ->setSubject($emailSubject)
-                    ->setBody($emailMessage);
-                $mail->send();
-            }
-            $emailRecipients = implode(',', $emailRecipients);
-            $dataHandler->newlog2('Notification email for stage change was sent to "' . $emailRecipients . '"', $table, $id);
-        }
-    }
-
-    /**
-     * Return be_users that should be notified on stage change from input list.
-     * previously called notifyStageChange_getEmails() in DataHandler
-     *
-     * @param string $listOfUsers List of backend users, on the form "be_users_10,be_users_2" or "10,2" in case noTablePrefix is set.
-     * @param bool $noTablePrefix If set, the input list are integers and not strings.
-     * @return array Array of emails
-     */
-    protected function getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = false)
-    {
-        $users = GeneralUtility::trimExplode(',', $listOfUsers, true);
-        $emails = [];
-        foreach ($users as $userIdent) {
-            if ($noTablePrefix) {
-                $id = (int)$userIdent;
-            } else {
-                list($table, $id) = GeneralUtility::revExplode('_', $userIdent, 2);
-            }
-            if ($table === 'be_users' || $noTablePrefix) {
-                if ($userRecord = BackendUtility::getRecord('be_users', $id, 'uid,email,lang,realName', BackendUtility::BEenableFields('be_users'))) {
-                    if (trim($userRecord['email']) !== '') {
-                        $emails[$id] = $userRecord;
-                    }
-                }
-            }
-        }
-        return $emails;
-    }
-
-    /****************************
-     *****  Stage Changes  ******
-     ****************************/
-    /**
-     * Setting stage of record
-     *
-     * @param string $table Table name
-     * @param int $integer Record UID
-     * @param int $stageId Stage ID to set
-     * @param string $comment Comment that goes into log
-     * @param bool $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
-     * @param DataHandler $dataHandler DataHandler object
-     * @param array $notificationAlternativeRecipients comma separated list of recipients to notify instead of normal be_users
-     */
-    protected function version_setStage($table, $id, $stageId, $comment = '', $notificationEmailInfo = false, DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
-    {
-        if ($errorCode = $dataHandler->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
-            $dataHandler->newlog('Attempt to set stage for record failed: ' . $errorCode, 1);
-        } elseif ($dataHandler->checkRecordUpdateAccess($table, $id)) {
-            $record = BackendUtility::getRecord($table, $id);
-            $stat = $dataHandler->BE_USER->checkWorkspace($record['t3ver_wsid']);
-            // check if the usere is allowed to the current stage, so it's also allowed to send to next stage
-            if ($dataHandler->BE_USER->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
-                // Set stage of record:
-                GeneralUtility::makeInstance(ConnectionPool::class)
-                    ->getConnectionForTable($table)
-                    ->update(
-                        $table,
-                        [
-                            't3ver_stage' => $stageId,
-                        ],
-                        ['uid' => (int)$id]
-                    );
-                $dataHandler->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
-                // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere!
-                $dataHandler->log($table, $id, 6, 0, 0, 'Stage raised...', 30, ['comment' => $comment, 'stage' => $stageId]);
-                if ((int)$stat['stagechg_notification'] > 0) {
-                    if ($notificationEmailInfo) {
-                        $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['shared'] = [$stat, $stageId, $comment];
-                        $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = $table . ':' . $id;
-                        $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['alternativeRecipients'] = $notificationAlternativeRecipients;
-                    } else {
-                        $this->notifyStageChange($stat, $stageId, $table, $id, $comment, $dataHandler, $notificationAlternativeRecipients);
-                    }
-                }
-            } else {
-                $dataHandler->newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', 1);
-            }
-        } else {
-            $dataHandler->newlog('Attempt to set stage for record failed because you do not have edit access', 1);
-        }
-    }
-
-    /*****************************
-     *****  CMD versioning  ******
-     *****************************/
-
-    /**
-     * Swapping versions of a record
-     * Version from archive (future/past, called "swap version") will get the uid of the "t3ver_oid", the official element with uid = "t3ver_oid" will get the new versions old uid. PIDs are swapped also
-     *
-     * @param string $table Table name
-     * @param int $id UID of the online record to swap
-     * @param int $swapWith UID of the archived version to swap with!
-     * @param bool $swapIntoWS If set, swaps online into workspace instead of publishing out of workspace.
-     * @param DataHandler $dataHandler DataHandler object
-     * @param string $comment Notification comment
-     * @param bool $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
-     * @param array $notificationAlternativeRecipients comma separated list of recipients to notificate instead of normal be_users
-     */
-    protected function version_swap($table, $id, $swapWith, $swapIntoWS = 0, DataHandler $dataHandler, $comment = '', $notificationEmailInfo = false, $notificationAlternativeRecipients = [])
-    {
-
-        // Check prerequisites before start swapping
-
-        // Skip records that have been deleted during the current execution
-        if ($dataHandler->hasDeletedRecord($table, $id)) {
-            return;
-        }
-
-        // First, check if we may actually edit the online record
-        if (!$dataHandler->checkRecordUpdateAccess($table, $id)) {
-            $dataHandler->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
-            return;
-        }
-        // Select the two versions:
-        $curVersion = BackendUtility::getRecord($table, $id, '*');
-        $swapVersion = BackendUtility::getRecord($table, $swapWith, '*');
-        $movePlh = [];
-        $movePlhID = 0;
-        if (!(is_array($curVersion) && is_array($swapVersion))) {
-            $dataHandler->newlog('Error: Either online or swap version could not be selected!', 2);
-            return;
-        }
-        if (!$dataHandler->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
-            $dataHandler->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
-            return;
-        }
-        $wsAccess = $dataHandler->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
-        if (!($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int)$swapVersion['t3ver_stage'] === -10)) {
-            $dataHandler->newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1);
-            return;
-        }
-        if (!($dataHandler->doesRecordExist($table, $swapWith, 'show') && $dataHandler->checkRecordUpdateAccess($table, $swapWith))) {
-            $dataHandler->newlog('You cannot publish a record you do not have edit and show permissions for', 1);
-            return;
-        }
-        if ($swapIntoWS && !$dataHandler->BE_USER->workspaceSwapAccess()) {
-            $dataHandler->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1);
-            return;
-        }
-        // Check if the swapWith record really IS a version of the original!
-        if (!(((int)$swapVersion['pid'] == -1 && (int)$curVersion['pid'] >= 0) && (int)$swapVersion['t3ver_oid'] === (int)$id)) {
-            $dataHandler->newlog('In swap version, either pid was not -1 or the t3ver_oid didn\'t match the id of the online version as it must!', 2);
-            return;
-        }
-        // Lock file name:
-        $lockFileName = PATH_site . 'typo3temp/var/swap_locking/' . $table . '_' . $id . '.ser';
-        if (@is_file($lockFileName)) {
-            $dataHandler->newlog('A swapping lock file was present. Either another swap process is already running or a previous swap process failed. Ask your administrator to handle the situation.', 2);
-            return;
-        }
-
-        // Now start to swap records by first creating the lock file
-
-        // Write lock-file:
-        GeneralUtility::writeFileToTypo3tempDir($lockFileName, serialize([
-            'tstamp' => $GLOBALS['EXEC_TIME'],
-            'user' => $dataHandler->BE_USER->user['username'],
-            'curVersion' => $curVersion,
-            'swapVersion' => $swapVersion
-        ]));
-        // Find fields to keep
-        $keepFields = $this->getUniqueFields($table);
-        if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
-            $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
-        }
-        // l10n-fields must be kept otherwise the localization
-        // will be lost during the publishing
-        if ($table !== 'pages_language_overlay' && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
-            $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
-        }
-        // Swap "keepfields"
-        foreach ($keepFields as $fN) {
-            $tmp = $swapVersion[$fN];
-            $swapVersion[$fN] = $curVersion[$fN];
-            $curVersion[$fN] = $tmp;
-        }
-        // Preserve states:
-        $t3ver_state = [];
-        $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
-        $t3ver_state['curVersion'] = $curVersion['t3ver_state'];
-        // Modify offline version to become online:
-        $tmp_wsid = $swapVersion['t3ver_wsid'];
-        // Set pid for ONLINE
-        $swapVersion['pid'] = (int)$curVersion['pid'];
-        // We clear this because t3ver_oid only make sense for offline versions
-        // and we want to prevent unintentional misuse of this
-        // value for online records.
-        $swapVersion['t3ver_oid'] = 0;
-        // In case of swapping and the offline record has a state
-        // (like 2 or 4 for deleting or move-pointer) we set the
-        // current workspace ID so the record is not deselected
-        // in the interface by BackendUtility::versioningPlaceholderClause()
-        $swapVersion['t3ver_wsid'] = 0;
-        if ($swapIntoWS) {
-            if ($t3ver_state['swapVersion'] > 0) {
-                $swapVersion['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
-            } else {
-                $swapVersion['t3ver_wsid'] = (int)$curVersion['t3ver_wsid'];
-            }
-        }
-        $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
-        $swapVersion['t3ver_stage'] = 0;
-        if (!$swapIntoWS) {
-            $swapVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
-        }
-        // Moving element.
-        if (BackendUtility::isTableWorkspaceEnabled($table)) {
-            //  && $t3ver_state['swapVersion']==4   // Maybe we don't need this?
-            if ($plhRec = BackendUtility::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . ($GLOBALS['TCA'][$table]['ctrl']['sortby'] ? ',' . $GLOBALS['TCA'][$table]['ctrl']['sortby'] : ''))) {
-                $movePlhID = $plhRec['uid'];
-                $movePlh['pid'] = $swapVersion['pid'];
-                $swapVersion['pid'] = (int)$plhRec['pid'];
-                $curVersion['t3ver_state'] = (int)$swapVersion['t3ver_state'];
-                $swapVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
-                if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
-                    // sortby is a "keepFields" which is why this will work...
-                    $movePlh[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
-                    $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $plhRec[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
-                }
-            }
-        }
-        // Take care of relations in each field (e.g. IRRE):
-        if (is_array($GLOBALS['TCA'][$table]['columns'])) {
-            foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
-                $this->version_swap_processFields($table, $field, $fieldConf['config'], $curVersion, $swapVersion, $dataHandler);
-            }
-        }
-        unset($swapVersion['uid']);
-        // Modify online version to become offline:
-        unset($curVersion['uid']);
-        // Set pid for OFFLINE
-        $curVersion['pid'] = -1;
-        $curVersion['t3ver_oid'] = (int)$id;
-        $curVersion['t3ver_wsid'] = $swapIntoWS ? (int)$tmp_wsid : 0;
-        $curVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
-        $curVersion['t3ver_count'] = $curVersion['t3ver_count'] + 1;
-        // Increment lifecycle counter
-        $curVersion['t3ver_stage'] = 0;
-        if (!$swapIntoWS) {
-            $curVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
-        }
-        // Registering and swapping MM relations in current and swap records:
-        $dataHandler->version_remapMMForVersionSwap($table, $id, $swapWith);
-        // Generating proper history data to prepare logging
-        $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
-        $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
-
-        // Execute swapping:
-        $sqlErrors = [];
-        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
-
-        $platform = $connection->getDatabasePlatform();
-        $tableDetails = null;
-        if ($platform instanceof SQLServerPlatform) {
-            // mssql needs to set proper PARAM_LOB and others to update fields
-            $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
-        }
-
-        try {
-            $types = [];
-
-            if ($platform instanceof SQLServerPlatform) {
-                foreach ($curVersion as $columnName => $columnValue) {
-                    $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
-                }
-            }
-
-            $connection->update(
-                $table,
-                $swapVersion,
-                ['uid' => (int)$id],
-                $types
-            );
-        } catch (DBALException $e) {
-            $sqlErrors[] = $e->getPrevious()->getMessage();
-        }
-
-        if (empty($sqlErrors)) {
-            try {
-                $types = [];
-                if ($platform instanceof SQLServerPlatform) {
-                    foreach ($curVersion as $columnName => $columnValue) {
-                        $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
-                    }
-                }
-
-                $connection->update(
-                    $table,
-                    $curVersion,
-                    ['uid' => (int)$swapWith],
-                    $types
-                );
-                unlink($lockFileName);
-            } catch (DBALException $e) {
-                $sqlErrors[] = $e->getPrevious()->getMessage();
-            }
-        }
-
-        if (!empty($sqlErrors)) {
-            $dataHandler->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
-        } else {
-            // Register swapped ids for later remapping:
-            $this->remappedIds[$table][$id] = $swapWith;
-            $this->remappedIds[$table][$swapWith] = $id;
-            // If a moving operation took place...:
-            if ($movePlhID) {
-                // Remove, if normal publishing:
-                if (!$swapIntoWS) {
-                    // For delete + completely delete!
-                    $dataHandler->deleteEl($table, $movePlhID, true, true);
-                } else {
-                    // Otherwise update the movePlaceholder:
-                    GeneralUtility::makeInstance(ConnectionPool::class)
-                        ->getConnectionForTable($table)
-                        ->update(
-                            $table,
-                            $movePlh,
-                            ['uid' => (int)$movePlhID]
-                        );
-                    $dataHandler->addRemapStackRefIndex($table, $movePlhID);
-                }
-            }
-            // Checking for delete:
-            // Delete only if new/deleted placeholders are there.
-            if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === 1 || (int)$t3ver_state['swapVersion'] === 2)) {
-                // Force delete
-                $dataHandler->deleteEl($table, $id, true);
-            }
-            $dataHandler->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']);
-            // Update reference index of the live record:
-            $dataHandler->addRemapStackRefIndex($table, $id);
-            // Set log entry for live record:
-            $propArr = $dataHandler->getRecordPropertiesFromRow($table, $swapVersion);
-            if ($propArr['_ORIG_pid'] == -1) {
-                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
-            } else {
-                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
-            }
-            $theLogId = $dataHandler->log($table, $id, 2, $propArr['pid'], 0, $label, 10, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
-            $dataHandler->setHistory($table, $id, $theLogId);
-            // Update reference index of the offline record:
-            $dataHandler->addRemapStackRefIndex($table, $swapWith);
-            // Set log entry for offline record:
-            $propArr = $dataHandler->getRecordPropertiesFromRow($table, $curVersion);
-            if ($propArr['_ORIG_pid'] == -1) {
-                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
-            } else {
-                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
-            }
-            $theLogId = $dataHandler->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, [$propArr['header'], $table . ':' . $swapWith], $propArr['event_pid']);
-            $dataHandler->setHistory($table, $swapWith, $theLogId);
-
-            $stageId = -20; // \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID;
-            if ($notificationEmailInfo) {
-                $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
-                $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = [$wsAccess, $stageId, $comment];
-                $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = $table . ':' . $id;
-                $this->notificationEmailInfo[$notificationEmailInfoKey]['alternativeRecipients'] = $notificationAlternativeRecipients;
-            } else {
-                $this->notifyStageChange($wsAccess, $stageId, $table, $id, $comment, $dataHandler, $notificationAlternativeRecipients);
-            }
-            // Write to log with stageId -20
-            $dataHandler->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
-            $dataHandler->log($table, $id, 6, 0, 0, 'Published', 30, ['comment' => $comment, 'stage' => $stageId]);
-
-            // Clear cache:
-            $dataHandler->registerRecordIdForPageCacheClearing($table, $id);
-            // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!):
-            if (!$swapIntoWS && $t3ver_state['curVersion'] > 0) {
-                // For delete + completely delete!
-                $dataHandler->deleteEl($table, $swapWith, true, true);
-            }
-
-            //Update reference index for live workspace too:
-            /** @var $refIndexObj \TYPO3\CMS\Core\Database\ReferenceIndex */
-            $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
-            $refIndexObj->setWorkspaceId(0);
-            $refIndexObj->updateRefIndexTable($table, $id);
-            $refIndexObj->updateRefIndexTable($table, $swapWith);
-        }
-    }
-
-    /**
-     * Writes remapped foreign field (IRRE).
-     *
-     * @param \TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis Instance that holds the sorting order of child records
-     * @param array $configuration The TCA field configuration
-     * @param int $parentId The uid of the parent record
-     */
-    public function writeRemappedForeignField(\TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis, array $configuration, $parentId)
-    {
-        foreach ($dbAnalysis->itemArray as &$item) {
-            if (isset($this->remappedIds[$item['table']][$item['id']])) {
-                $item['id'] = $this->remappedIds[$item['table']][$item['id']];
-            }
-        }
-        $dbAnalysis->writeForeignField($configuration, $parentId);
-    }
-
-    /**
-     * Processes fields of a record for the publishing/swapping process.
-     * Basically this takes care of IRRE (type "inline") child references.
-     *
-     * @param string $tableName Table name
-     * @param string $fieldName: Field name
-     * @param array $configuration TCA field configuration
-     * @param array $liveData: Live record data
-     * @param array $versionData: Version record data
-     * @param DataHandler $dataHandler Calling data-handler object
-     */
-    protected function version_swap_processFields($tableName, $fieldName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
-    {
-        $inlineType = $dataHandler->getInlineFieldType($configuration);
-        if ($inlineType !== 'field') {
-            return;
-        }
-        $foreignTable = $configuration['foreign_table'];
-        // Read relations that point to the current record (e.g. live record):
-        $liveRelations = $this->createRelationHandlerInstance();
-        $liveRelations->setWorkspaceId(0);
-        $liveRelations->start('', $foreignTable, '', $liveData['uid'], $tableName, $configuration);
-        // Read relations that point to the record to be swapped with e.g. draft record):
-        $versionRelations = $this->createRelationHandlerInstance();
-        $versionRelations->setUseLiveReferenceIds(false);
-        $versionRelations->start('', $foreignTable, '', $versionData['uid'], $tableName, $configuration);
-        // Update relations for both (workspace/versioning) sites:
-        if (count($liveRelations->itemArray)) {
-            $dataHandler->addRemapAction(
-                    $tableName,
-                $liveData['uid'],
-                    [$this, 'updateInlineForeignFieldSorting'],
-                    [$tableName, $liveData['uid'], $foreignTable, $liveRelations->tableArray[$foreignTable], $configuration, $dataHandler->BE_USER->workspace]
-            );
-        }
-        if (count($versionRelations->itemArray)) {
-            $dataHandler->addRemapAction(
-                    $tableName,
-                $liveData['uid'],
-                    [$this, 'updateInlineForeignFieldSorting'],
-                    [$tableName, $liveData['uid'], $foreignTable, $versionRelations->tableArray[$foreignTable], $configuration, 0]
-            );
-        }
-    }
-
-    /**
-     * Updates foreign field sorting values of versioned and live
-     * parents after(!) the whole structure has been published.
-     *
-     * This method is used as callback function in
-     * DataHandlerHook::version_swap_procBasedOnFieldType().
-     * Sorting fields ("sortby") are not modified during the
-     * workspace publishing/swapping process directly.
-     *
-     * @param string $parentTableName
-     * @param string $parentId
-     * @param string $foreignTableName
-     * @param int[] $foreignIds
-     * @param array $configuration
-     * @param int $targetWorkspaceId
-     * @internal
-     */
-    public function updateInlineForeignFieldSorting($parentTableName, $parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
-    {
-        $remappedIds = [];
-        // Use remapped ids (live id <-> version id)
-        foreach ($foreignIds as $foreignId) {
-            if (!empty($this->remappedIds[$foreignTableName][$foreignId])) {
-                $remappedIds[] = $this->remappedIds[$foreignTableName][$foreignId];
-            } else {
-                $remappedIds[] = $foreignId;
-            }
-        }
-
-        $relationHandler = $this->createRelationHandlerInstance();
-        $relationHandler->setWorkspaceId($targetWorkspaceId);
-        $relationHandler->setUseLiveReferenceIds(false);
-        $relationHandler->start(implode(',', $remappedIds), $foreignTableName);
-        $relationHandler->processDeletePlaceholder();
-        $relationHandler->writeForeignField($configuration, $parentId);
-    }
-
-    /**
-     * Release version from this workspace (and into "Live" workspace but as an offline version).
-     *
-     * @param string $table Table name
-     * @param int $id Record UID
-     * @param bool $flush If set, will completely delete element
-     * @param DataHandler $dataHandler DataHandler object
-     */
-    protected function version_clearWSID($table, $id, $flush = false, DataHandler $dataHandler)
-    {
-        if ($errorCode = $dataHandler->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
-            $dataHandler->newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1);
-            return;
-        }
-        if (!$dataHandler->checkRecordUpdateAccess($table, $id)) {
-            $dataHandler->newlog('Attempt to reset workspace for record failed because you do not have edit access', 1);
-            return;
-        }
-        $liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
-        if (!$liveRec) {
-            return;
-        }
-        // Clear workspace ID:
-        $updateData = [
-            't3ver_wsid' => 0,
-            't3ver_tstamp' => $GLOBALS['EXEC_TIME']
-        ];
-        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
-        $connection->update(
-            $table,
-            $updateData,
-            ['uid' => (int)$id]
-        );
-
-        // Clear workspace ID for live version AND DELETE IT as well because it is a new record!
-        if (
-            VersionState::cast($liveRec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER)
-            || VersionState::cast($liveRec['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
-        ) {
-            $connection->update(
-                $table,
-                $updateData,
-                ['uid' => (int)$liveRec['uid']]
-            );
-
-            // THIS assumes that the record was placeholder ONLY for ONE record (namely $id)
-            $dataHandler->deleteEl($table, $liveRec['uid'], true);
-        }
-        // If "deleted" flag is set for the version that got released
-        // it doesn't make sense to keep that "placeholder" anymore and we delete it completly.
-        $wsRec = BackendUtility::getRecord($table, $id);
-        if (
-            $flush
-            || (
-                VersionState::cast($wsRec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER)
-                || VersionState::cast($wsRec['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
-            )
-        ) {
-            $dataHandler->deleteEl($table, $id, true, true);
-        }
-        // Remove the move-placeholder if found for live record.
-        if (BackendUtility::isTableWorkspaceEnabled($table)) {
-            if ($plhRec = BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid')) {
-                $dataHandler->deleteEl($table, $plhRec['uid'], true, true);
-            }
-        }
-    }
-
-    /*******************************
-     *****  helper functions  ******
-     *******************************/
-
-    /**
-     * Finds all elements for swapping versions in workspace
-     *
-     * @param string $table Table name of the original element to swap
-     * @param int $id UID of the original element to swap (online)
-     * @param int $offlineId As above but offline
-     * @return array Element data. Key is table name, values are array with first element as online UID, second - offline UID
-     */
-    public function findPageElementsForVersionSwap($table, $id, $offlineId)
-    {
-        $rec = BackendUtility::getRecord($table, $offlineId, 't3ver_wsid');
-        $workspaceId = (int)$rec['t3ver_wsid'];
-        $elementData = [];
-        if ($workspaceId === 0) {
-            return $elementData;
-        }
-        // Get page UID for LIVE and workspace
-        if ($table !== 'pages') {
-            $rec = BackendUtility::getRecord($table, $id, 'pid');
-            $pageId = $rec['pid'];
-            $rec = BackendUtility::getRecord('pages', $pageId);
-            BackendUtility::workspaceOL('pages', $rec, $workspaceId);
-            $offlinePageId = $rec['_ORIG_uid'];
-        } else {
-            $pageId = $id;
-            $offlinePageId = $offlineId;
-        }
-        // Traversing all tables supporting versioning:
-        foreach ($GLOBALS['TCA'] as $table => $cfg) {
-            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
-                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                    ->getQueryBuilderForTable($table);
-
-                $queryBuilder->getRestrictions()
-                    ->removeAll()
-                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
-
-                $statement = $queryBuilder
-                    ->select('A.uid AS offlineUid', 'B.uid AS uid')
-                    ->from($table, 'A')
-                    ->from($table, 'B')
-                    ->where(
-                        $queryBuilder->expr()->eq(
-                            'A.pid',
-                            $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
-                        ),
-                        $queryBuilder->expr()->eq(
-                            'B.pid',
-                            $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
-                        ),
-                        $queryBuilder->expr()->eq(
-                            'A.t3ver_wsid',
-                            $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
-                        ),
-                        $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
-                    )
-                    ->execute();
-
-                while ($row = $statement->fetch()) {
-                    $elementData[$table][] = [$row['uid'], $row['offlineUid']];
-                }
-            }
-        }
-        if ($offlinePageId && $offlinePageId != $pageId) {
-            $elementData['pages'][] = [$pageId, $offlinePageId];
-        }
-
-        return $elementData;
-    }
-
-    /**
-     * Searches for all elements from all tables on the given pages in the same workspace.
-     *
-     * @param array $pageIdList List of PIDs to search
-     * @param int $workspaceId Workspace ID
-     * @param array $elementList List of found elements. Key is table name, value is array of element UIDs
-     */
-    public function findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
-    {
-        if ($workspaceId == 0) {
-            return;
-        }
-        // Traversing all tables supporting versioning:
-        foreach ($GLOBALS['TCA'] as $table => $cfg) {
-            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
-                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                    ->getQueryBuilderForTable($table);
-
-                $queryBuilder->getRestrictions()
-                    ->removeAll()
-                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
-
-                $statement = $queryBuilder
-                    ->select('A.uid')
-                    ->from($table, 'A')
-                    ->from($table, 'B')
-                    ->where(
-                        $queryBuilder->expr()->eq(
-                            'A.pid',
-                            $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
-                        ),
-                        $queryBuilder->expr()->in(
-                            'B.pid',
-                            $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
-                        ),
-                        $queryBuilder->expr()->eq(
-                            'A.t3ver_wsid',
-                            $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
-                        ),
-                        $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
-                    )
-                    ->groupBy('A.uid')
-                    ->execute();
-
-                while ($row = $statement->fetch()) {
-                    $elementList[$table][] = $row['uid'];
-                }
-                if (is_array($elementList[$table])) {
-                    // Yes, it is possible to get non-unique array even with DISTINCT above!
-                    // It happens because several UIDs are passed in the array already.
-                    $elementList[$table] = array_unique($elementList[$table]);
-                }
-            }
-        }
-    }
-
-    /**
-     * Finds page UIDs for the element from table <code>$table</code> with UIDs from <code>$idList</code>
-     *
-     * @param string $table Table to search
-     * @param array $idList List of records' UIDs
-     * @param int $workspaceId Workspace ID. We need this parameter because user can be in LIVE but he still can publisg DRAFT from ws module!
-     * @param array $pageIdList List of found page UIDs
-     * @param array $elementList List of found element UIDs. Key is table name, value is list of UIDs
-     */
-    public function findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
-    {
-        if ($workspaceId == 0) {
-            return;
-        }
-
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable($table);
-        $queryBuilder->getRestrictions()
-            ->removeAll()
-            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
-
-        $statement = $queryBuilder
-            ->select('B.pid')
-            ->from($table, 'A')
-            ->from($table, 'B')
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'A.pid',
-                    $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
-                ),
-                $queryBuilder->expr()->eq(
-                    'A.t3ver_wsid',
-                    $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
-                ),
-                $queryBuilder->expr()->in(
-                    'A.uid',
-                    $queryBuilder->createNamedParameter($idList, Connection::PARAM_INT_ARRAY)
-                ),
-                $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
-            )
-            ->groupBy('B.pid')
-            ->execute();
-
-        while ($row = $statement->fetch()) {
-            $pageIdList[] = $row['pid'];
-            // Find ws version
-            // Note: cannot use BackendUtility::getRecordWSOL()
-            // here because it does not accept workspace id!
-            $rec = BackendUtility::getRecord('pages', $row[0]);
-            BackendUtility::workspaceOL('pages', $rec, $workspaceId);
-            if ($rec['_ORIG_uid']) {
-                $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
-            }
-        }
-        // The line below is necessary even with DISTINCT
-        // because several elements can be passed by caller
-        $pageIdList = array_unique($pageIdList);
-    }
-
-    /**
-     * Finds real page IDs for state change.
-     *
-     * @param array $idList List of page UIDs, possibly versioned
-     */
-    public function findRealPageIds(array &$idList)
-    {
-        foreach ($idList as $key => $id) {
-            $rec = BackendUtility::getRecord('pages', $id, 't3ver_oid');
-            if ($rec['t3ver_oid'] > 0) {
-                $idList[$key] = $rec['t3ver_oid'];
-            }
-        }
-    }
-
-    /**
-     * Creates a move placeholder for workspaces.
-     * USE ONLY INTERNALLY
-     * Moving placeholder: Can be done because the system sees it as a placeholder for NEW elements like t3ver_state=VersionState::NEW_PLACEHOLDER
-     * Moving original: Will either create the placeholder if it doesn't exist or move existing placeholder in workspace.
-     *
-     * @param string $table Table name to move
-     * @param int $uid Record uid to move (online record)
-     * @param int $destPid Position to move to: $destPid: >=0 then it points to a page-id on which to insert the record (as the first element). <0 then it points to a uid from its own table after which to insert it (works if
-     * @param int $wsUid UID of offline version of online record
-     * @param DataHandler $dataHandler DataHandler object
-     * @see moveRecord()
-     */
-    protected function moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, DataHandler $dataHandler)
-    {
-        // If a record gets moved after a record that already has a placeholder record
-        // then the new placeholder record needs to be after the existing one
-        $originalRecordDestinationPid = $destPid;
-        if ($destPid < 0) {
-            $movePlaceHolder = BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid');
-            if ($movePlaceHolder !== false) {
-                $destPid = -$movePlaceHolder['uid'];
-            }
-        }
-        if ($plh = BackendUtility::getMovePlaceholder($table, $uid, 'uid')) {
-            // If already a placeholder exists, move it:
-            $dataHandler->moveRecord_raw($table, $plh['uid'], $destPid);
-        } else {
-            // First, we create a placeholder record in the Live workspace that
-            // represents the position to where the record is eventually moved to.
-            $newVersion_placeholderFieldArray = [];
-
-            // Use property for move placeholders if set (since TYPO3 CMS 6.2)
-            if (isset($GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForMovePlaceholders'])) {
-                $shadowColumnsForMovePlaceholder = $GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForMovePlaceholders'];
-            } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForNewPlaceholders'])) {
-                // Fallback to property for new placeholder (existed long time before TYPO3 CMS 6.2)
-                $shadowColumnsForMovePlaceholder = $GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForNewPlaceholders'];
-            }
-
-            // Set values from the versioned record to the move placeholder
-            if (!empty($shadowColumnsForMovePlaceholder)) {
-                $versionedRecord = BackendUtility::getRecord($table, $wsUid);
-                $shadowColumns = GeneralUtility::trimExplode(',', $shadowColumnsForMovePlaceholder, true);
-                foreach ($shadowColumns as $shadowColumn) {
-                    if (isset($versionedRecord[$shadowColumn])) {
-                        $newVersion_placeholderFieldArray[$shadowColumn] = $versionedRecord[$shadowColumn];
-                    }
-                }
-            }
-
-            if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
-                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
-            }
-            if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
-                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $dataHandler->userid;
-            }
-            if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
-                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
-            }
-            if ($table === 'pages') {
-                // Copy page access settings from original page to placeholder
-                $perms_clause = $dataHandler->BE_USER->getPagePermsClause(1);
-                $access = BackendUtility::readPageAccess($uid, $perms_clause);
-                $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid'];
-                $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid'];
-                $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user'];
-                $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group'];
-                $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody'];
-            }
-            $newVersion_placeholderFieldArray['t3ver_label'] = 'MovePlaceholder #' . $uid;
-            $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
-            // Setting placeholder state value for temporary record
-            $newVersion_placeholderFieldArray['t3ver_state'] = (string)new VersionState(VersionState::MOVE_PLACEHOLDER);
-            // Setting workspace - only so display of place holders can filter out those from other workspaces.
-            $newVersion_placeholderFieldArray['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
-            $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['label']] = $dataHandler->getPlaceholderTitleForTableLabel($table, 'MOVE-TO PLACEHOLDER for #' . $uid);
-            // moving localized records requires to keep localization-settings for the placeholder too
-            if (isset($GLOBALS['TCA'][$table]['ctrl']['languageField']) && isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
-                $l10nParentRec = BackendUtility::getRecord($table, $uid);
-                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
-                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
-                if (isset($GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'])) {
-                    $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']];
-                }
-                unset($l10nParentRec);
-            }
-            // Initially, create at root level.
-            $newVersion_placeholderFieldArray['pid'] = 0;
-            $id = 'NEW_MOVE_PLH';
-            // Saving placeholder as 'original'
-            $dataHandler->insertDB($table, $id, $newVersion_placeholderFieldArray, false);
-            // Move the new placeholder from temporary root-level to location:
-            $dataHandler->moveRecord_raw($table, $dataHandler->substNEWwithIDs[$id], $destPid);
-            // Move the workspace-version of the original to be the version of the move-to-placeholder:
-            // Setting placeholder state value for version (so it can know it is currently a new version...)
-            $updateFields = [
-                't3ver_state' => (string)new VersionState(VersionState::MOVE_POINTER)
-            ];
-
-            GeneralUtility::makeInstance(ConnectionPool::class)
-                ->getConnectionForTable($table)
-                ->update(
-                    $table,
-                    $updateFields,
-                    ['uid' => (int)$wsUid]
-                );
-        }
-        // Check for the localizations of that element and move them as well
-        $dataHandler->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
-    }
-
-    /**
-     * Gets an instance of the command map helper.
-     *
-     * @param DataHandler $dataHandler DataHandler object
-     * @return \TYPO3\CMS\Version\DataHandler\CommandMap
-     */
-    public function getCommandMap(DataHandler $dataHandler)
-    {
-        return GeneralUtility::makeInstance(
-            \TYPO3\CMS\Version\DataHandler\CommandMap::class,
-            $this,
-            $dataHandler,
-            $dataHandler->cmdmap,
-            $dataHandler->BE_USER->workspace
-        );
-    }
-
-    /**
-     * Returns all fieldnames from a table which have the unique evaluation type set.
-     *
-     * @param string $table Table name
-     * @return array Array of fieldnames
-     */
-    protected function getUniqueFields($table)
-    {
-        $listArr = [];
-        if (empty($GLOBALS['TCA'][$table]['columns'])) {
-            return $listArr;
-        }
-        foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
-            if ($configArr['config']['type'] === 'input') {
-                $evalCodesArray = GeneralUtility::trimExplode(',', $configArr['config']['eval'], true);
-                if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
-                    $listArr[] = $field;
-                }
-            }
-        }
-        return $listArr;
-    }
-
-    /**
-     * @return \TYPO3\CMS\Core\Database\RelationHandler
-     */
-    protected function createRelationHandlerInstance()
-    {
-        return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
-    }
-
-    /**
-     * @return LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-}
diff --git a/typo3/sysext/version/LICENSE.txt b/typo3/sysext/version/LICENSE.txt
deleted file mode 100644
index 95d36a78ffce9a9ad8c4000f87603a76dd17e67e..0000000000000000000000000000000000000000
--- a/typo3/sysext/version/LICENSE.txt
+++ /dev/null
@@ -1,345 +0,0 @@
-Some icons used in the TYPO3 project are retrieved from the "Silk" icon set of
-Mark James, which can be found at http://famfamfam.com/lab/icons/silk/. This
-set is distributed under a Creative Commons Attribution 2.5 License. The
-license can be found at http://creativecommons.org/licenses/by/2.5/.
----------------------------------
-
-                    GNU GENERAL PUBLIC LICENSE
-                       Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-                            Preamble
-
-  The licenses for most software are designed to take away your
-freedom to share and change it.  By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users.  This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it.  (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.)  You can apply it to
-your programs, too.
-
-  When we speak of free software, we are referring to freedom, not
-price.  Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
-  To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
-  For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have.  You must make sure that they, too, receive or can get the
-source code.  And you must show them these terms so they know their
-rights.
-
-  We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
-  Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software.  If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
-  Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary.  To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
-  The precise terms and conditions for copying, distribution and
-modification follow.
-
-                    GNU GENERAL PUBLIC LICENSE
-   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-  0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License.  The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language.  (Hereinafter, translation is included without limitation in
-the term "modification".)  Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
-  1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
-  2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
-    a) You must cause the modified files to carry prominent notices
-    stating that you changed the files and the date of any change.
-
-    b) You must cause any work that you distribute or publish, that in
-    whole or in part contains or is derived from the Program or any
-    part thereof, to be licensed as a whole at no charge to all third
-    parties under the terms of this License.
-
-    c) If the modified program normally reads commands interactively
-    when run, you must cause it, when started running for such
-    interactive use in the most ordinary way, to print or display an
-    announcement including an appropriate copyright notice and a
-    notice that there is no warranty (or else, saying that you provide
-    a warranty) and that users may redistribute the program under
-    these conditions, and telling the user how to view a copy of this
-    License.  (Exception: if the Program itself is interactive but
-    does not normally print such an announcement, your work based on
-    the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works.  But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
-  3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
-    a) Accompany it with the complete corresponding machine-readable
-    source code, which must be distributed under the terms of Sections
-    1 and 2 above on a medium customarily used for software interchange; or,
-
-    b) Accompany it with a written offer, valid for at least three
-    years, to give any third party, for a charge no more than your
-    cost of physically performing source distribution, a complete
-    machine-readable copy of the corresponding source code, to be
-    distributed under the terms of Sections 1 and 2 above on a medium
-    customarily used for software interchange; or,
-
-    c) Accompany it with the information you received as to the offer
-    to distribute corresponding source code.  (This alternative is
-    allowed only for noncommercial distribution and only if you
-    received the program in object code or executable form with such
-    an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it.  For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable.  However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
-  4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License.  Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-  5. You are not required to accept this License, since you have not
-signed it.  However, nothing else grants you permission to modify or
-distribute the Program or its derivative works.  These actions are
-prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-  6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions.  You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
-  7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License.  If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices.  Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
-  8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded.  In such case, this License incorporates
-the limitation as if written in the body of this License.
-
-  9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time.  Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number.  If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation.  If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
-  10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission.  For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this.  Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
-                            NO WARRANTY
-
-  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
-  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
-                     END OF TERMS AND CONDITIONS
-
-            How to Apply These Terms to Your New Programs
-
-  If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
-  To do so, attach the following notices to the program.  It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-    <one line to give the program's name and a brief idea of what it does.>
-    Copyright (C) <year>  <name of author>
-
-    This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License along
-    with this program; if not, write to the Free Software Foundation, Inc.,
-    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
-    Gnomovision version 69, Copyright (C) year name of author
-    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
-    This is free software, and you are welcome to redistribute it
-    under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License.  Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary.  Here is a sample; alter the names:
-
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
-  `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
-  <signature of Ty Coon>, 1 April 1989
-  Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs.  If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library.  If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
diff --git a/typo3/sysext/version/Migrations/Code/ClassAliasMap.php b/typo3/sysext/version/Migrations/Code/ClassAliasMap.php
deleted file mode 100644
index c8ad25d385de5914ed8e5368e6a954d5f13952e4..0000000000000000000000000000000000000000
--- a/typo3/sysext/version/Migrations/Code/ClassAliasMap.php
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-return [
-    'TYPO3\\CMS\\Version\\Hook\\PreviewHook' => \TYPO3\CMS\Workspaces\Hook\PreviewHook::class,
-    'TYPO3\\CMS\\Version\\Task\\AutoPublishTask' => \TYPO3\CMS\Workspaces\Task\AutoPublishTask::class,
-    'TYPO3\\CMS\\Version\\Utility\\WorkspacesUtility' => \TYPO3\CMS\Workspaces\Service\WorkspaceService::class
-];
diff --git a/typo3/sysext/version/Resources/Public/Icons/Extension.png b/typo3/sysext/version/Resources/Public/Icons/Extension.png
deleted file mode 100644
index 3a7dfc08452a75bf5b2401b783c40a63e51a4324..0000000000000000000000000000000000000000
Binary files a/typo3/sysext/version/Resources/Public/Icons/Extension.png and /dev/null differ
diff --git a/typo3/sysext/version/Resources/Public/Icons/module-version.svg b/typo3/sysext/version/Resources/Public/Icons/module-version.svg
deleted file mode 100644
index a33d3429cfa817435aba8157f4501068f53284fd..0000000000000000000000000000000000000000
--- a/typo3/sysext/version/Resources/Public/Icons/module-version.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#7A950F" d="M0 0h64v64H0z"/><g fill="#FFF"><path fill-rule="evenodd" clip-rule="evenodd" d="M47.2 48H36.8c-1.6 0-2.9-1.3-2.9-2.9v-5.3l3.2 3.2c.7.7 1.6 1.1 2.6 1.1s1.9-.4 2.6-1.1l5.4-5.4c.7-.7 1.1-1.6 1.1-2.6v-.4c.7.5 1.2 1.4 1.2 2.3v8.3c0 1.5-1.3 2.8-2.8 2.8zm0-18h-1.8c-.4-5.3-2.4-8.7-6.1-10.2-1.3-.5-2.8-.8-4.6-.8-.2 0-.5 0-.7.1v-.2c0-1.6 1.3-2.9 2.9-2.9h10.3c1.6 0 2.9 1.3 2.9 2.9v8.3c-.1 1.5-1.4 2.8-2.9 2.8zm-18.6-9.3l-.1.1c-.3.1-.7.3-1.1.6-.9.6-1.5 1.7-1.5 2.8 0 1.8 1.5 3.3 3.4 3.3.2 0 .4 0 .7-.1-.1 1.4-1.4 2.6-2.8 2.6H16.8c-1.6 0-2.9-1.3-2.9-2.9v-8.3c0-1.6 1.3-2.9 2.9-2.9h10.3c1.6 0 2.9 1.3 2.9 2.9V20c-.4.3-.9.5-1.4.7zM16.8 34h10.3c1.6 0 2.9 1.3 2.9 2.9v8.3c0 1.6-1.3 2.9-2.9 2.9H16.8c-1.6 0-2.9-1.3-2.9-2.9v-8.3c.1-1.6 1.4-2.9 2.9-2.9z"/><path d="M34.7 22c1.2 0 2.4.1 3.5.6 3.7 1.5 4.3 5.8 4.3 9.3v2.4h2.7c.4 0 .7.3.7.7 0 .2-.1.4-.2.5l-5.4 5.4c-.2 0-.3.1-.5.1s-.4-.1-.5-.2l-5.4-5.4c-.1-.1-.2-.3-.2-.5 0-.4.3-.7.7-.7h2.7v-2.4c0-4.6-.9-7.6-5.9-7.6-.4 0-.9 0-1.3.1-.2 0-.4.1-.5.1-.2 0-.4-.1-.4-.3 0-.1.1-.2.2-.3.2-.2.6-.3.8-.4 1.2-.7 3.2-1.4 4.7-1.4z"/></g></svg>
\ No newline at end of file
diff --git a/typo3/sysext/version/composer.json b/typo3/sysext/version/composer.json
deleted file mode 100644
index fd7dda315048de32df49ceb69f758cda4c561916..0000000000000000000000000000000000000000
--- a/typo3/sysext/version/composer.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
-	"name": "typo3/cms-version",
-	"type": "typo3-cms-framework",
-	"description": "Backend Interface for management of the versioning API.",
-	"homepage": "https://typo3.org",
-	"license": ["GPL-2.0+"],
-	"authors": [{
-			"name": "TYPO3 Core Team",
-			"email": "typo3cms@typo3.org",
-			"role": "Developer"
-		}],
-
-	"require": {
-		"typo3/cms-core": ">=9.0.0 <=9.0.99"
-	},
-	"conflict": {
-		"typo3/cms": "*"
-	},
-	"replace": {
-		"version": "*"
-	},
-	"extra": {
-		"branch-alias": {
-			"dev-master": "9.x-dev"
-		},
-		"typo3/class-alias-loader": {
-			"class-alias-maps": [
-				"Migrations/Code/ClassAliasMap.php"
-			]
-		},
-		"typo3/cms": {
-			"extension-key": "version"
-		}
-	},
-	"autoload": {
-		"psr-4": {
-			"TYPO3\\CMS\\Version\\": "Classes/"
-		}
-	}
-}
diff --git a/typo3/sysext/version/ext_emconf.php b/typo3/sysext/version/ext_emconf.php
deleted file mode 100644
index 51f975230393909e8eb10ab0a03c78484fbb9162..0000000000000000000000000000000000000000
--- a/typo3/sysext/version/ext_emconf.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-$EM_CONF[$_EXTKEY] = [
-    'title' => 'Versioning Management',
-    'description' => 'Backend Interface for management of the versioning API.',
-    'category' => 'be',
-    'author' => 'TYPO3 Core Team',
-    'author_email' => 'typo3cms@typo3.org',
-    'author_company' => '',
-    'state' => 'stable',
-    'uploadfolder' => 0,
-    'createDirs' => '',
-    'clearCacheOnLoad' => 0,
-    'version' => '9.0.0',
-    'constraints' => [
-        'depends' => [
-            'typo3' => '9.0.0-9.0.99',
-        ],
-        'conflicts' => [],
-        'suggests' => [],
-    ],
-];
diff --git a/typo3/sysext/version/ext_localconf.php b/typo3/sysext/version/ext_localconf.php
deleted file mode 100644
index 758122964e943b0fee27c6902551e69f023aa534..0000000000000000000000000000000000000000
--- a/typo3/sysext/version/ext_localconf.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-defined('TYPO3_MODE') or die();
-
-// register the hook to actually do the work within DataHandler
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['version'] = \TYPO3\CMS\Version\Hook\DataHandlerHook::class;
-$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass']['version'] = \TYPO3\CMS\Version\Hook\DataHandlerHook::class;
-
-// add default notification options to every page
-\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig('
-tx_version.workspaces.stageNotificationEmail.subject = LLL:EXT:version/Resources/Private/Language/locallang_emails.xlf:subject
-tx_version.workspaces.stageNotificationEmail.message = LLL:EXT:version/Resources/Private/Language/locallang_emails.xlf:message
-# tx_version.workspaces.stageNotificationEmail.additionalHeaders =
-');
diff --git a/typo3/sysext/version/Classes/DataHandler/CommandMap.php b/typo3/sysext/workspaces/Classes/DataHandler/CommandMap.php
similarity index 93%
rename from typo3/sysext/version/Classes/DataHandler/CommandMap.php
rename to typo3/sysext/workspaces/Classes/DataHandler/CommandMap.php
index 3f2315f912e6ff3e8d8562f310e31747e5eb93cf..7028053fbe7e6d7808d59cf936a78e03ecb00c81 100644
--- a/typo3/sysext/version/Classes/DataHandler/CommandMap.php
+++ b/typo3/sysext/workspaces/Classes/DataHandler/CommandMap.php
@@ -1,5 +1,5 @@
 <?php
-namespace TYPO3\CMS\Version\DataHandler;
+namespace TYPO3\CMS\Workspaces\DataHandler;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -16,7 +16,7 @@ namespace TYPO3\CMS\Version\DataHandler;
 
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Version\Dependency\ElementEntity;
+use TYPO3\CMS\Workspaces\Dependency\ElementEntity;
 
 /**
  * Handles the \TYPO3\CMS\Core\DataHandling\DataHandler command map and is
@@ -36,7 +36,7 @@ class CommandMap
     const KEY_TransformDependentElementsToUseLiveId = 'KEY_TransformDependentElementsToUseLiveId';
 
     /**
-     * @var \TYPO3\CMS\Version\Hook\DataHandlerHook
+     * @var \TYPO3\CMS\Workspaces\Hook\DataHandlerHook
      */
     protected $parent;
 
@@ -71,19 +71,19 @@ class CommandMap
     protected $scopes;
 
     /**
-     * @var \TYPO3\CMS\Version\Dependency\ElementEntityProcessor
+     * @var \TYPO3\CMS\Workspaces\Dependency\ElementEntityProcessor
      */
     protected $elementEntityProcessor;
 
     /**
      * Creates this object.
      *
-     * @param \TYPO3\CMS\Version\Hook\DataHandlerHook $parent
+     * @param \TYPO3\CMS\Workspaces\Hook\DataHandlerHook $parent
      * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain
      * @param array $commandMap
      * @param int $workspace
      */
-    public function __construct(\TYPO3\CMS\Version\Hook\DataHandlerHook $parent, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain, array $commandMap, $workspace)
+    public function __construct(\TYPO3\CMS\Workspaces\Hook\DataHandlerHook $parent, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain, array $commandMap, $workspace)
     {
         $this->setParent($parent);
         $this->setTceMain($tceMain);
@@ -108,7 +108,7 @@ class CommandMap
      * Sets the command map.
      *
      * @param array $commandMap
-     * @return \TYPO3\CMS\Version\DataHandler\CommandMap
+     * @return \TYPO3\CMS\Workspaces\DataHandler\CommandMap
      */
     public function set(array $commandMap)
     {
@@ -119,7 +119,7 @@ class CommandMap
     /**
      * Gets the parent object.
      *
-     * @return \TYPO3\CMS\Version\Hook\DataHandlerHook
+     * @return \TYPO3\CMS\Workspaces\Hook\DataHandlerHook
      */
     public function getParent()
     {
@@ -129,10 +129,10 @@ class CommandMap
     /**
      * Sets the parent object.
      *
-     * @param \TYPO3\CMS\Version\Hook\DataHandlerHook $parent
-     * @return \TYPO3\CMS\Version\DataHandler\CommandMap
+     * @param \TYPO3\CMS\Workspaces\Hook\DataHandlerHook $parent
+     * @return \TYPO3\CMS\Workspaces\DataHandler\CommandMap
      */
-    public function setParent(\TYPO3\CMS\Version\Hook\DataHandlerHook $parent)
+    public function setParent(\TYPO3\CMS\Workspaces\Hook\DataHandlerHook $parent)
     {
         $this->parent = $parent;
         return $this;
@@ -152,7 +152,7 @@ class CommandMap
      * Sets the parent object.
      *
      * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain
-     * @return \TYPO3\CMS\Version\DataHandler\CommandMap
+     * @return \TYPO3\CMS\Workspaces\DataHandler\CommandMap
      */
     public function setTceMain(\TYPO3\CMS\Core\DataHandling\DataHandler $tceMain)
     {
@@ -185,7 +185,7 @@ class CommandMap
      * (see options.workspaces.swapMode).
      *
      * @param string $workspacesSwapMode
-     * @return \TYPO3\CMS\Version\DataHandler\CommandMap
+     * @return \TYPO3\CMS\Workspaces\DataHandler\CommandMap
      */
     public function setWorkspacesSwapMode($workspacesSwapMode)
     {
@@ -198,7 +198,7 @@ class CommandMap
      * see options.workspaces.changeStageMode)
      *
      * @param string $workspacesChangeStageMode
-     * @return \TYPO3\CMS\Version\DataHandler\CommandMap
+     * @return \TYPO3\CMS\Workspaces\DataHandler\CommandMap
      */
     public function setWorkspacesChangeStageMode($workspacesChangeStageMode)
     {
@@ -209,13 +209,13 @@ class CommandMap
     /**
      * Gets the element entity processor.
      *
-     * @return \TYPO3\CMS\Version\Dependency\ElementEntityProcessor
+     * @return \TYPO3\CMS\Workspaces\Dependency\ElementEntityProcessor
      */
     protected function getElementEntityProcessor()
     {
         if (!isset($this->elementEntityProcessor)) {
             $this->elementEntityProcessor = GeneralUtility::makeInstance(
-                \TYPO3\CMS\Version\Dependency\ElementEntityProcessor::class
+                \TYPO3\CMS\Workspaces\Dependency\ElementEntityProcessor::class
             );
             $this->elementEntityProcessor->setWorkspace($this->getWorkspace());
         }
@@ -225,7 +225,7 @@ class CommandMap
     /**
      * Processes the command map.
      *
-     * @return \TYPO3\CMS\Version\DataHandler\CommandMap
+     * @return \TYPO3\CMS\Workspaces\DataHandler\CommandMap
      */
     public function process()
     {
@@ -303,12 +303,12 @@ class CommandMap
     /**
      * Adds workspaces elements for swapping/publishing.
      *
-     * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
+     * @param \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency
      * @param string $table
      * @param int $liveId
      * @param array $properties
      */
-    protected function addWorkspacesSwapElements(\TYPO3\CMS\Version\Dependency\DependencyResolver $dependency, $table, $liveId, array $properties)
+    protected function addWorkspacesSwapElements(\TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency, $table, $liveId, array $properties)
     {
         $elementList = [];
         // Fetch accordant elements if the swapMode is 'any' or 'pages':
@@ -408,12 +408,12 @@ class CommandMap
     /**
      * Adds workspaces elements for staging.
      *
-     * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
+     * @param \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency
      * @param string $table
      * @param string $versionId
      * @param array $properties
      */
-    protected function addWorkspacesSetStageElements(\TYPO3\CMS\Version\Dependency\DependencyResolver $dependency, $table, $versionId, array $properties)
+    protected function addWorkspacesSetStageElements(\TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency, $table, $versionId, array $properties)
     {
         $dependency->addElement($table, $versionId, ['versionId' => $versionId, 'properties' => $properties]);
     }
@@ -468,10 +468,10 @@ class CommandMap
      * Applies the workspaces dependencies and removes incomplete structures or automatically
      * completes them
      *
-     * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
+     * @param \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency
      * @param string $scope
      */
-    protected function applyWorkspacesDependencies(\TYPO3\CMS\Version\Dependency\DependencyResolver $dependency, $scope)
+    protected function applyWorkspacesDependencies(\TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency, $scope)
     {
         $transformDependentElementsToUseLiveId = $this->getScopeData($scope, self::KEY_TransformDependentElementsToUseLiveId);
         $elementsToBeVersioned = $dependency->getElements();
@@ -680,12 +680,12 @@ class CommandMap
      * Gets an instance of the depency resolver utility.
      *
      * @param string $scope Scope identifier
-     * @return \TYPO3\CMS\Version\Dependency\DependencyResolver
+     * @return \TYPO3\CMS\Workspaces\Dependency\DependencyResolver
      */
     protected function getDependencyUtility($scope)
     {
-        /** @var $dependency \TYPO3\CMS\Version\Dependency\DependencyResolver */
-        $dependency = GeneralUtility::makeInstance(\TYPO3\CMS\Version\Dependency\DependencyResolver::class);
+        /** @var $dependency \TYPO3\CMS\Workspaces\Dependency\DependencyResolver */
+        $dependency = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Dependency\DependencyResolver::class);
         $dependency->setWorkspace($this->getWorkspace());
         $dependency->setOuterMostParentsRequireReferences(true);
         if ($this->getScopeData($scope, self::KEY_ElementConstructCallback)) {
@@ -782,12 +782,12 @@ class CommandMap
      *
      * @param string $method
      * @param array $targetArguments
-     * @return \TYPO3\CMS\Version\Dependency\EventCallback
+     * @return \TYPO3\CMS\Workspaces\Dependency\EventCallback
      */
     protected function getDependencyCallback($method, array $targetArguments = [])
     {
         return GeneralUtility::makeInstance(
-            \TYPO3\CMS\Version\Dependency\EventCallback::class,
+            \TYPO3\CMS\Workspaces\Dependency\EventCallback::class,
             $this->getElementEntityProcessor(),
             $method,
             $targetArguments
diff --git a/typo3/sysext/version/Classes/Dependency/DependencyEntityFactory.php b/typo3/sysext/workspaces/Classes/Dependency/DependencyEntityFactory.php
similarity index 68%
rename from typo3/sysext/version/Classes/Dependency/DependencyEntityFactory.php
rename to typo3/sysext/workspaces/Classes/Dependency/DependencyEntityFactory.php
index 78660e396d367aca72d3fabb74c61339c942d740..95e90ca8d960c44e8a912f89af6b5fb2764f0237 100644
--- a/typo3/sysext/version/Classes/Dependency/DependencyEntityFactory.php
+++ b/typo3/sysext/workspaces/Classes/Dependency/DependencyEntityFactory.php
@@ -1,5 +1,5 @@
 <?php
-namespace TYPO3\CMS\Version\Dependency;
+namespace TYPO3\CMS\Workspaces\Dependency;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -35,13 +35,13 @@ class DependencyEntityFactory
      * @param string $table
      * @param int $id
      * @param array $data (optional)
-     * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
-     * @return \TYPO3\CMS\Version\Dependency\ElementEntity
+     * @param \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency
+     * @return \TYPO3\CMS\Workspaces\Dependency\ElementEntity
      */
-    public function getElement($table, $id, array $data = [], \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency)
+    public function getElement($table, $id, array $data = [], \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency)
     {
         /** @var $element ElementEntity */
-        $element = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Version\Dependency\ElementEntity::class, $table, $id, $data, $dependency);
+        $element = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Dependency\ElementEntity::class, $table, $id, $data, $dependency);
         $elementName = $element->__toString();
         if (!isset($this->elements[$elementName])) {
             $this->elements[$elementName] = $element;
@@ -52,15 +52,15 @@ class DependencyEntityFactory
     /**
      * Gets and registers a new reference.
      *
-     * @param \TYPO3\CMS\Version\Dependency\ElementEntity $element
+     * @param \TYPO3\CMS\Workspaces\Dependency\ElementEntity $element
      * @param string $field
-     * @return \TYPO3\CMS\Version\Dependency\ReferenceEntity
+     * @return \TYPO3\CMS\Workspaces\Dependency\ReferenceEntity
      */
-    public function getReference(\TYPO3\CMS\Version\Dependency\ElementEntity $element, $field)
+    public function getReference(\TYPO3\CMS\Workspaces\Dependency\ElementEntity $element, $field)
     {
         $referenceName = $element->__toString() . '.' . $field;
         if (!isset($this->references[$referenceName][$field])) {
-            $this->references[$referenceName][$field] = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Version\Dependency\ReferenceEntity::class, $element, $field);
+            $this->references[$referenceName][$field] = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Dependency\ReferenceEntity::class, $element, $field);
         }
         return $this->references[$referenceName][$field];
     }
@@ -72,12 +72,12 @@ class DependencyEntityFactory
      * @param int $id
      * @param string $field
      * @param array $data (optional)
-     * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
-     * @return \TYPO3\CMS\Version\Dependency\ReferenceEntity
+     * @param \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency
+     * @return \TYPO3\CMS\Workspaces\Dependency\ReferenceEntity
      * @see getElement
      * @see getReference
      */
-    public function getReferencedElement($table, $id, $field, array $data = [], \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency)
+    public function getReferencedElement($table, $id, $field, array $data = [], \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency)
     {
         return $this->getReference($this->getElement($table, $id, $data, $dependency), $field);
     }
diff --git a/typo3/sysext/version/Classes/Dependency/DependencyResolver.php b/typo3/sysext/workspaces/Classes/Dependency/DependencyResolver.php
similarity index 80%
rename from typo3/sysext/version/Classes/Dependency/DependencyResolver.php
rename to typo3/sysext/workspaces/Classes/Dependency/DependencyResolver.php
index e3dd2730b180ba82a7099b52ef0be1cdb85688d9..01eaf93afbc31bdba1fe2d8d651af14f5404e798 100644
--- a/typo3/sysext/version/Classes/Dependency/DependencyResolver.php
+++ b/typo3/sysext/workspaces/Classes/Dependency/DependencyResolver.php
@@ -1,5 +1,5 @@
 <?php
-namespace TYPO3\CMS\Version\Dependency;
+namespace TYPO3\CMS\Workspaces\Dependency;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -25,7 +25,7 @@ class DependencyResolver
     protected $workspace = 0;
 
     /**
-     * @var \TYPO3\CMS\Version\Dependency\DependencyEntityFactory
+     * @var \TYPO3\CMS\Workspaces\Dependency\DependencyEntityFactory
      */
     protected $factory;
 
@@ -73,10 +73,10 @@ class DependencyResolver
      * Sets a callback for a particular event.
      *
      * @param string $eventName
-     * @param \TYPO3\CMS\Version\Dependency\EventCallback $callback
-     * @return \TYPO3\CMS\Version\Dependency\DependencyResolver
+     * @param \TYPO3\CMS\Workspaces\Dependency\EventCallback $callback
+     * @return \TYPO3\CMS\Workspaces\Dependency\DependencyResolver
      */
-    public function setEventCallback($eventName, \TYPO3\CMS\Version\Dependency\EventCallback $callback)
+    public function setEventCallback($eventName, \TYPO3\CMS\Workspaces\Dependency\EventCallback $callback)
     {
         $this->eventCallbacks[$eventName] = $callback;
         return $this;
@@ -93,7 +93,7 @@ class DependencyResolver
     public function executeEventCallback($eventName, $caller, array $callerArguments = [])
     {
         if (isset($this->eventCallbacks[$eventName])) {
-            /** @var $callback \TYPO3\CMS\Version\Dependency\EventCallback */
+            /** @var $callback \TYPO3\CMS\Workspaces\Dependency\EventCallback */
             $callback = $this->eventCallbacks[$eventName];
             return $callback->execute($callerArguments, $caller, $eventName);
         }
@@ -104,7 +104,7 @@ class DependencyResolver
      * Sets the condition that outermost parents required at least one child or parent reference.
      *
      * @param bool $outerMostParentsRequireReferences
-     * @return \TYPO3\CMS\Version\Dependency\DependencyResolver
+     * @return \TYPO3\CMS\Workspaces\Dependency\DependencyResolver
      */
     public function setOuterMostParentsRequireReferences($outerMostParentsRequireReferences)
     {
@@ -118,7 +118,7 @@ class DependencyResolver
      * @param string $table
      * @param int $id
      * @param array $data
-     * @return \TYPO3\CMS\Version\Dependency\ElementEntity
+     * @return \TYPO3\CMS\Workspaces\Dependency\ElementEntity
      */
     public function addElement($table, $id, array $data = [])
     {
@@ -131,13 +131,13 @@ class DependencyResolver
     /**
      * Gets the outermost parents that define complete dependent structure each.
      *
-     * @return array|\TYPO3\CMS\Version\Dependency\ElementEntity[]
+     * @return array|\TYPO3\CMS\Workspaces\Dependency\ElementEntity[]
      */
     public function getOuterMostParents()
     {
         if (!isset($this->outerMostParents)) {
             $this->outerMostParents = [];
-            /** @var $element \TYPO3\CMS\Version\Dependency\ElementEntity */
+            /** @var $element \TYPO3\CMS\Workspaces\Dependency\ElementEntity */
             foreach ($this->elements as $element) {
                 $this->processOuterMostParent($element);
             }
@@ -148,9 +148,9 @@ class DependencyResolver
     /**
      * Processes and registers the outermost parents accordant to the registered elements.
      *
-     * @param \TYPO3\CMS\Version\Dependency\ElementEntity $element
+     * @param \TYPO3\CMS\Workspaces\Dependency\ElementEntity $element
      */
-    protected function processOuterMostParent(\TYPO3\CMS\Version\Dependency\ElementEntity $element)
+    protected function processOuterMostParent(\TYPO3\CMS\Workspaces\Dependency\ElementEntity $element)
     {
         if ($this->outerMostParentsRequireReferences === false || $element->hasReferences()) {
             $outerMostParent = $element->getOuterMostParent();
@@ -167,10 +167,10 @@ class DependencyResolver
      * Gets all nested elements (including the parent) of a particular outermost parent element.
      *
      * @throws \RuntimeException
-     * @param \TYPO3\CMS\Version\Dependency\ElementEntity $outerMostParent
+     * @param \TYPO3\CMS\Workspaces\Dependency\ElementEntity $outerMostParent
      * @return array
      */
-    public function getNestedElements(\TYPO3\CMS\Version\Dependency\ElementEntity $outerMostParent)
+    public function getNestedElements(\TYPO3\CMS\Workspaces\Dependency\ElementEntity $outerMostParent)
     {
         $outerMostParentName = $outerMostParent->__toString();
         if (!isset($this->outerMostParents[$outerMostParentName])) {
@@ -193,12 +193,12 @@ class DependencyResolver
     /**
      * Gets an instance of the factory to keep track of element or reference entities.
      *
-     * @return \TYPO3\CMS\Version\Dependency\DependencyEntityFactory
+     * @return \TYPO3\CMS\Workspaces\Dependency\DependencyEntityFactory
      */
     public function getFactory()
     {
         if (!isset($this->factory)) {
-            $this->factory = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Version\Dependency\DependencyEntityFactory::class);
+            $this->factory = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Dependency\DependencyEntityFactory::class);
         }
         return $this->factory;
     }
diff --git a/typo3/sysext/version/Classes/Dependency/ElementEntity.php b/typo3/sysext/workspaces/Classes/Dependency/ElementEntity.php
similarity index 94%
rename from typo3/sysext/version/Classes/Dependency/ElementEntity.php
rename to typo3/sysext/workspaces/Classes/Dependency/ElementEntity.php
index f6a4811c828a5ca781aea1efb39741d3932b9ba1..b3e40fec9af2f6a410aafb7a80f37da8091507e8 100644
--- a/typo3/sysext/version/Classes/Dependency/ElementEntity.php
+++ b/typo3/sysext/workspaces/Classes/Dependency/ElementEntity.php
@@ -1,5 +1,5 @@
 <?php
-namespace TYPO3\CMS\Version\Dependency;
+namespace TYPO3\CMS\Workspaces\Dependency;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -54,7 +54,7 @@ class ElementEntity
     protected $record;
 
     /**
-     * @var \TYPO3\CMS\Version\Dependency\DependencyResolver
+     * @var \TYPO3\CMS\Workspaces\Dependency\DependencyResolver
      */
     protected $dependency;
 
@@ -74,7 +74,7 @@ class ElementEntity
     protected $traversingParents = false;
 
     /**
-     * @var \TYPO3\CMS\Version\Dependency\ElementEntity
+     * @var \TYPO3\CMS\Workspaces\Dependency\ElementEntity
      */
     protected $outerMostParent;
 
@@ -89,9 +89,9 @@ class ElementEntity
      * @param string $table
      * @param int $id
      * @param array $data (optional)
-     * @param \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency
+     * @param \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency
      */
-    public function __construct($table, $id, array $data = [], \TYPO3\CMS\Version\Dependency\DependencyResolver $dependency)
+    public function __construct($table, $id, array $data = [], \TYPO3\CMS\Workspaces\Dependency\DependencyResolver $dependency)
     {
         $this->table = $table;
         $this->id = (int)$id;
@@ -206,7 +206,7 @@ class ElementEntity
     /**
      * Gets the parent dependency object.
      *
-     * @return \TYPO3\CMS\Version\Dependency\DependencyResolver
+     * @return \TYPO3\CMS\Workspaces\Dependency\DependencyResolver
      */
     public function getDependency()
     {
@@ -361,10 +361,10 @@ class ElementEntity
                 $this->outerMostParent = $this;
             } else {
                 $this->outerMostParent = false;
-                /** @var $parent \TYPO3\CMS\Version\Dependency\ReferenceEntity */
+                /** @var $parent \TYPO3\CMS\Workspaces\Dependency\ReferenceEntity */
                 foreach ($parents as $parent) {
                     $outerMostParent = $parent->getElement()->getOuterMostParent();
-                    if ($outerMostParent instanceof \TYPO3\CMS\Version\Dependency\ElementEntity) {
+                    if ($outerMostParent instanceof \TYPO3\CMS\Workspaces\Dependency\ElementEntity) {
                         $this->outerMostParent = $outerMostParent;
                         break;
                     }
@@ -387,7 +387,7 @@ class ElementEntity
         if (!isset($this->nestedChildren)) {
             $this->nestedChildren = [];
             $children = $this->getChildren();
-            /** @var $child \TYPO3\CMS\Version\Dependency\ReferenceEntity */
+            /** @var $child \TYPO3\CMS\Workspaces\Dependency\ReferenceEntity */
             foreach ($children as $child) {
                 $this->nestedChildren = array_merge($this->nestedChildren, [$child->getElement()->__toString() => $child->getElement()], $child->getElement()->getNestedChildren());
             }
diff --git a/typo3/sysext/version/Classes/Dependency/ElementEntityProcessor.php b/typo3/sysext/workspaces/Classes/Dependency/ElementEntityProcessor.php
similarity index 98%
rename from typo3/sysext/version/Classes/Dependency/ElementEntityProcessor.php
rename to typo3/sysext/workspaces/Classes/Dependency/ElementEntityProcessor.php
index e65a2df12acd69f16462414c1e2787e03fdb472e..01554403817257407cb5ef06e79e62eb51d2de9f 100644
--- a/typo3/sysext/version/Classes/Dependency/ElementEntityProcessor.php
+++ b/typo3/sysext/workspaces/Classes/Dependency/ElementEntityProcessor.php
@@ -1,5 +1,5 @@
 <?php
-namespace TYPO3\CMS\Version\Dependency;
+namespace TYPO3\CMS\Workspaces\Dependency;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -105,7 +105,7 @@ class ElementEntityProcessor
      *
      * @param array $callerArguments
      * @param array $targetArgument
-     * @param \TYPO3\CMS\Version\Dependency\ElementEntity $caller
+     * @param \TYPO3\CMS\Workspaces\Dependency\ElementEntity $caller
      * @param string $eventName
      * @return NULL|string Skip response (if required)
      */
diff --git a/typo3/sysext/version/Classes/Dependency/EventCallback.php b/typo3/sysext/workspaces/Classes/Dependency/EventCallback.php
similarity index 97%
rename from typo3/sysext/version/Classes/Dependency/EventCallback.php
rename to typo3/sysext/workspaces/Classes/Dependency/EventCallback.php
index 71d7200396078737bb1d9f7e67fb13f09393381a..096fd7495916e3a6ace21dd96b259946ebfa391e 100644
--- a/typo3/sysext/version/Classes/Dependency/EventCallback.php
+++ b/typo3/sysext/workspaces/Classes/Dependency/EventCallback.php
@@ -1,5 +1,5 @@
 <?php
-namespace TYPO3\CMS\Version\Dependency;
+namespace TYPO3\CMS\Workspaces\Dependency;
 
 /*
  * This file is part of the TYPO3 CMS project.
diff --git a/typo3/sysext/version/Classes/Dependency/ReferenceEntity.php b/typo3/sysext/workspaces/Classes/Dependency/ReferenceEntity.php
similarity index 79%
rename from typo3/sysext/version/Classes/Dependency/ReferenceEntity.php
rename to typo3/sysext/workspaces/Classes/Dependency/ReferenceEntity.php
index 47864db9cd7bd980529e30d47cdd3a87437b88b6..e2256efa84f65e90fc8c05c96382787ba7ccd584 100644
--- a/typo3/sysext/version/Classes/Dependency/ReferenceEntity.php
+++ b/typo3/sysext/workspaces/Classes/Dependency/ReferenceEntity.php
@@ -1,5 +1,5 @@
 <?php
-namespace TYPO3\CMS\Version\Dependency;
+namespace TYPO3\CMS\Workspaces\Dependency;
 
 /*
  * This file is part of the TYPO3 CMS project.
@@ -20,7 +20,7 @@ namespace TYPO3\CMS\Version\Dependency;
 class ReferenceEntity
 {
     /**
-     * @var \TYPO3\CMS\Version\Dependency\ElementEntity
+     * @var \TYPO3\CMS\Workspaces\Dependency\ElementEntity
      */
     protected $element;
 
@@ -32,10 +32,10 @@ class ReferenceEntity
     /**
      * Creates this object.
      *
-     * @param \TYPO3\CMS\Version\Dependency\ElementEntity $element
+     * @param \TYPO3\CMS\Workspaces\Dependency\ElementEntity $element
      * @param string $field
      */
-    public function __construct(\TYPO3\CMS\Version\Dependency\ElementEntity $element, $field)
+    public function __construct(\TYPO3\CMS\Workspaces\Dependency\ElementEntity $element, $field)
     {
         $this->element = $element;
         $this->field = $field;
@@ -44,7 +44,7 @@ class ReferenceEntity
     /**
      * Gets the elements.
      *
-     * @return \TYPO3\CMS\Version\Dependency\ElementEntity
+     * @return \TYPO3\CMS\Workspaces\Dependency\ElementEntity
      */
     public function getElement()
     {
diff --git a/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php b/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
index a77acd455e7decb00dd68a0c4d1c0204b99f4b88..2819d812f43705483cfbfb801f9cbcdc0da58770 100644
--- a/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
+++ b/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
@@ -14,18 +14,267 @@ namespace TYPO3\CMS\Workspaces\Hook;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\Platforms\SQLServerPlatform;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\ReferenceIndex;
+use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Versioning\VersionState;
 use TYPO3\CMS\Workspaces\Service\StagesService;
 
 /**
- * DataHandler service
+ * Contains some parts for staging, versioning and workspaces
+ * to interact with the TYPO3 Core Engine
  */
 class DataHandlerHook
 {
+    /**
+     * For accumulating information about workspace stages raised
+     * on elements so a single mail is sent as notification.
+     * previously called "accumulateForNotifEmail" in DataHandler
+     *
+     * @var array
+     */
+    protected $notificationEmailInfo = [];
+
+    /**
+     * Contains remapped IDs.
+     *
+     * @var array
+     */
+    protected $remappedIds = [];
+
+    /**
+     * @var \TYPO3\CMS\Workspaces\Service\WorkspaceService
+     */
+    protected $workspaceService;
+
+    /****************************
+     *****  Cmdmap  Hooks  ******
+     ****************************/
+    /**
+     * hook that is called before any cmd of the commandmap is executed
+     *
+     * @param DataHandler $dataHandler reference to the main DataHandler object
+     */
+    public function processCmdmap_beforeStart(DataHandler $dataHandler)
+    {
+        // Reset notification array
+        $this->notificationEmailInfo = [];
+        // Resolve dependencies of version/workspaces actions:
+        $dataHandler->cmdmap = $this->getCommandMap($dataHandler)->process()->get();
+    }
+
+    /**
+     * hook that is called when no prepared command was found
+     *
+     * @param string $command the command to be executed
+     * @param string $table the table of the record
+     * @param int $id the ID of the record
+     * @param mixed $value the value containing the data
+     * @param bool $commandIsProcessed can be set so that other hooks or
+     * @param DataHandler $dataHandler reference to the main DataHandler object
+     */
+    public function processCmdmap($command, $table, $id, $value, &$commandIsProcessed, DataHandler $dataHandler)
+    {
+        // custom command "version"
+        if ($command === 'version') {
+            $commandIsProcessed = true;
+            $action = (string)$value['action'];
+            $comment = !empty($value['comment']) ? $value['comment'] : '';
+            $notificationAlternativeRecipients = (isset($value['notificationAlternativeRecipients'])) && is_array($value['notificationAlternativeRecipients']) ? $value['notificationAlternativeRecipients'] : [];
+            switch ($action) {
+                case 'new':
+                    $dataHandler->versionizeRecord($table, $id, $value['label']);
+                    break;
+                case 'swap':
+                    $this->version_swap(
+                        $table,
+                        $id,
+                        $value['swapWith'],
+                        $value['swapIntoWS'],
+                        $dataHandler,
+                        $comment,
+                        true,
+                        $notificationAlternativeRecipients
+                    );
+                    break;
+                case 'clearWSID':
+                    $this->version_clearWSID($table, $id, false, $dataHandler);
+                    break;
+                case 'flush':
+                    $this->version_clearWSID($table, $id, true, $dataHandler);
+                    break;
+                case 'setStage':
+                    $elementIds = GeneralUtility::trimExplode(',', $id, true);
+                    foreach ($elementIds as $elementId) {
+                        $this->version_setStage(
+                            $table,
+                            $elementId,
+                            $value['stageId'],
+                            $comment,
+                            true,
+                            $dataHandler,
+                            $notificationAlternativeRecipients
+                        );
+                    }
+                    break;
+                default:
+                    // Do nothing
+            }
+        }
+    }
+
+    /**
+     * hook that is called AFTER all commands of the commandmap was
+     * executed
+     *
+     * @param DataHandler $dataHandler reference to the main DataHandler object
+     */
+    public function processCmdmap_afterFinish(DataHandler $dataHandler)
+    {
+        // Empty accumulation array:
+        foreach ($this->notificationEmailInfo as $notifItem) {
+            $this->notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $dataHandler, $notifItem['alternativeRecipients']);
+        }
+        // Reset notification array
+        $this->notificationEmailInfo = [];
+        // Reset remapped IDs
+        $this->remappedIds = [];
+
+        $this->flushWorkspaceCacheEntriesByWorkspaceId($dataHandler->BE_USER->workspace);
+    }
+
+    /**
+     * hook that is called when an element shall get deleted
+     *
+     * @param string $table the table of the record
+     * @param int $id the ID of the record
+     * @param array $record The accordant database record
+     * @param bool $recordWasDeleted can be set so that other hooks or
+     * @param DataHandler $dataHandler reference to the main DataHandler object
+     */
+    public function processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, DataHandler $dataHandler)
+    {
+        // only process the hook if it wasn't processed
+        // by someone else before
+        if ($recordWasDeleted) {
+            return;
+        }
+        $recordWasDeleted = true;
+        // For Live version, try if there is a workspace version because if so, rather "delete" that instead
+        // Look, if record is an offline version, then delete directly:
+        if ($record['pid'] != -1) {
+            if ($wsVersion = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $id)) {
+                $record = $wsVersion;
+                $id = $record['uid'];
+            }
+        }
+        $recordVersionState = VersionState::cast($record['t3ver_state']);
+        // Look, if record is an offline version, then delete directly:
+        if ($record['pid'] == -1) {
+            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                // In Live workspace, delete any. In other workspaces there must be match.
+                if ($dataHandler->BE_USER->workspace == 0 || (int)$record['t3ver_wsid'] == $dataHandler->BE_USER->workspace) {
+                    $liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
+                    // Processing can be skipped if a delete placeholder shall be swapped/published
+                    // during the current request. Thus it will be deleted later on...
+                    $liveRecordVersionState = VersionState::cast($liveRec['t3ver_state']);
+                    if ($recordVersionState->equals(VersionState::DELETE_PLACEHOLDER) && !empty($liveRec['uid'])
+                        && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'])
+                        && !empty($dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'])
+                        && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap'
+                        && $dataHandler->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id
+                    ) {
+                        return null;
+                    }
+
+                    if ($record['t3ver_wsid'] > 0 && $recordVersionState->equals(VersionState::DEFAULT_STATE)) {
+                        // Change normal versioned record to delete placeholder
+                        // Happens when an edited record is deleted
+                        GeneralUtility::makeInstance(ConnectionPool::class)
+                            ->getConnectionForTable($table)
+                            ->update(
+                                $table,
+                                [
+                                    't3ver_label' => 'DELETED!',
+                                    't3ver_state' => 2,
+                                ],
+                                ['uid' => $id]
+                            );
+
+                        // Delete localization overlays:
+                        $dataHandler->deleteL10nOverlayRecords($table, $id);
+                    } elseif ($record['t3ver_wsid'] == 0 || !$liveRecordVersionState->indicatesPlaceholder()) {
+                        // Delete those in WS 0 + if their live records state was not "Placeholder".
+                        $dataHandler->deleteEl($table, $id);
+                        // Delete move-placeholder if current version record is a move-to-pointer
+                        if ($recordVersionState->equals(VersionState::MOVE_POINTER)) {
+                            $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid', $record['t3ver_wsid']);
+                            if (!empty($movePlaceholder)) {
+                                $dataHandler->deleteEl($table, $movePlaceholder['uid']);
+                            }
+                        }
+                    } else {
+                        // If live record was placeholder (new/deleted), rather clear
+                        // it from workspace (because it clears both version and placeholder).
+                        $this->version_clearWSID($table, $id, false, $dataHandler);
+                    }
+                } else {
+                    $dataHandler->newlog('Tried to delete record from another workspace', 1);
+                }
+            } else {
+                $dataHandler->newlog('Versioning not enabled for record with PID = -1!', 2);
+            }
+        } elseif ($res = $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) {
+            // Look, if record is "online" or in a versionized branch, then delete directly.
+            if ($res > 0) {
+                $dataHandler->deleteEl($table, $id);
+            } else {
+                $dataHandler->newlog('Stage of root point did not allow for deletion', 1);
+            }
+        } elseif ($recordVersionState->equals(VersionState::MOVE_PLACEHOLDER)) {
+            // Placeholders for moving operations are deletable directly.
+            // Get record which its a placeholder for and reset the t3ver_state of that:
+            if ($wsRec = BackendUtility::getWorkspaceVersionOfRecord($record['t3ver_wsid'], $table, $record['t3ver_move_id'], 'uid')) {
+                // Clear the state flag of the workspace version of the record
+                // Setting placeholder state value for version (so it can know it is currently a new version...)
+
+                GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getConnectionForTable($table)
+                    ->update(
+                        $table,
+                        [
+                            't3ver_state' => (string)new VersionState(VersionState::DEFAULT_STATE)
+                        ],
+                        ['uid' => (int)$wsRec['uid']]
+                    );
+            }
+            $dataHandler->deleteEl($table, $id);
+        } else {
+            // Otherwise, try to delete by versioning:
+            $copyMappingArray = $dataHandler->copyMappingArray;
+            $dataHandler->versionizeRecord($table, $id, 'DELETED!', true);
+            // Determine newly created versions:
+            // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
+            $versionizedElements = ArrayUtility::arrayDiffAssocRecursive($dataHandler->copyMappingArray, $copyMappingArray);
+            // Delete localization overlays:
+            foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
+                foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
+                    $dataHandler->deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
+                }
+            }
+        }
+    }
+
     /**
      * In case a sys_workspace_stage record is deleted we do a hard reset
      * for all existing records in that stage to avoid that any of these end up
@@ -49,14 +298,926 @@ class DataHandlerHook
     }
 
     /**
-     * hook that is called AFTER all commands of the commandmap was
-     * executed
+     * Hook for \TYPO3\CMS\Core\DataHandling\DataHandler::moveRecord that cares about
+     * moving records that are *not* in the live workspace
      *
-     * @param \TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler reference to the main DataHandler object
+     * @param string $table the table of the record
+     * @param int $uid the ID of the record
+     * @param int $destPid Position to move to: $destPid: >=0 then it points to
+     * @param array $propArr Record properties, like header and pid (includes workspace overlay)
+     * @param array $moveRec Record properties, like header and pid (without workspace overlay)
+     * @param int $resolvedPid The final page ID of the record
+     * @param bool $recordWasMoved can be set so that other hooks or
+     * @param DataHandler $dataHandler
      */
-    public function processCmdmap_afterFinish(\TYPO3\CMS\Core\DataHandling\DataHandler $dataHandler)
+    public function moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, DataHandler $dataHandler)
     {
-        $this->flushWorkspaceCacheEntriesByWorkspaceId($dataHandler->BE_USER->workspace);
+        // Only do something in Draft workspace
+        if ($dataHandler->BE_USER->workspace === 0) {
+            return;
+        }
+        if ($destPid < 0) {
+            // Fetch move placeholder, since it might point to a new page in the current workspace
+            $movePlaceHolder = BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid,pid');
+            if ($movePlaceHolder !== false) {
+                $resolvedPid = $movePlaceHolder['pid'];
+            }
+        }
+        $recordWasMoved = true;
+        $moveRecVersionState = VersionState::cast($moveRec['t3ver_state']);
+        // Get workspace version of the source record, if any:
+        $WSversion = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
+        // Handle move-placeholders if the current record is not one already
+        if (
+            BackendUtility::isTableWorkspaceEnabled($table)
+            && !$moveRecVersionState->equals(VersionState::MOVE_PLACEHOLDER)
+        ) {
+            // Create version of record first, if it does not exist
+            if (empty($WSversion['uid'])) {
+                $dataHandler->versionizeRecord($table, $uid, 'MovePointer');
+                $WSversion = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
+                $this->moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
+            } elseif ($dataHandler->isRecordCopied($table, $uid) && (int)$dataHandler->copyMappingArray[$table][$uid] === (int)$WSversion['uid']) {
+                // If the record has been versioned before (e.g. cascaded parent-child structure), create only the move-placeholders
+                $this->moveRecord_processFields($dataHandler, $resolvedPid, $table, $uid);
+            }
+        }
+        // Check workspace permissions:
+        $workspaceAccessBlocked = [];
+        // Element was in "New/Deleted/Moved" so it can be moved...
+        $recIsNewVersion = $moveRecVersionState->indicatesPlaceholder();
+        $destRes = $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table);
+        $canMoveRecord = ($recIsNewVersion || BackendUtility::isTableWorkspaceEnabled($table));
+        // Workspace source check:
+        if (!$recIsNewVersion) {
+            $errorCode = $dataHandler->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid);
+            if ($errorCode) {
+                $workspaceAccessBlocked['src1'] = 'Record could not be edited in workspace: ' . $errorCode . ' ';
+            } elseif (!$canMoveRecord && $dataHandler->BE_USER->workspaceAllowLiveRecordsInPID($moveRec['pid'], $table) <= 0) {
+                $workspaceAccessBlocked['src2'] = 'Could not remove record from table "' . $table . '" from its page "' . $moveRec['pid'] . '" ';
+            }
+        }
+        // Workspace destination check:
+        // All records can be inserted if $destRes is greater than zero.
+        // Only new versions can be inserted if $destRes is FALSE.
+        // NO RECORDS can be inserted if $destRes is negative which indicates a stage
+        //  not allowed for use. If "versioningWS" is version 2, moving can take place of versions.
+        // since TYPO3 CMS 7, version2 is the default and the only option
+        if (!($destRes > 0 || $canMoveRecord && !$destRes)) {
+            $workspaceAccessBlocked['dest1'] = 'Could not insert record from table "' . $table . '" in destination PID "' . $resolvedPid . '" ';
+        } elseif ($destRes == 1 && $WSversion['uid']) {
+            $workspaceAccessBlocked['dest2'] = 'Could not insert other versions in destination PID ';
+        }
+        if (empty($workspaceAccessBlocked)) {
+            // If the move operation is done on a versioned record, which is
+            // NOT new/deleted placeholder and versioningWS is in version 2, then...
+            // since TYPO3 CMS 7, version2 is the default and the only option
+            if ($WSversion['uid'] && !$recIsNewVersion && BackendUtility::isTableWorkspaceEnabled($table)) {
+                $this->moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $dataHandler);
+            } else {
+                // moving not needed, just behave like in live workspace
+                $recordWasMoved = false;
+            }
+        } else {
+            $dataHandler->newlog('Move attempt failed due to workspace restrictions: ' . implode(' // ', $workspaceAccessBlocked), 1);
+        }
+    }
+
+    /**
+     * Processes fields of a moved record and follows references.
+     *
+     * @param DataHandler $dataHandler Calling DataHandler instance
+     * @param int $resolvedPageId Resolved real destination page id
+     * @param string $table Name of parent table
+     * @param int $uid UID of the parent record
+     */
+    protected function moveRecord_processFields(DataHandler $dataHandler, $resolvedPageId, $table, $uid)
+    {
+        $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $table, $uid);
+        if (empty($versionedRecord)) {
+            return;
+        }
+        foreach ($versionedRecord as $field => $value) {
+            if (empty($GLOBALS['TCA'][$table]['columns'][$field]['config'])) {
+                continue;
+            }
+            $this->moveRecord_processFieldValue(
+                $dataHandler,
+                $resolvedPageId,
+                $table,
+                $uid,
+                $field,
+                $value,
+                $GLOBALS['TCA'][$table]['columns'][$field]['config']
+            );
+        }
+    }
+
+    /**
+     * Processes a single field of a moved record and follows references.
+     *
+     * @param DataHandler $dataHandler Calling DataHandler instance
+     * @param int $resolvedPageId Resolved real destination page id
+     * @param string $table Name of parent table
+     * @param int $uid UID of the parent record
+     * @param string $field Name of the field of the parent record
+     * @param string $value Value of the field of the parent record
+     * @param array $configuration TCA field configuration of the parent record
+     */
+    protected function moveRecord_processFieldValue(DataHandler $dataHandler, $resolvedPageId, $table, $uid, $field, $value, array $configuration)
+    {
+        $inlineFieldType = $dataHandler->getInlineFieldType($configuration);
+        $inlineProcessing = (
+            ($inlineFieldType === 'list' || $inlineFieldType === 'field')
+            && BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])
+            && (!isset($configuration['behaviour']['disableMovingChildrenWithParent']) || !$configuration['behaviour']['disableMovingChildrenWithParent'])
+        );
+
+        if ($inlineProcessing) {
+            if ($table === 'pages') {
+                // If the inline elements are related to a page record,
+                // make sure they reside at that page and not at its parent
+                $resolvedPageId = $uid;
+            }
+
+            $dbAnalysis = $this->createRelationHandlerInstance();
+            $dbAnalysis->start($value, $configuration['foreign_table'], '', $uid, $table, $configuration);
+
+            // Moving records to a positive destination will insert each
+            // record at the beginning, thus the order is reversed here:
+            foreach ($dbAnalysis->itemArray as $item) {
+                $versionedRecord = BackendUtility::getWorkspaceVersionOfRecord($dataHandler->BE_USER->workspace, $item['table'], $item['id'], 'uid,t3ver_state');
+                if (empty($versionedRecord) || VersionState::cast($versionedRecord['t3ver_state'])->indicatesPlaceholder()) {
+                    continue;
+                }
+                $dataHandler->moveRecord($item['table'], $item['id'], $resolvedPageId);
+            }
+        }
+    }
+
+    /****************************
+     *****  Notifications  ******
+     ****************************/
+    /**
+     * Send an email notification to users in workspace
+     *
+     * @param array $stat Workspace access array from \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::checkWorkspace()
+     * @param int $stageId New Stage number: 0 = editing, 1= just ready for review, 10 = ready for publication, -1 = rejected!
+     * @param string $table Table name of element (or list of element names if $id is zero)
+     * @param int $id Record uid of element (if zero, then $table is used as reference to element(s) alone)
+     * @param string $comment User comment sent along with action
+     * @param DataHandler $dataHandler DataHandler object
+     * @param array $notificationAlternativeRecipients List of recipients to notify instead of be_users selected by sys_workspace, list is generated by workspace extension module
+     */
+    protected function notifyStageChange(array $stat, $stageId, $table, $id, $comment, DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
+    {
+        $workspaceRec = BackendUtility::getRecord('sys_workspace', $stat['uid']);
+        // So, if $id is not set, then $table is taken to be the complete element name!
+        $elementName = $id ? $table . ':' . $id : $table;
+        if (!is_array($workspaceRec)) {
+            return;
+        }
+
+        // Get the new stage title
+        $stageService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\StagesService::class);
+        $newStage = $stageService->getStageTitle((int)$stageId);
+        if (empty($notificationAlternativeRecipients)) {
+            // Compile list of recipients:
+            $emails = [];
+            switch ((int)$stat['stagechg_notification']) {
+                case 1:
+                    switch ((int)$stageId) {
+                        case 1:
+                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']);
+                            break;
+                        case 10:
+                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
+                            break;
+                        case -1:
+                            // List of elements to reject:
+                            $allElements = explode(',', $elementName);
+                            // Traverse them, and find the history of each
+                            foreach ($allElements as $elRef) {
+                                list($eTable, $eUid) = explode(':', $elRef);
+
+                                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                                    ->getQueryBuilderForTable('sys_log');
+
+                                $queryBuilder->getRestrictions()->removeAll();
+
+                                $result = $queryBuilder
+                                    ->select('log_data', 'tstamp', 'userid')
+                                    ->from('sys_log')
+                                    ->where(
+                                        $queryBuilder->expr()->eq(
+                                            'action',
+                                            $queryBuilder->createNamedParameter(6, \PDO::PARAM_INT)
+                                        ),
+                                        $queryBuilder->expr()->eq(
+                                            'details_nr',
+                                            $queryBuilder->createNamedParameter(30, \PDO::PARAM_INT)
+                                        ),
+                                        $queryBuilder->expr()->eq(
+                                            'tablename',
+                                            $queryBuilder->createNamedParameter($eTable, \PDO::PARAM_STR)
+                                        ),
+                                        $queryBuilder->expr()->eq(
+                                            'recuid',
+                                            $queryBuilder->createNamedParameter($eUid, \PDO::PARAM_INT)
+                                        )
+                                    )
+                                    ->orderBy('uid', 'DESC')
+                                    ->execute();
+
+                                // Find all implicated since the last stage-raise from editing to review:
+                                while ($dat = $result->fetch()) {
+                                    $data = unserialize($dat['log_data']);
+                                    $emails = $this->getEmailsForStageChangeNotification($dat['userid'], true) + $emails;
+                                    if ($data['stage'] == 1) {
+                                        break;
+                                    }
+                                }
+                            }
+                            break;
+                        case 0:
+                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']);
+                            break;
+                        default:
+                            $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
+                    }
+                    break;
+                case 10:
+                    $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], true);
+                    $emails = $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']) + $emails;
+                    $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']) + $emails;
+                    break;
+                default:
+                    // Do nothing
+            }
+        } else {
+            $emails = $notificationAlternativeRecipients;
+        }
+        // prepare and then send the emails
+        if (!empty($emails)) {
+            // Path to record is found:
+            list($elementTable, $elementUid) = explode(':', $elementName);
+            $elementUid = (int)$elementUid;
+            $elementRecord = BackendUtility::getRecord($elementTable, $elementUid);
+            $recordTitle = BackendUtility::getRecordTitle($elementTable, $elementRecord);
+            if ($elementTable === 'pages') {
+                $pageUid = $elementUid;
+            } else {
+                BackendUtility::fixVersioningPid($elementTable, $elementRecord);
+                $pageUid = ($elementUid = $elementRecord['pid']);
+            }
+
+            // new way, options are
+            // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject
+            // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject
+            $pageTsConfig = BackendUtility::getPagesTSconfig($pageUid);
+            $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.'];
+            $markers = [
+                '###RECORD_TITLE###' => $recordTitle,
+                '###RECORD_PATH###' => BackendUtility::getRecordPath($elementUid, '', 20),
+                '###SITE_NAME###' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
+                '###SITE_URL###' => GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . TYPO3_mainDir,
+                '###WORKSPACE_TITLE###' => $workspaceRec['title'],
+                '###WORKSPACE_UID###' => $workspaceRec['uid'],
+                '###ELEMENT_NAME###' => $elementName,
+                '###NEXT_STAGE###' => $newStage,
+                '###COMMENT###' => $comment,
+                // See: #30212 - keep both markers for compatibility
+                '###USER_REALNAME###' => $dataHandler->BE_USER->user['realName'],
+                '###USER_FULLNAME###' => $dataHandler->BE_USER->user['realName'],
+                '###USER_USERNAME###' => $dataHandler->BE_USER->user['username']
+            ];
+            // add marker for preview links if workspace extension is loaded
+            $this->workspaceService = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Service\WorkspaceService::class);
+            // only generate the link if the marker is in the template - prevents database from getting to much entries
+            if (GeneralUtility::isFirstPartOfStr($emailConfig['message'], 'LLL:')) {
+                $tempEmailMessage = $this->getLanguageService()->sL($emailConfig['message']);
+            } else {
+                $tempEmailMessage = $emailConfig['message'];
+            }
+            if (strpos($tempEmailMessage, '###PREVIEW_LINK###') !== false) {
+                $markers['###PREVIEW_LINK###'] = $this->workspaceService->generateWorkspacePreviewLink($elementUid);
+            }
+            unset($tempEmailMessage);
+            $markers['###SPLITTED_PREVIEW_LINK###'] = $this->workspaceService->generateWorkspaceSplittedPreviewLink($elementUid, true);
+            // Hook for preprocessing of the content for formmails:
+            if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'])) {
+                foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'] as $className) {
+                    $_procObj = GeneralUtility::makeInstance($className);
+                    $markers = $_procObj->postModifyMarkers($markers, $this);
+                }
+            }
+            // send an email to each individual user, to ensure the
+            // multilanguage version of the email
+            $emailRecipients = [];
+            // an array of language objects that are needed
+            // for emails with different languages
+            $languageObjects = [
+                $this->getLanguageService()->lang => $this->getLanguageService()
+            ];
+            // loop through each recipient and send the email
+            foreach ($emails as $recipientData) {
+                // don't send an email twice
+                if (isset($emailRecipients[$recipientData['email']])) {
+                    continue;
+                }
+                $emailSubject = $emailConfig['subject'];
+                $emailMessage = $emailConfig['message'];
+                $emailRecipients[$recipientData['email']] = $recipientData['email'];
+                // check if the email needs to be localized
+                // in the users' language
+                if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:') || GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
+                    $recipientLanguage = $recipientData['lang'] ? $recipientData['lang'] : 'default';
+                    if (!isset($languageObjects[$recipientLanguage])) {
+                        // a LANG object in this language hasn't been
+                        // instantiated yet, so this is done here
+                        /** @var $languageObject \TYPO3\CMS\Core\Localization\LanguageService */
+                        $languageObject = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\LanguageService::class);
+                        $languageObject->init($recipientLanguage);
+                        $languageObjects[$recipientLanguage] = $languageObject;
+                    } else {
+                        $languageObject = $languageObjects[$recipientLanguage];
+                    }
+                    if (GeneralUtility::isFirstPartOfStr($emailSubject, 'LLL:')) {
+                        $emailSubject = $languageObject->sL($emailSubject);
+                    }
+                    if (GeneralUtility::isFirstPartOfStr($emailMessage, 'LLL:')) {
+                        $emailMessage = $languageObject->sL($emailMessage);
+                    }
+                }
+                $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
+                $emailSubject = $templateService->substituteMarkerArray($emailSubject, $markers, '', true, true);
+                $emailMessage = $templateService->substituteMarkerArray($emailMessage, $markers, '', true, true);
+                // Send an email to the recipient
+                /** @var $mail \TYPO3\CMS\Core\Mail\MailMessage */
+                $mail = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Mail\MailMessage::class);
+                if (!empty($recipientData['realName'])) {
+                    $recipient = [$recipientData['email'] => $recipientData['realName']];
+                } else {
+                    $recipient = $recipientData['email'];
+                }
+                $mail->setTo($recipient)
+                    ->setSubject($emailSubject)
+                    ->setBody($emailMessage);
+                $mail->send();
+            }
+            $emailRecipients = implode(',', $emailRecipients);
+            $dataHandler->newlog2('Notification email for stage change was sent to "' . $emailRecipients . '"', $table, $id);
+        }
+    }
+
+    /**
+     * Return be_users that should be notified on stage change from input list.
+     * previously called notifyStageChange_getEmails() in DataHandler
+     *
+     * @param string $listOfUsers List of backend users, on the form "be_users_10,be_users_2" or "10,2" in case noTablePrefix is set.
+     * @param bool $noTablePrefix If set, the input list are integers and not strings.
+     * @return array Array of emails
+     */
+    protected function getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = false)
+    {
+        $users = GeneralUtility::trimExplode(',', $listOfUsers, true);
+        $emails = [];
+        foreach ($users as $userIdent) {
+            if ($noTablePrefix) {
+                $id = (int)$userIdent;
+            } else {
+                list($table, $id) = GeneralUtility::revExplode('_', $userIdent, 2);
+            }
+            if ($table === 'be_users' || $noTablePrefix) {
+                if ($userRecord = BackendUtility::getRecord('be_users', $id, 'uid,email,lang,realName', BackendUtility::BEenableFields('be_users'))) {
+                    if (trim($userRecord['email']) !== '') {
+                        $emails[$id] = $userRecord;
+                    }
+                }
+            }
+        }
+        return $emails;
+    }
+
+    /****************************
+     *****  Stage Changes  ******
+     ****************************/
+    /**
+     * Setting stage of record
+     *
+     * @param string $table Table name
+     * @param int $integer Record UID
+     * @param int $stageId Stage ID to set
+     * @param string $comment Comment that goes into log
+     * @param bool $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
+     * @param DataHandler $dataHandler DataHandler object
+     * @param array $notificationAlternativeRecipients comma separated list of recipients to notify instead of normal be_users
+     */
+    protected function version_setStage($table, $id, $stageId, $comment = '', $notificationEmailInfo = false, DataHandler $dataHandler, array $notificationAlternativeRecipients = [])
+    {
+        if ($errorCode = $dataHandler->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
+            $dataHandler->newlog('Attempt to set stage for record failed: ' . $errorCode, 1);
+        } elseif ($dataHandler->checkRecordUpdateAccess($table, $id)) {
+            $record = BackendUtility::getRecord($table, $id);
+            $stat = $dataHandler->BE_USER->checkWorkspace($record['t3ver_wsid']);
+            // check if the usere is allowed to the current stage, so it's also allowed to send to next stage
+            if ($dataHandler->BE_USER->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
+                // Set stage of record:
+                GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getConnectionForTable($table)
+                    ->update(
+                        $table,
+                        [
+                            't3ver_stage' => $stageId,
+                        ],
+                        ['uid' => (int)$id]
+                    );
+                $dataHandler->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
+                // TEMPORARY, except 6-30 as action/detail number which is observed elsewhere!
+                $dataHandler->log($table, $id, 6, 0, 0, 'Stage raised...', 30, ['comment' => $comment, 'stage' => $stageId]);
+                if ((int)$stat['stagechg_notification'] > 0) {
+                    if ($notificationEmailInfo) {
+                        $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['shared'] = [$stat, $stageId, $comment];
+                        $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['elements'][] = $table . ':' . $id;
+                        $this->notificationEmailInfo[$stat['uid'] . ':' . $stageId . ':' . $comment]['alternativeRecipients'] = $notificationAlternativeRecipients;
+                    } else {
+                        $this->notifyStageChange($stat, $stageId, $table, $id, $comment, $dataHandler, $notificationAlternativeRecipients);
+                    }
+                }
+            } else {
+                $dataHandler->newlog('The member user tried to set a stage value "' . $stageId . '" that was not allowed', 1);
+            }
+        } else {
+            $dataHandler->newlog('Attempt to set stage for record failed because you do not have edit access', 1);
+        }
+    }
+
+    /*****************************
+     *****  CMD versioning  ******
+     *****************************/
+
+    /**
+     * Swapping versions of a record
+     * Version from archive (future/past, called "swap version") will get the uid of the "t3ver_oid", the official element with uid = "t3ver_oid" will get the new versions old uid. PIDs are swapped also
+     *
+     * @param string $table Table name
+     * @param int $id UID of the online record to swap
+     * @param int $swapWith UID of the archived version to swap with!
+     * @param bool $swapIntoWS If set, swaps online into workspace instead of publishing out of workspace.
+     * @param DataHandler $dataHandler DataHandler object
+     * @param string $comment Notification comment
+     * @param bool $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
+     * @param array $notificationAlternativeRecipients comma separated list of recipients to notificate instead of normal be_users
+     */
+    protected function version_swap($table, $id, $swapWith, $swapIntoWS = 0, DataHandler $dataHandler, $comment = '', $notificationEmailInfo = false, $notificationAlternativeRecipients = [])
+    {
+
+        // Check prerequisites before start swapping
+
+        // Skip records that have been deleted during the current execution
+        if ($dataHandler->hasDeletedRecord($table, $id)) {
+            return;
+        }
+
+        // First, check if we may actually edit the online record
+        if (!$dataHandler->checkRecordUpdateAccess($table, $id)) {
+            $dataHandler->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
+            return;
+        }
+        // Select the two versions:
+        $curVersion = BackendUtility::getRecord($table, $id, '*');
+        $swapVersion = BackendUtility::getRecord($table, $swapWith, '*');
+        $movePlh = [];
+        $movePlhID = 0;
+        if (!(is_array($curVersion) && is_array($swapVersion))) {
+            $dataHandler->newlog('Error: Either online or swap version could not be selected!', 2);
+            return;
+        }
+        if (!$dataHandler->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
+            $dataHandler->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
+            return;
+        }
+        $wsAccess = $dataHandler->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
+        if (!($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1) || (int)$swapVersion['t3ver_stage'] === -10)) {
+            $dataHandler->newlog('Records in workspace #' . $swapVersion['t3ver_wsid'] . ' can only be published when in "Publish" stage.', 1);
+            return;
+        }
+        if (!($dataHandler->doesRecordExist($table, $swapWith, 'show') && $dataHandler->checkRecordUpdateAccess($table, $swapWith))) {
+            $dataHandler->newlog('You cannot publish a record you do not have edit and show permissions for', 1);
+            return;
+        }
+        if ($swapIntoWS && !$dataHandler->BE_USER->workspaceSwapAccess()) {
+            $dataHandler->newlog('Workspace #' . $swapVersion['t3ver_wsid'] . ' does not support swapping.', 1);
+            return;
+        }
+        // Check if the swapWith record really IS a version of the original!
+        if (!(((int)$swapVersion['pid'] == -1 && (int)$curVersion['pid'] >= 0) && (int)$swapVersion['t3ver_oid'] === (int)$id)) {
+            $dataHandler->newlog('In swap version, either pid was not -1 or the t3ver_oid didn\'t match the id of the online version as it must!', 2);
+            return;
+        }
+        // Lock file name:
+        $lockFileName = PATH_site . 'typo3temp/var/swap_locking/' . $table . '_' . $id . '.ser';
+        if (@is_file($lockFileName)) {
+            $dataHandler->newlog('A swapping lock file was present. Either another swap process is already running or a previous swap process failed. Ask your administrator to handle the situation.', 2);
+            return;
+        }
+
+        // Now start to swap records by first creating the lock file
+
+        // Write lock-file:
+        GeneralUtility::writeFileToTypo3tempDir($lockFileName, serialize([
+            'tstamp' => $GLOBALS['EXEC_TIME'],
+            'user' => $dataHandler->BE_USER->user['username'],
+            'curVersion' => $curVersion,
+            'swapVersion' => $swapVersion
+        ]));
+        // Find fields to keep
+        $keepFields = $this->getUniqueFields($table);
+        if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
+            $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
+        }
+        // l10n-fields must be kept otherwise the localization
+        // will be lost during the publishing
+        if ($table !== 'pages_language_overlay' && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
+            $keepFields[] = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
+        }
+        // Swap "keepfields"
+        foreach ($keepFields as $fN) {
+            $tmp = $swapVersion[$fN];
+            $swapVersion[$fN] = $curVersion[$fN];
+            $curVersion[$fN] = $tmp;
+        }
+        // Preserve states:
+        $t3ver_state = [];
+        $t3ver_state['swapVersion'] = $swapVersion['t3ver_state'];
+        $t3ver_state['curVersion'] = $curVersion['t3ver_state'];
+        // Modify offline version to become online:
+        $tmp_wsid = $swapVersion['t3ver_wsid'];
+        // Set pid for ONLINE
+        $swapVersion['pid'] = (int)$curVersion['pid'];
+        // We clear this because t3ver_oid only make sense for offline versions
+        // and we want to prevent unintentional misuse of this
+        // value for online records.
+        $swapVersion['t3ver_oid'] = 0;
+        // In case of swapping and the offline record has a state
+        // (like 2 or 4 for deleting or move-pointer) we set the
+        // current workspace ID so the record is not deselected
+        // in the interface by BackendUtility::versioningPlaceholderClause()
+        $swapVersion['t3ver_wsid'] = 0;
+        if ($swapIntoWS) {
+            if ($t3ver_state['swapVersion'] > 0) {
+                $swapVersion['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
+            } else {
+                $swapVersion['t3ver_wsid'] = (int)$curVersion['t3ver_wsid'];
+            }
+        }
+        $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
+        $swapVersion['t3ver_stage'] = 0;
+        if (!$swapIntoWS) {
+            $swapVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
+        }
+        // Moving element.
+        if (BackendUtility::isTableWorkspaceEnabled($table)) {
+            //  && $t3ver_state['swapVersion']==4   // Maybe we don't need this?
+            if ($plhRec = BackendUtility::getMovePlaceholder($table, $id, 't3ver_state,pid,uid' . ($GLOBALS['TCA'][$table]['ctrl']['sortby'] ? ',' . $GLOBALS['TCA'][$table]['ctrl']['sortby'] : ''))) {
+                $movePlhID = $plhRec['uid'];
+                $movePlh['pid'] = $swapVersion['pid'];
+                $swapVersion['pid'] = (int)$plhRec['pid'];
+                $curVersion['t3ver_state'] = (int)$swapVersion['t3ver_state'];
+                $swapVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
+                if ($GLOBALS['TCA'][$table]['ctrl']['sortby']) {
+                    // sortby is a "keepFields" which is why this will work...
+                    $movePlh[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
+                    $swapVersion[$GLOBALS['TCA'][$table]['ctrl']['sortby']] = $plhRec[$GLOBALS['TCA'][$table]['ctrl']['sortby']];
+                }
+            }
+        }
+        // Take care of relations in each field (e.g. IRRE):
+        if (is_array($GLOBALS['TCA'][$table]['columns'])) {
+            foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $fieldConf) {
+                $this->version_swap_processFields($table, $field, $fieldConf['config'], $curVersion, $swapVersion, $dataHandler);
+            }
+        }
+        unset($swapVersion['uid']);
+        // Modify online version to become offline:
+        unset($curVersion['uid']);
+        // Set pid for OFFLINE
+        $curVersion['pid'] = -1;
+        $curVersion['t3ver_oid'] = (int)$id;
+        $curVersion['t3ver_wsid'] = $swapIntoWS ? (int)$tmp_wsid : 0;
+        $curVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
+        $curVersion['t3ver_count'] = $curVersion['t3ver_count'] + 1;
+        // Increment lifecycle counter
+        $curVersion['t3ver_stage'] = 0;
+        if (!$swapIntoWS) {
+            $curVersion['t3ver_state'] = (string)new VersionState(VersionState::DEFAULT_STATE);
+        }
+        // Registering and swapping MM relations in current and swap records:
+        $dataHandler->version_remapMMForVersionSwap($table, $id, $swapWith);
+        // Generating proper history data to prepare logging
+        $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
+        $dataHandler->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
+
+        // Execute swapping:
+        $sqlErrors = [];
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
+
+        $platform = $connection->getDatabasePlatform();
+        $tableDetails = null;
+        if ($platform instanceof SQLServerPlatform) {
+            // mssql needs to set proper PARAM_LOB and others to update fields
+            $tableDetails = $connection->getSchemaManager()->listTableDetails($table);
+        }
+
+        try {
+            $types = [];
+
+            if ($platform instanceof SQLServerPlatform) {
+                foreach ($curVersion as $columnName => $columnValue) {
+                    $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
+                }
+            }
+
+            $connection->update(
+                $table,
+                $swapVersion,
+                ['uid' => (int)$id],
+                $types
+            );
+        } catch (DBALException $e) {
+            $sqlErrors[] = $e->getPrevious()->getMessage();
+        }
+
+        if (empty($sqlErrors)) {
+            try {
+                $types = [];
+                if ($platform instanceof SQLServerPlatform) {
+                    foreach ($curVersion as $columnName => $columnValue) {
+                        $types[$columnName] = $tableDetails->getColumn($columnName)->getType()->getBindingType();
+                    }
+                }
+
+                $connection->update(
+                    $table,
+                    $curVersion,
+                    ['uid' => (int)$swapWith],
+                    $types
+                );
+                unlink($lockFileName);
+            } catch (DBALException $e) {
+                $sqlErrors[] = $e->getPrevious()->getMessage();
+            }
+        }
+
+        if (!empty($sqlErrors)) {
+            $dataHandler->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
+        } else {
+            // Register swapped ids for later remapping:
+            $this->remappedIds[$table][$id] = $swapWith;
+            $this->remappedIds[$table][$swapWith] = $id;
+            // If a moving operation took place...:
+            if ($movePlhID) {
+                // Remove, if normal publishing:
+                if (!$swapIntoWS) {
+                    // For delete + completely delete!
+                    $dataHandler->deleteEl($table, $movePlhID, true, true);
+                } else {
+                    // Otherwise update the movePlaceholder:
+                    GeneralUtility::makeInstance(ConnectionPool::class)
+                        ->getConnectionForTable($table)
+                        ->update(
+                            $table,
+                            $movePlh,
+                            ['uid' => (int)$movePlhID]
+                        );
+                    $dataHandler->addRemapStackRefIndex($table, $movePlhID);
+                }
+            }
+            // Checking for delete:
+            // Delete only if new/deleted placeholders are there.
+            if (!$swapIntoWS && ((int)$t3ver_state['swapVersion'] === 1 || (int)$t3ver_state['swapVersion'] === 2)) {
+                // Force delete
+                $dataHandler->deleteEl($table, $id, true);
+            }
+            $dataHandler->newlog2(($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "' . $table . '" uid ' . $id . '=>' . $swapWith, $table, $id, $swapVersion['pid']);
+            // Update reference index of the live record:
+            $dataHandler->addRemapStackRefIndex($table, $id);
+            // Set log entry for live record:
+            $propArr = $dataHandler->getRecordPropertiesFromRow($table, $swapVersion);
+            if ($propArr['_ORIG_pid'] == -1) {
+                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
+            } else {
+                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
+            }
+            $theLogId = $dataHandler->log($table, $id, 2, $propArr['pid'], 0, $label, 10, [$propArr['header'], $table . ':' . $id], $propArr['event_pid']);
+            $dataHandler->setHistory($table, $id, $theLogId);
+            // Update reference index of the offline record:
+            $dataHandler->addRemapStackRefIndex($table, $swapWith);
+            // Set log entry for offline record:
+            $propArr = $dataHandler->getRecordPropertiesFromRow($table, $curVersion);
+            if ($propArr['_ORIG_pid'] == -1) {
+                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.offline_record_updated');
+            } else {
+                $label = $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_tcemain.xlf:version_swap.online_record_updated');
+            }
+            $theLogId = $dataHandler->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, [$propArr['header'], $table . ':' . $swapWith], $propArr['event_pid']);
+            $dataHandler->setHistory($table, $swapWith, $theLogId);
+
+            $stageId = -20; // \TYPO3\CMS\Workspaces\Service\StagesService::STAGE_PUBLISH_EXECUTE_ID;
+            if ($notificationEmailInfo) {
+                $notificationEmailInfoKey = $wsAccess['uid'] . ':' . $stageId . ':' . $comment;
+                $this->notificationEmailInfo[$notificationEmailInfoKey]['shared'] = [$wsAccess, $stageId, $comment];
+                $this->notificationEmailInfo[$notificationEmailInfoKey]['elements'][] = $table . ':' . $id;
+                $this->notificationEmailInfo[$notificationEmailInfoKey]['alternativeRecipients'] = $notificationAlternativeRecipients;
+            } else {
+                $this->notifyStageChange($wsAccess, $stageId, $table, $id, $comment, $dataHandler, $notificationAlternativeRecipients);
+            }
+            // Write to log with stageId -20
+            $dataHandler->newlog2('Stage for record was changed to ' . $stageId . '. Comment was: "' . substr($comment, 0, 100) . '"', $table, $id);
+            $dataHandler->log($table, $id, 6, 0, 0, 'Published', 30, ['comment' => $comment, 'stage' => $stageId]);
+
+            // Clear cache:
+            $dataHandler->registerRecordIdForPageCacheClearing($table, $id);
+            // Checking for "new-placeholder" and if found, delete it (BUT FIRST after swapping!):
+            if (!$swapIntoWS && $t3ver_state['curVersion'] > 0) {
+                // For delete + completely delete!
+                $dataHandler->deleteEl($table, $swapWith, true, true);
+            }
+
+            //Update reference index for live workspace too:
+            /** @var $refIndexObj \TYPO3\CMS\Core\Database\ReferenceIndex */
+            $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
+            $refIndexObj->setWorkspaceId(0);
+            $refIndexObj->updateRefIndexTable($table, $id);
+            $refIndexObj->updateRefIndexTable($table, $swapWith);
+        }
+    }
+
+    /**
+     * Writes remapped foreign field (IRRE).
+     *
+     * @param \TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis Instance that holds the sorting order of child records
+     * @param array $configuration The TCA field configuration
+     * @param int $parentId The uid of the parent record
+     */
+    public function writeRemappedForeignField(\TYPO3\CMS\Core\Database\RelationHandler $dbAnalysis, array $configuration, $parentId)
+    {
+        foreach ($dbAnalysis->itemArray as &$item) {
+            if (isset($this->remappedIds[$item['table']][$item['id']])) {
+                $item['id'] = $this->remappedIds[$item['table']][$item['id']];
+            }
+        }
+        $dbAnalysis->writeForeignField($configuration, $parentId);
+    }
+
+    /**
+     * Processes fields of a record for the publishing/swapping process.
+     * Basically this takes care of IRRE (type "inline") child references.
+     *
+     * @param string $tableName Table name
+     * @param string $fieldName: Field name
+     * @param array $configuration TCA field configuration
+     * @param array $liveData: Live record data
+     * @param array $versionData: Version record data
+     * @param DataHandler $dataHandler Calling data-handler object
+     */
+    protected function version_swap_processFields($tableName, $fieldName, array $configuration, array $liveData, array $versionData, DataHandler $dataHandler)
+    {
+        $inlineType = $dataHandler->getInlineFieldType($configuration);
+        if ($inlineType !== 'field') {
+            return;
+        }
+        $foreignTable = $configuration['foreign_table'];
+        // Read relations that point to the current record (e.g. live record):
+        $liveRelations = $this->createRelationHandlerInstance();
+        $liveRelations->setWorkspaceId(0);
+        $liveRelations->start('', $foreignTable, '', $liveData['uid'], $tableName, $configuration);
+        // Read relations that point to the record to be swapped with e.g. draft record):
+        $versionRelations = $this->createRelationHandlerInstance();
+        $versionRelations->setUseLiveReferenceIds(false);
+        $versionRelations->start('', $foreignTable, '', $versionData['uid'], $tableName, $configuration);
+        // Update relations for both (workspace/versioning) sites:
+        if (count($liveRelations->itemArray)) {
+            $dataHandler->addRemapAction(
+                $tableName,
+                $liveData['uid'],
+                [$this, 'updateInlineForeignFieldSorting'],
+                [$tableName, $liveData['uid'], $foreignTable, $liveRelations->tableArray[$foreignTable], $configuration, $dataHandler->BE_USER->workspace]
+            );
+        }
+        if (count($versionRelations->itemArray)) {
+            $dataHandler->addRemapAction(
+                $tableName,
+                $liveData['uid'],
+                [$this, 'updateInlineForeignFieldSorting'],
+                [$tableName, $liveData['uid'], $foreignTable, $versionRelations->tableArray[$foreignTable], $configuration, 0]
+            );
+        }
+    }
+
+    /**
+     * Updates foreign field sorting values of versioned and live
+     * parents after(!) the whole structure has been published.
+     *
+     * This method is used as callback function in
+     * DataHandlerHook::version_swap_procBasedOnFieldType().
+     * Sorting fields ("sortby") are not modified during the
+     * workspace publishing/swapping process directly.
+     *
+     * @param string $parentTableName
+     * @param string $parentId
+     * @param string $foreignTableName
+     * @param int[] $foreignIds
+     * @param array $configuration
+     * @param int $targetWorkspaceId
+     * @internal
+     */
+    public function updateInlineForeignFieldSorting($parentTableName, $parentId, $foreignTableName, $foreignIds, array $configuration, $targetWorkspaceId)
+    {
+        $remappedIds = [];
+        // Use remapped ids (live id <-> version id)
+        foreach ($foreignIds as $foreignId) {
+            if (!empty($this->remappedIds[$foreignTableName][$foreignId])) {
+                $remappedIds[] = $this->remappedIds[$foreignTableName][$foreignId];
+            } else {
+                $remappedIds[] = $foreignId;
+            }
+        }
+
+        $relationHandler = $this->createRelationHandlerInstance();
+        $relationHandler->setWorkspaceId($targetWorkspaceId);
+        $relationHandler->setUseLiveReferenceIds(false);
+        $relationHandler->start(implode(',', $remappedIds), $foreignTableName);
+        $relationHandler->processDeletePlaceholder();
+        $relationHandler->writeForeignField($configuration, $parentId);
+    }
+
+    /**
+     * Release version from this workspace (and into "Live" workspace but as an offline version).
+     *
+     * @param string $table Table name
+     * @param int $id Record UID
+     * @param bool $flush If set, will completely delete element
+     * @param DataHandler $dataHandler DataHandler object
+     */
+    protected function version_clearWSID($table, $id, $flush = false, DataHandler $dataHandler)
+    {
+        if ($errorCode = $dataHandler->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
+            $dataHandler->newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1);
+            return;
+        }
+        if (!$dataHandler->checkRecordUpdateAccess($table, $id)) {
+            $dataHandler->newlog('Attempt to reset workspace for record failed because you do not have edit access', 1);
+            return;
+        }
+        $liveRec = BackendUtility::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state');
+        if (!$liveRec) {
+            return;
+        }
+        // Clear workspace ID:
+        $updateData = [
+            't3ver_wsid' => 0,
+            't3ver_tstamp' => $GLOBALS['EXEC_TIME']
+        ];
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
+        $connection->update(
+            $table,
+            $updateData,
+            ['uid' => (int)$id]
+        );
+
+        // Clear workspace ID for live version AND DELETE IT as well because it is a new record!
+        if (
+            VersionState::cast($liveRec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER)
+            || VersionState::cast($liveRec['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
+        ) {
+            $connection->update(
+                $table,
+                $updateData,
+                ['uid' => (int)$liveRec['uid']]
+            );
+
+            // THIS assumes that the record was placeholder ONLY for ONE record (namely $id)
+            $dataHandler->deleteEl($table, $liveRec['uid'], true);
+        }
+        // If "deleted" flag is set for the version that got released
+        // it doesn't make sense to keep that "placeholder" anymore and we delete it completly.
+        $wsRec = BackendUtility::getRecord($table, $id);
+        if (
+            $flush
+            || (
+                VersionState::cast($wsRec['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER)
+                || VersionState::cast($wsRec['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)
+            )
+        ) {
+            $dataHandler->deleteEl($table, $id, true, true);
+        }
+        // Remove the move-placeholder if found for live record.
+        if (BackendUtility::isTableWorkspaceEnabled($table)) {
+            if ($plhRec = BackendUtility::getMovePlaceholder($table, $liveRec['uid'], 'uid')) {
+                $dataHandler->deleteEl($table, $plhRec['uid'], true, true);
+            }
+        }
     }
 
     /**
@@ -159,4 +1320,376 @@ class DataHandlerHook
         $workspacesCache->flushByTag($workspaceId);
         $workspacesCache->flushByTag(\TYPO3\CMS\Workspaces\Service\WorkspaceService::SELECT_ALL_WORKSPACES);
     }
+
+    /*******************************
+     *****  helper functions  ******
+     *******************************/
+
+    /**
+     * Finds all elements for swapping versions in workspace
+     *
+     * @param string $table Table name of the original element to swap
+     * @param int $id UID of the original element to swap (online)
+     * @param int $offlineId As above but offline
+     * @return array Element data. Key is table name, values are array with first element as online UID, second - offline UID
+     */
+    public function findPageElementsForVersionSwap($table, $id, $offlineId)
+    {
+        $rec = BackendUtility::getRecord($table, $offlineId, 't3ver_wsid');
+        $workspaceId = (int)$rec['t3ver_wsid'];
+        $elementData = [];
+        if ($workspaceId === 0) {
+            return $elementData;
+        }
+        // Get page UID for LIVE and workspace
+        if ($table !== 'pages') {
+            $rec = BackendUtility::getRecord($table, $id, 'pid');
+            $pageId = $rec['pid'];
+            $rec = BackendUtility::getRecord('pages', $pageId);
+            BackendUtility::workspaceOL('pages', $rec, $workspaceId);
+            $offlinePageId = $rec['_ORIG_uid'];
+        } else {
+            $pageId = $id;
+            $offlinePageId = $offlineId;
+        }
+        // Traversing all tables supporting versioning:
+        foreach ($GLOBALS['TCA'] as $table => $cfg) {
+            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($table);
+
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
+                $statement = $queryBuilder
+                    ->select('A.uid AS offlineUid', 'B.uid AS uid')
+                    ->from($table, 'A')
+                    ->from($table, 'B')
+                    ->where(
+                        $queryBuilder->expr()->eq(
+                            'A.pid',
+                            $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
+                        ),
+                        $queryBuilder->expr()->eq(
+                            'B.pid',
+                            $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
+                        ),
+                        $queryBuilder->expr()->eq(
+                            'A.t3ver_wsid',
+                            $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
+                        ),
+                        $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
+                    )
+                    ->execute();
+
+                while ($row = $statement->fetch()) {
+                    $elementData[$table][] = [$row['uid'], $row['offlineUid']];
+                }
+            }
+        }
+        if ($offlinePageId && $offlinePageId != $pageId) {
+            $elementData['pages'][] = [$pageId, $offlinePageId];
+        }
+
+        return $elementData;
+    }
+
+    /**
+     * Searches for all elements from all tables on the given pages in the same workspace.
+     *
+     * @param array $pageIdList List of PIDs to search
+     * @param int $workspaceId Workspace ID
+     * @param array $elementList List of found elements. Key is table name, value is array of element UIDs
+     */
+    public function findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList)
+    {
+        if ($workspaceId == 0) {
+            return;
+        }
+        // Traversing all tables supporting versioning:
+        foreach ($GLOBALS['TCA'] as $table => $cfg) {
+            if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                    ->getQueryBuilderForTable($table);
+
+                $queryBuilder->getRestrictions()
+                    ->removeAll()
+                    ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
+                $statement = $queryBuilder
+                    ->select('A.uid')
+                    ->from($table, 'A')
+                    ->from($table, 'B')
+                    ->where(
+                        $queryBuilder->expr()->eq(
+                            'A.pid',
+                            $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
+                        ),
+                        $queryBuilder->expr()->in(
+                            'B.pid',
+                            $queryBuilder->createNamedParameter($pageIdList, Connection::PARAM_INT_ARRAY)
+                        ),
+                        $queryBuilder->expr()->eq(
+                            'A.t3ver_wsid',
+                            $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
+                        ),
+                        $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
+                    )
+                    ->groupBy('A.uid')
+                    ->execute();
+
+                while ($row = $statement->fetch()) {
+                    $elementList[$table][] = $row['uid'];
+                }
+                if (is_array($elementList[$table])) {
+                    // Yes, it is possible to get non-unique array even with DISTINCT above!
+                    // It happens because several UIDs are passed in the array already.
+                    $elementList[$table] = array_unique($elementList[$table]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Finds page UIDs for the element from table <code>$table</code> with UIDs from <code>$idList</code>
+     *
+     * @param string $table Table to search
+     * @param array $idList List of records' UIDs
+     * @param int $workspaceId Workspace ID. We need this parameter because user can be in LIVE but he still can publisg DRAFT from ws module!
+     * @param array $pageIdList List of found page UIDs
+     * @param array $elementList List of found element UIDs. Key is table name, value is list of UIDs
+     */
+    public function findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList)
+    {
+        if ($workspaceId == 0) {
+            return;
+        }
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($table);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
+        $statement = $queryBuilder
+            ->select('B.pid')
+            ->from($table, 'A')
+            ->from($table, 'B')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    'A.pid',
+                    $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
+                ),
+                $queryBuilder->expr()->eq(
+                    'A.t3ver_wsid',
+                    $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
+                ),
+                $queryBuilder->expr()->in(
+                    'A.uid',
+                    $queryBuilder->createNamedParameter($idList, Connection::PARAM_INT_ARRAY)
+                ),
+                $queryBuilder->expr()->eq('A.t3ver_oid', $queryBuilder->quoteIdentifier('B.uid'))
+            )
+            ->groupBy('B.pid')
+            ->execute();
+
+        while ($row = $statement->fetch()) {
+            $pageIdList[] = $row['pid'];
+            // Find ws version
+            // Note: cannot use BackendUtility::getRecordWSOL()
+            // here because it does not accept workspace id!
+            $rec = BackendUtility::getRecord('pages', $row[0]);
+            BackendUtility::workspaceOL('pages', $rec, $workspaceId);
+            if ($rec['_ORIG_uid']) {
+                $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
+            }
+        }
+        // The line below is necessary even with DISTINCT
+        // because several elements can be passed by caller
+        $pageIdList = array_unique($pageIdList);
+    }
+
+    /**
+     * Finds real page IDs for state change.
+     *
+     * @param array $idList List of page UIDs, possibly versioned
+     */
+    public function findRealPageIds(array &$idList)
+    {
+        foreach ($idList as $key => $id) {
+            $rec = BackendUtility::getRecord('pages', $id, 't3ver_oid');
+            if ($rec['t3ver_oid'] > 0) {
+                $idList[$key] = $rec['t3ver_oid'];
+            }
+        }
+    }
+
+    /**
+     * Creates a move placeholder for workspaces.
+     * USE ONLY INTERNALLY
+     * Moving placeholder: Can be done because the system sees it as a placeholder for NEW elements like t3ver_state=VersionState::NEW_PLACEHOLDER
+     * Moving original: Will either create the placeholder if it doesn't exist or move existing placeholder in workspace.
+     *
+     * @param string $table Table name to move
+     * @param int $uid Record uid to move (online record)
+     * @param int $destPid Position to move to: $destPid: >=0 then it points to a page-id on which to insert the record (as the first element). <0 then it points to a uid from its own table after which to insert it (works if
+     * @param int $wsUid UID of offline version of online record
+     * @param DataHandler $dataHandler DataHandler object
+     * @see moveRecord()
+     */
+    protected function moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, DataHandler $dataHandler)
+    {
+        // If a record gets moved after a record that already has a placeholder record
+        // then the new placeholder record needs to be after the existing one
+        $originalRecordDestinationPid = $destPid;
+        if ($destPid < 0) {
+            $movePlaceHolder = BackendUtility::getMovePlaceholder($table, abs($destPid), 'uid');
+            if ($movePlaceHolder !== false) {
+                $destPid = -$movePlaceHolder['uid'];
+            }
+        }
+        if ($plh = BackendUtility::getMovePlaceholder($table, $uid, 'uid')) {
+            // If already a placeholder exists, move it:
+            $dataHandler->moveRecord_raw($table, $plh['uid'], $destPid);
+        } else {
+            // First, we create a placeholder record in the Live workspace that
+            // represents the position to where the record is eventually moved to.
+            $newVersion_placeholderFieldArray = [];
+
+            // Use property for move placeholders if set (since TYPO3 CMS 6.2)
+            if (isset($GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForMovePlaceholders'])) {
+                $shadowColumnsForMovePlaceholder = $GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForMovePlaceholders'];
+            } elseif (isset($GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForNewPlaceholders'])) {
+                // Fallback to property for new placeholder (existed long time before TYPO3 CMS 6.2)
+                $shadowColumnsForMovePlaceholder = $GLOBALS['TCA'][$table]['ctrl']['shadowColumnsForNewPlaceholders'];
+            }
+
+            // Set values from the versioned record to the move placeholder
+            if (!empty($shadowColumnsForMovePlaceholder)) {
+                $versionedRecord = BackendUtility::getRecord($table, $wsUid);
+                $shadowColumns = GeneralUtility::trimExplode(',', $shadowColumnsForMovePlaceholder, true);
+                foreach ($shadowColumns as $shadowColumn) {
+                    if (isset($versionedRecord[$shadowColumn])) {
+                        $newVersion_placeholderFieldArray[$shadowColumn] = $versionedRecord[$shadowColumn];
+                    }
+                }
+            }
+
+            if ($GLOBALS['TCA'][$table]['ctrl']['crdate']) {
+                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['crdate']] = $GLOBALS['EXEC_TIME'];
+            }
+            if ($GLOBALS['TCA'][$table]['ctrl']['cruser_id']) {
+                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['cruser_id']] = $dataHandler->userid;
+            }
+            if ($GLOBALS['TCA'][$table]['ctrl']['tstamp']) {
+                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
+            }
+            if ($table === 'pages') {
+                // Copy page access settings from original page to placeholder
+                $perms_clause = $dataHandler->BE_USER->getPagePermsClause(1);
+                $access = BackendUtility::readPageAccess($uid, $perms_clause);
+                $newVersion_placeholderFieldArray['perms_userid'] = $access['perms_userid'];
+                $newVersion_placeholderFieldArray['perms_groupid'] = $access['perms_groupid'];
+                $newVersion_placeholderFieldArray['perms_user'] = $access['perms_user'];
+                $newVersion_placeholderFieldArray['perms_group'] = $access['perms_group'];
+                $newVersion_placeholderFieldArray['perms_everybody'] = $access['perms_everybody'];
+            }
+            $newVersion_placeholderFieldArray['t3ver_label'] = 'MovePlaceholder #' . $uid;
+            $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
+            // Setting placeholder state value for temporary record
+            $newVersion_placeholderFieldArray['t3ver_state'] = (string)new VersionState(VersionState::MOVE_PLACEHOLDER);
+            // Setting workspace - only so display of place holders can filter out those from other workspaces.
+            $newVersion_placeholderFieldArray['t3ver_wsid'] = $dataHandler->BE_USER->workspace;
+            $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['label']] = $dataHandler->getPlaceholderTitleForTableLabel($table, 'MOVE-TO PLACEHOLDER for #' . $uid);
+            // moving localized records requires to keep localization-settings for the placeholder too
+            if (isset($GLOBALS['TCA'][$table]['ctrl']['languageField']) && isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])) {
+                $l10nParentRec = BackendUtility::getRecord($table, $uid);
+                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['languageField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
+                $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']];
+                if (isset($GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField'])) {
+                    $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']] = $l10nParentRec[$GLOBALS['TCA'][$table]['ctrl']['transOrigDiffSourceField']];
+                }
+                unset($l10nParentRec);
+            }
+            // Initially, create at root level.
+            $newVersion_placeholderFieldArray['pid'] = 0;
+            $id = 'NEW_MOVE_PLH';
+            // Saving placeholder as 'original'
+            $dataHandler->insertDB($table, $id, $newVersion_placeholderFieldArray, false);
+            // Move the new placeholder from temporary root-level to location:
+            $dataHandler->moveRecord_raw($table, $dataHandler->substNEWwithIDs[$id], $destPid);
+            // Move the workspace-version of the original to be the version of the move-to-placeholder:
+            // Setting placeholder state value for version (so it can know it is currently a new version...)
+            $updateFields = [
+                't3ver_state' => (string)new VersionState(VersionState::MOVE_POINTER)
+            ];
+
+            GeneralUtility::makeInstance(ConnectionPool::class)
+                ->getConnectionForTable($table)
+                ->update(
+                    $table,
+                    $updateFields,
+                    ['uid' => (int)$wsUid]
+                );
+        }
+        // Check for the localizations of that element and move them as well
+        $dataHandler->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
+    }
+
+    /**
+     * Gets an instance of the command map helper.
+     *
+     * @param DataHandler $dataHandler DataHandler object
+     * @return \TYPO3\CMS\Workspaces\DataHandler\CommandMap
+     */
+    public function getCommandMap(DataHandler $dataHandler)
+    {
+        return GeneralUtility::makeInstance(
+            \TYPO3\CMS\Workspaces\DataHandler\CommandMap::class,
+            $this,
+            $dataHandler,
+            $dataHandler->cmdmap,
+            $dataHandler->BE_USER->workspace
+        );
+    }
+
+    /**
+     * Returns all fieldnames from a table which have the unique evaluation type set.
+     *
+     * @param string $table Table name
+     * @return array Array of fieldnames
+     */
+    protected function getUniqueFields($table)
+    {
+        $listArr = [];
+        if (empty($GLOBALS['TCA'][$table]['columns'])) {
+            return $listArr;
+        }
+        foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
+            if ($configArr['config']['type'] === 'input') {
+                $evalCodesArray = GeneralUtility::trimExplode(',', $configArr['config']['eval'], true);
+                if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
+                    $listArr[] = $field;
+                }
+            }
+        }
+        return $listArr;
+    }
+
+    /**
+     * @return \TYPO3\CMS\Core\Database\RelationHandler
+     */
+    protected function createRelationHandlerInstance()
+    {
+        return GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\RelationHandler::class);
+    }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
 }
diff --git a/typo3/sysext/workspaces/Classes/Service/Dependency/CollectionService.php b/typo3/sysext/workspaces/Classes/Service/Dependency/CollectionService.php
index d376771d9cf2b9362e39774fd377d585584ff9be..86e78d6d58625c1bd5d8e886a6c758a4d82c336b 100644
--- a/typo3/sysext/workspaces/Classes/Service/Dependency/CollectionService.php
+++ b/typo3/sysext/workspaces/Classes/Service/Dependency/CollectionService.php
@@ -15,7 +15,7 @@ namespace TYPO3\CMS\Workspaces\Service\Dependency;
  */
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Version\Dependency;
+use TYPO3\CMS\Workspaces\Dependency;
 use TYPO3\CMS\Workspaces\Service\GridDataService;
 
 /**
@@ -54,7 +54,7 @@ class CollectionService implements \TYPO3\CMS\Core\SingletonInterface
     public function getDependencyResolver()
     {
         if (!isset($this->dependencyResolver)) {
-            $this->dependencyResolver = GeneralUtility::makeInstance(\TYPO3\CMS\Version\Dependency\DependencyResolver::class);
+            $this->dependencyResolver = GeneralUtility::makeInstance(\TYPO3\CMS\Workspaces\Dependency\DependencyResolver::class);
             $this->dependencyResolver->setOuterMostParentsRequireReferences(true);
             $this->dependencyResolver->setWorkspace($this->getWorkspace());
 
@@ -87,7 +87,7 @@ class CollectionService implements \TYPO3\CMS\Core\SingletonInterface
     protected function getDependencyCallback($method, array $targetArguments = [])
     {
         return GeneralUtility::makeInstance(
-            \TYPO3\CMS\Version\Dependency\EventCallback::class,
+            \TYPO3\CMS\Workspaces\Dependency\EventCallback::class,
             $this->getElementEntityProcessor(),
             $method,
             $targetArguments
diff --git a/typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php b/typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php
index 38a19d0d9229d50a5047c82b2db85760b24d71c7..0394c52e8959327b9729a4b1b614b643499f5b35 100644
--- a/typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php
+++ b/typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php
@@ -1,4 +1,15 @@
 <?php
 return [
     'TYPO3\\CMS\\Lowlevel\\Command\\WorkspaceVersionRecordsCommand' => \TYPO3\CMS\Workspaces\Command\WorkspaceVersionRecordsCommand::class,
+    'TYPO3\\CMS\\Version\\DataHandler\\CommandMap' => \TYPO3\CMS\Workspaces\DataHandler\CommandMap::class,
+    'TYPO3\\CMS\\Version\\Dependency\\DependencyEntityFactory' => \TYPO3\CMS\Workspaces\Dependency\DependencyEntityFactory::class,
+    'TYPO3\\CMS\\Version\\Dependency\\DependencyResolver' => \TYPO3\CMS\Workspaces\Dependency\DependencyResolver::class,
+    'TYPO3\\CMS\\Version\\Dependency\\ElementEntity' => \TYPO3\CMS\Workspaces\Dependency\ElementEntity::class,
+    'TYPO3\\CMS\\Version\\Dependency\\ElementEntityProcessor' => \TYPO3\CMS\Workspaces\Dependency\ElementEntityProcessor::class,
+    'TYPO3\\CMS\\Version\\Dependency\\EventCallback' => \TYPO3\CMS\Workspaces\Dependency\EventCallback::class,
+    'TYPO3\\CMS\\Version\\Dependency\\ReferenceEntity' => \TYPO3\CMS\Workspaces\Dependency\ReferenceEntity::class,
+    'TYPO3\\CMS\\Version\\Hook\\DataHandlerHook' => \TYPO3\CMS\Workspaces\Hook\DataHandlerHook::class,
+    'TYPO3\\CMS\\Version\\Hook\\PreviewHook' => \TYPO3\CMS\Workspaces\Hook\PreviewHook::class,
+    'TYPO3\\CMS\\Version\\Task\\AutoPublishTask' => \TYPO3\CMS\Workspaces\Task\AutoPublishTask::class,
+    'TYPO3\\CMS\\Version\\Utility\\WorkspacesUtility' => \TYPO3\CMS\Workspaces\Service\WorkspaceService::class,
 ];
diff --git a/typo3/sysext/version/Resources/Private/Language/locallang_emails.xlf b/typo3/sysext/workspaces/Resources/Private/Language/locallang_emails.xlf
similarity index 98%
rename from typo3/sysext/version/Resources/Private/Language/locallang_emails.xlf
rename to typo3/sysext/workspaces/Resources/Private/Language/locallang_emails.xlf
index 9f43bf681326d1087f73d7932796ba54b778fc50..91fc47b4385ba17776426970a284e9f184be0292 100644
--- a/typo3/sysext/version/Resources/Private/Language/locallang_emails.xlf
+++ b/typo3/sysext/workspaces/Resources/Private/Language/locallang_emails.xlf
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
-	<file t3:id="1415815010" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:37Z" product-name="version">
+	<file t3:id="1415815010" source-language="en" datatype="plaintext" original="messages" date="2011-10-17T20:22:37Z" product-name="workspaces">
 		<header/>
 		<body>
 			<trans-unit id="subject">
diff --git a/typo3/sysext/workspaces/Tests/Functional/ActionHandler/ActionHandlerTest.php b/typo3/sysext/workspaces/Tests/Functional/ActionHandler/ActionHandlerTest.php
index 40ed63b024e000a46a9324db7676def9c2d6cfbf..28a603a1f188c7af45493b21f0723fe8473fdef9 100644
--- a/typo3/sysext/workspaces/Tests/Functional/ActionHandler/ActionHandlerTest.php
+++ b/typo3/sysext/workspaces/Tests/Functional/ActionHandler/ActionHandlerTest.php
@@ -24,7 +24,7 @@ class ActionHandlerTest extends \TYPO3\TestingFramework\Core\Functional\Function
     /**
      * @var array
      */
-    protected $coreExtensionsToLoad = ['version', 'workspaces'];
+    protected $coreExtensionsToLoad = ['workspaces'];
 
     /**
      * Set up
diff --git a/typo3/sysext/workspaces/Tests/Functional/DataHandling/FAL/AbstractActionTestCase.php b/typo3/sysext/workspaces/Tests/Functional/DataHandling/FAL/AbstractActionTestCase.php
index 5966409d92fe604f3e602af7bfc2016b1ddc0464..65704a6f1fa36f2ed888597e329d16fd4aa5f72f 100644
--- a/typo3/sysext/workspaces/Tests/Functional/DataHandling/FAL/AbstractActionTestCase.php
+++ b/typo3/sysext/workspaces/Tests/Functional/DataHandling/FAL/AbstractActionTestCase.php
@@ -31,7 +31,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     protected $coreExtensionsToLoad = [
         'fluid',
-        'version',
         'workspaces',
     ];
 
diff --git a/typo3/sysext/workspaces/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php b/typo3/sysext/workspaces/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php
index 901ee06bfd327645122d432d373bb6e91ac890a8..f36c84d2793a8322d3c22f46afb0851b5d32cd60 100644
--- a/typo3/sysext/workspaces/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php
+++ b/typo3/sysext/workspaces/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php
@@ -31,7 +31,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     protected $coreExtensionsToLoad = [
         'fluid',
-        'version',
         'workspaces',
     ];
 
diff --git a/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php b/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php
index f9aa0efbf88c2a6e0015f058962304b24bbec2c7..68220e1c5d76a4d977420725e5bf1e0efb954c9d 100644
--- a/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php
+++ b/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php
@@ -31,7 +31,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     protected $coreExtensionsToLoad = [
         'fluid',
-        'version',
         'workspaces',
     ];
 
diff --git a/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php b/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
index c73f275ecb619fb208d0852d6aa834418d5b7192..ec59a4be36d557e26e67fda74e40156bed6c847c 100644
--- a/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
+++ b/typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
@@ -31,7 +31,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     protected $coreExtensionsToLoad = [
         'fluid',
-        'version',
         'workspaces',
     ];
 
diff --git a/typo3/sysext/workspaces/Tests/Functional/DataHandling/ManyToMany/AbstractActionTestCase.php b/typo3/sysext/workspaces/Tests/Functional/DataHandling/ManyToMany/AbstractActionTestCase.php
index 03bc55294d0c822db00ecb44bd3823d0ac48415a..db227695cb1c015b6ab0b7b29560a57479bdcd5d 100644
--- a/typo3/sysext/workspaces/Tests/Functional/DataHandling/ManyToMany/AbstractActionTestCase.php
+++ b/typo3/sysext/workspaces/Tests/Functional/DataHandling/ManyToMany/AbstractActionTestCase.php
@@ -32,7 +32,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     protected $coreExtensionsToLoad = [
         'fluid',
-        'version',
         'workspaces',
     ];
 
diff --git a/typo3/sysext/workspaces/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php b/typo3/sysext/workspaces/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php
index 26863222221f8105ac6440dbf38f756545d09a81..dd26a064719cdb7fa61319be79ed34f7ed711998 100644
--- a/typo3/sysext/workspaces/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php
+++ b/typo3/sysext/workspaces/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php
@@ -33,7 +33,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     protected $coreExtensionsToLoad = [
         'fluid',
-        'version',
         'workspaces',
     ];
 
diff --git a/typo3/sysext/workspaces/Tests/Functional/DataHandling/Select/AbstractActionTestCase.php b/typo3/sysext/workspaces/Tests/Functional/DataHandling/Select/AbstractActionTestCase.php
index c3bb4dcce2f691d1f655ab361e8168295ff6bbbb..86f0005076fcacf5a12f7061e0b503e6738d75d6 100644
--- a/typo3/sysext/workspaces/Tests/Functional/DataHandling/Select/AbstractActionTestCase.php
+++ b/typo3/sysext/workspaces/Tests/Functional/DataHandling/Select/AbstractActionTestCase.php
@@ -31,7 +31,6 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
      */
     protected $coreExtensionsToLoad = [
         'fluid',
-        'version',
         'workspaces',
     ];
 
diff --git a/typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceServiceTest.php b/typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceServiceTest.php
index 88a6884cdae7451ad6934e70656780b8c8a86e99..c8ecf179ec0d2b17589ab06aa0818a275c7c9c89 100644
--- a/typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceServiceTest.php
+++ b/typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceServiceTest.php
@@ -26,7 +26,7 @@ class WorkspaceServiceTest extends FunctionalTestCase
     /**
      * @var array
      */
-    protected $coreExtensionsToLoad = ['version', 'workspaces'];
+    protected $coreExtensionsToLoad = ['workspaces'];
 
     /**
      * Set up
diff --git a/typo3/sysext/workspaces/composer.json b/typo3/sysext/workspaces/composer.json
index 1f5d2f4a80ab5d8b40e723e0c1530ac124f783de..4eb1abfa81e0a9851e243417f4e981b32d471e68 100644
--- a/typo3/sysext/workspaces/composer.json
+++ b/typo3/sysext/workspaces/composer.json
@@ -11,8 +11,7 @@
 		}],
 
 	"require": {
-		"typo3/cms-core": ">=9.0.0 <=9.0.99",
-		"typo3/cms-version": ">=9.0.0 <=9.0.99"
+		"typo3/cms-core": ">=9.0.0 <=9.0.99"
 	},
 	"conflict": {
 		"typo3/cms": "*"
diff --git a/typo3/sysext/workspaces/ext_emconf.php b/typo3/sysext/workspaces/ext_emconf.php
index b8b00d119fa7f981549d5039cc40edec136d22d9..d5d684c5a2488ae937317e74c4d9c282f6a7d97d 100644
--- a/typo3/sysext/workspaces/ext_emconf.php
+++ b/typo3/sysext/workspaces/ext_emconf.php
@@ -1,7 +1,7 @@
 <?php
 $EM_CONF[$_EXTKEY] = [
     'title' => 'Workspaces Management',
-    'description' => 'Adds workspaces functionality with custom stages to TYPO3.',
+    'description' => 'Adds versioning of records and workspaces functionality with custom stages to TYPO3.',
     'category' => 'be',
     'author' => 'TYPO3 Core Team',
     'author_email' => 'typo3cms@typo3.org',
@@ -13,8 +13,7 @@ $EM_CONF[$_EXTKEY] = [
     'version' => '9.0.0',
     'constraints' => [
         'depends' => [
-            'typo3' => '9.0.0-9.0.99',
-            'version' => '9.0.0-9.0.99',
+            'typo3' => '9.0.0-9.0.99'
         ],
         'conflicts' => [],
         'suggests' => [],
diff --git a/typo3/sysext/workspaces/ext_localconf.php b/typo3/sysext/workspaces/ext_localconf.php
index 5797317942f261ebadbf1b65ef6dd76493d9b1c1..a8cf15a86fc93d502194200e2647642a6f075762 100644
--- a/typo3/sysext/workspaces/ext_localconf.php
+++ b/typo3/sysext/workspaces/ext_localconf.php
@@ -1,6 +1,13 @@
 <?php
 defined('TYPO3_MODE') or die();
 
+// add default notification options to every page
+\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addPageTSConfig('
+tx_version.workspaces.stageNotificationEmail.subject = LLL:EXT:workspaces/Resources/Private/Language/locallang_emails.xlf:subject
+tx_version.workspaces.stageNotificationEmail.message = LLL:EXT:workspaces/Resources/Private/Language/locallang_emails.xlf:message
+# tx_version.workspaces.stageNotificationEmail.additionalHeaders =
+');
+
 // Register the autopublishing task
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Workspaces\Task\AutoPublishTask::class] = [
     'extension' => 'workspaces',
@@ -15,7 +22,9 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][\TYPO3\CMS\Works
     'description' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:cleanupPreviewLinkTask.description'
 ];
 
+// register the hook to actually do the work within DataHandler
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['workspaces'] = \TYPO3\CMS\Workspaces\Hook\DataHandlerHook::class;
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass']['version'] = \TYPO3\CMS\Workspaces\Hook\DataHandlerHook::class;
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass']['workspaces'] = \TYPO3\CMS\Workspaces\Hook\BackendUtilityHook::class;
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['hook_previewInfo']['workspaces'] = \TYPO3\CMS\Workspaces\Hook\TypoScriptFrontendControllerHook::class . '->renderPreviewInfo';
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck']['workspaces'] = \TYPO3\CMS\Workspaces\Hook\BackendUtilityHook::class . '->makeEditForm_accessCheck';