[TASK] Move classes to new target location
authorThomas Maroschik <tmaroschik@dfau.de>
Tue, 21 Aug 2012 03:14:27 +0000 (05:14 +0200)
committerThomas Maroschik <tmaroschik@dfau.de>
Tue, 21 Aug 2012 03:39:27 +0000 (05:39 +0200)
30 files changed:
typo3/sysext/version/Classes/ClickMenu/VersionClickMenu.php [new file with mode: 0644]
typo3/sysext/version/Classes/Controller/VersionModuleController.php [new file with mode: 0644]
typo3/sysext/version/Classes/Controller/WorkspaceModuleController.php [new file with mode: 0644]
typo3/sysext/version/Classes/DataHandler/CommandMap.php [new file with mode: 0644]
typo3/sysext/version/Classes/Dependency/DependencyEntityFactory.php [new file with mode: 0644]
typo3/sysext/version/Classes/Dependency/DependencyResolver.php [new file with mode: 0644]
typo3/sysext/version/Classes/Dependency/ElementEntity.php [new file with mode: 0644]
typo3/sysext/version/Classes/Dependency/EventCallback.php [new file with mode: 0644]
typo3/sysext/version/Classes/Dependency/ReferenceEntity.php [new file with mode: 0644]
typo3/sysext/version/Classes/Hook/DataHandlerHook.php [new file with mode: 0644]
typo3/sysext/version/Classes/Hook/IconUtilityHook.php [new file with mode: 0644]
typo3/sysext/version/Classes/Hook/PreviewHook.php [new file with mode: 0644]
typo3/sysext/version/Classes/Preview.php
typo3/sysext/version/Classes/Task/AutoPublishTask.php [new file with mode: 0644]
typo3/sysext/version/Classes/Utility/WorkspacesUtility.php [new file with mode: 0644]
typo3/sysext/version/Classes/View/VersionView.php [new file with mode: 0644]
typo3/sysext/version/class.tx_version_cm1.php
typo3/sysext/version/class.tx_version_gui.php
typo3/sysext/version/class.tx_version_iconworks.php
typo3/sysext/version/class.tx_version_tcemain.php
typo3/sysext/version/class.tx_version_tcemain_commandmap.php
typo3/sysext/version/cm1/conf.php
typo3/sysext/version/cm1/index.php
typo3/sysext/version/ext_autoload.php
typo3/sysext/version/ext_emconf.php
typo3/sysext/version/ext_localconf.php
typo3/sysext/version/ext_tables.php
typo3/sysext/version/tasks/class.tx_version_tasks_autopublish.php
typo3/sysext/version/ws/class.wslib.php
typo3/sysext/version/ws/index.php

diff --git a/typo3/sysext/version/Classes/ClickMenu/VersionClickMenu.php b/typo3/sysext/version/Classes/ClickMenu/VersionClickMenu.php
new file mode 100644 (file)
index 0000000..150eb1d
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2004-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * "Versioning" item added to click menu of elements.
+ *
+ * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
+ * @package TYPO3
+ * @subpackage core
+ */
+class tx_version_cm1 {
+
+       /**
+        * Main function, adding the item to input menuItems array
+        *
+        * @param       object          References to parent clickmenu objects.
+        * @param       array           Array of existing menu items accumulated. New element added to this.
+        * @param       string          Table name of the element
+        * @param       integer         Record UID of the element
+        * @return      array           Modified menuItems array
+        * @todo Define visibility
+        */
+       public function main(&$backRef, $menuItems, $table, $uid) {
+               $localItems = array();
+               if ((!$backRef->cmLevel && $uid > 0) && $GLOBALS['BE_USER']->check('modules', 'web_txversionM1')) {
+                       // Returns directly, because the clicked item was not from the pages table
+                       if ((in_array('versioning', $backRef->disabledItems) || !$GLOBALS['TCA'][$table]) || !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                               return $menuItems;
+                       }
+                       // Adds the regular item
+                       $LL = $this->includeLL();
+                       // "Versioning" element added:
+                       $url = (((t3lib_extMgm::extRelPath('version') . 'cm1/index.php?table=') . rawurlencode($table)) . '&uid=') . $uid;
+                       $localItems[] = $backRef->linkItem($GLOBALS['LANG']->getLLL('title', $LL), $backRef->excludeIcon((('<img src="' . $backRef->backPath) . t3lib_extMgm::extRelPath('version')) . 'cm1/cm_icon.gif" width="15" height="12" border="0" align="top" alt="" />'), $backRef->urlRefForCM($url), 1);
+                       // "Send to review" element added:
+                       /*
+                       $url = t3lib_extMgm::extRelPath('version').'cm1/index.php?id='.($table=='pages'?$uid:$backRef->rec['pid']).'&table='.rawurlencode($table).'&uid='.$uid.'&sendToReview=1';
+                       $localItems[] = $backRef->linkItem(
+                       $GLOBALS['LANG']->getLLL('title_review', $LL),
+                       $backRef->excludeIcon('<img src="'.$backRef->backPath.t3lib_extMgm::extRelPath('version').'cm1/cm_icon.gif" width="15" height="12" border="0" align="top" alt="" />'),
+                       $backRef->urlRefForCM($url),
+                       1
+                       );
+                        */
+                       // Find position of "delete" element:
+                       $c = 0;
+                       foreach ($menuItems as $k => $value) {
+                               $c++;
+                               if (!strcmp($k, 'delete')) {
+                                       break;
+                               }
+                       }
+                       // .. subtract two (delete item + divider line)
+                       $c -= 2;
+                       // ... and insert the items just before the delete element.
+                       array_splice($menuItems, $c, 0, $localItems);
+               }
+               return $menuItems;
+       }
+
+       /**
+        * Includes the [extDir]/locallang.php and returns the $LOCAL_LANG array found in that file.
+        *
+        * @return      array           Local lang array
+        * @todo Define visibility
+        */
+       public function includeLL() {
+               return $GLOBALS['LANG']->includeLLFile('EXT:version/locallang.xml', FALSE);
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Controller/VersionModuleController.php b/typo3/sysext/version/Classes/Controller/VersionModuleController.php
new file mode 100644 (file)
index 0000000..150eb1d
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2004-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * "Versioning" item added to click menu of elements.
+ *
+ * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
+ * @package TYPO3
+ * @subpackage core
+ */
+class tx_version_cm1 {
+
+       /**
+        * Main function, adding the item to input menuItems array
+        *
+        * @param       object          References to parent clickmenu objects.
+        * @param       array           Array of existing menu items accumulated. New element added to this.
+        * @param       string          Table name of the element
+        * @param       integer         Record UID of the element
+        * @return      array           Modified menuItems array
+        * @todo Define visibility
+        */
+       public function main(&$backRef, $menuItems, $table, $uid) {
+               $localItems = array();
+               if ((!$backRef->cmLevel && $uid > 0) && $GLOBALS['BE_USER']->check('modules', 'web_txversionM1')) {
+                       // Returns directly, because the clicked item was not from the pages table
+                       if ((in_array('versioning', $backRef->disabledItems) || !$GLOBALS['TCA'][$table]) || !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                               return $menuItems;
+                       }
+                       // Adds the regular item
+                       $LL = $this->includeLL();
+                       // "Versioning" element added:
+                       $url = (((t3lib_extMgm::extRelPath('version') . 'cm1/index.php?table=') . rawurlencode($table)) . '&uid=') . $uid;
+                       $localItems[] = $backRef->linkItem($GLOBALS['LANG']->getLLL('title', $LL), $backRef->excludeIcon((('<img src="' . $backRef->backPath) . t3lib_extMgm::extRelPath('version')) . 'cm1/cm_icon.gif" width="15" height="12" border="0" align="top" alt="" />'), $backRef->urlRefForCM($url), 1);
+                       // "Send to review" element added:
+                       /*
+                       $url = t3lib_extMgm::extRelPath('version').'cm1/index.php?id='.($table=='pages'?$uid:$backRef->rec['pid']).'&table='.rawurlencode($table).'&uid='.$uid.'&sendToReview=1';
+                       $localItems[] = $backRef->linkItem(
+                       $GLOBALS['LANG']->getLLL('title_review', $LL),
+                       $backRef->excludeIcon('<img src="'.$backRef->backPath.t3lib_extMgm::extRelPath('version').'cm1/cm_icon.gif" width="15" height="12" border="0" align="top" alt="" />'),
+                       $backRef->urlRefForCM($url),
+                       1
+                       );
+                        */
+                       // Find position of "delete" element:
+                       $c = 0;
+                       foreach ($menuItems as $k => $value) {
+                               $c++;
+                               if (!strcmp($k, 'delete')) {
+                                       break;
+                               }
+                       }
+                       // .. subtract two (delete item + divider line)
+                       $c -= 2;
+                       // ... and insert the items just before the delete element.
+                       array_splice($menuItems, $c, 0, $localItems);
+               }
+               return $menuItems;
+       }
+
+       /**
+        * Includes the [extDir]/locallang.php and returns the $LOCAL_LANG array found in that file.
+        *
+        * @return      array           Local lang array
+        * @todo Define visibility
+        */
+       public function includeLL() {
+               return $GLOBALS['LANG']->includeLLFile('EXT:version/locallang.xml', FALSE);
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Controller/WorkspaceModuleController.php b/typo3/sysext/version/Classes/Controller/WorkspaceModuleController.php
new file mode 100644 (file)
index 0000000..cab2a9a
--- /dev/null
@@ -0,0 +1,880 @@
+<?php
+/**
+ * Module: Workspace manager
+ *
+ * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
+ * @package TYPO3
+ * @subpackage core
+ */
+class SC_mod_user_ws_index extends t3lib_SCbase {
+
+       // Default variables for backend modules
+       /**
+        * @todo Define visibility
+        */
+       public $MCONF = array();
+
+       // Module configuration
+       /**
+        * @todo Define visibility
+        */
+       public $MOD_MENU = array();
+
+       // Module menu items
+       /**
+        * @todo Define visibility
+        */
+       public $MOD_SETTINGS = array();
+
+       // Module session settings
+       /**
+        * Document Template Object
+        *
+        * @var noDoc
+        * @todo Define visibility
+        */
+       public $doc;
+
+       /**
+        * @todo Define visibility
+        */
+       public $content;
+
+       // Accumulated content
+       // Internal:
+       /**
+        * @todo Define visibility
+        */
+       public $publishAccess = FALSE;
+
+       /**
+        * @todo Define visibility
+        */
+       public $be_user_Array = array();
+
+       /**
+        * @todo Define visibility
+        */
+       public $be_user_Array_full = array();
+
+       // not blinded, used by workspace listing
+       protected $showDraftWorkspace = FALSE;
+
+       // Determines whether the draft workspace is shown
+       /*********************************
+        *
+        * Standard module initialization
+        *
+        *********************************/
+       /**
+        * Initialize menu configuration
+        *
+        * @return      void
+        * @todo Define visibility
+        */
+       public function menuConfig() {
+               // fetches the configuration of the version extension
+               $versionExtconf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['version']);
+               // show draft workspace only if enabled in the version extensions config
+               if ($versionExtconf['showDraftWorkspace']) {
+                       $this->showDraftWorkspace = TRUE;
+               }
+               // Menu items:
+               $this->MOD_MENU = array(
+                       'function' => array(
+                               'publish' => $GLOBALS['LANG']->getLL('menuitem_review'),
+                               'workspaces' => $GLOBALS['LANG']->getLL('menuitem_workspaces')
+                       ),
+                       'filter' => array(
+                               1 => $GLOBALS['LANG']->getLL('filter_drafts'),
+                               2 => $GLOBALS['LANG']->getLL('filter_archive'),
+                               0 => $GLOBALS['LANG']->getLL('filter_all')
+                       ),
+                       'display' => array(
+                               0 => ('[' . $GLOBALS['LANG']->getLL('shortcut_onlineWS')) . ']',
+                               -98 => $GLOBALS['LANG']->getLL('label_offlineWSes'),
+                               -99 => $GLOBALS['LANG']->getLL('label_allWSes')
+                       ),
+                       'diff' => array(
+                               0 => $GLOBALS['LANG']->getLL('diff_no_diff'),
+                               1 => $GLOBALS['LANG']->getLL('diff_show_inline'),
+                               2 => $GLOBALS['LANG']->getLL('diff_show_popup')
+                       ),
+                       'expandSubElements' => ''
+               );
+               // check if draft workspace was enabled, and if the user has access to it
+               if ($this->showDraftWorkspace === TRUE && $GLOBALS['BE_USER']->checkWorkspace(array('uid' => -1))) {
+                       $this->MOD_MENU['display'][-1] = ('[' . $GLOBALS['LANG']->getLL('shortcut_offlineWS')) . ']';
+               }
+               // Add workspaces:
+               if ($GLOBALS['BE_USER']->workspace === 0) {
+                       // Spend time on this only in online workspace because it might take time:
+                       $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,title,adminusers,members,reviewers', 'sys_workspace', 'pid = 0' . t3lib_BEfunc::deleteClause('sys_workspace'), '', 'title');
+                       foreach ($workspaces as $rec) {
+                               if ($GLOBALS['BE_USER']->checkWorkspace($rec)) {
+                                       $this->MOD_MENU['display'][$rec['uid']] = (('[' . $rec['uid']) . '] ') . htmlspecialchars($rec['title']);
+                               }
+                       }
+               }
+               // CLEANSE SETTINGS
+               $this->MOD_SETTINGS = t3lib_BEfunc::getModuleData($this->MOD_MENU, t3lib_div::_GP('SET'), $this->MCONF['name'], 'ses');
+       }
+
+       /**
+        * Executes action for selected elements, if any is sent:
+        *
+        * @todo Define visibility
+        */
+       public function execute() {
+               $post = t3lib_div::_POST();
+               if ($post['_with_selected_do']) {
+                       if (is_array($post['items']) && count($post['items'])) {
+                               $cmdArray = array();
+                               foreach ($post['items'] as $item => $v) {
+                                       list($table, $uid) = explode(':', $item, 2);
+                                       if ($GLOBALS['TCA'][$table] && t3lib_utility_Math::canBeInterpretedAsInteger($uid)) {
+                                               switch ($post['_with_selected_do']) {
+                                               case 'stage_-1':
+                                                       $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+                                                       $cmdArray[$table][$uid]['version']['stageId'] = -1;
+                                                       break;
+                                               case 'stage_0':
+                                                       $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+                                                       $cmdArray[$table][$uid]['version']['stageId'] = 0;
+                                                       break;
+                                               case 'stage_1':
+                                                       $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+                                                       $cmdArray[$table][$uid]['version']['stageId'] = 1;
+                                                       break;
+                                               case 'stage_10':
+                                                       $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+                                                       $cmdArray[$table][$uid]['version']['stageId'] = 10;
+                                                       break;
+                                               case 'publish':
+                                                       if ($onlineRec = t3lib_BEfunc::getLiveVersionOfRecord($table, $uid, 'uid')) {
+                                                               $cmdArray[$table][$onlineRec['uid']]['version']['action'] = 'swap';
+                                                               $cmdArray[$table][$onlineRec['uid']]['version']['swapWith'] = $uid;
+                                                       }
+                                                       break;
+                                               case 'swap':
+                                                       if ($onlineRec = t3lib_BEfunc::getLiveVersionOfRecord($table, $uid, 'uid')) {
+                                                               $cmdArray[$table][$onlineRec['uid']]['version']['action'] = 'swap';
+                                                               $cmdArray[$table][$onlineRec['uid']]['version']['swapWith'] = $uid;
+                                                               $cmdArray[$table][$onlineRec['uid']]['version']['swapIntoWS'] = 1;
+                                                       }
+                                                       break;
+                                               case 'release':
+                                                       $cmdArray[$table][$uid]['version']['action'] = 'clearWSID';
+                                                       break;
+                                               case 'flush':
+                                                       $cmdArray[$table][$uid]['version']['action'] = 'flush';
+                                                       break;
+                                               }
+                                       }
+                               }
+                               /** @var $tce t3lib_TCEmain */
+                               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+                               $tce->stripslashes_values = 0;
+                               $tce->start(array(), $cmdArray);
+                               $tce->process_cmdmap();
+                               $tce->printLogErrorMessages('');
+                       }
+               }
+       }
+
+       /**
+        * Standard init function of a module.
+        *
+        * @return      void
+        * @todo Define visibility
+        */
+       public function init() {
+               // Setting module configuration:
+               $this->MCONF = $GLOBALS['MCONF'];
+               // Initialize Document Template object:
+               $this->doc = t3lib_div::makeInstance('template');
+               $this->doc->backPath = $GLOBALS['BACK_PATH'];
+               $this->doc->setModuleTemplate('templates/ws.html');
+               // JavaScript
+               $this->doc->JScode = $this->doc->wrapScriptTags('
+                       script_ended = 0;
+                       function jumpToUrl(URL) {       //
+                               window.location.href = URL;
+                       }
+
+                       function expandCollapse(rowNumber) {
+                               elementId = "wl_" + rowNumber;
+                               element = document.getElementById(elementId);
+                               image = document.getElementById("spanw1_" + rowNumber);
+                               if (element.style) {
+                                       if (element.style.display == "none") {
+                                               element.style.display = "table-row";
+                                               image.className = "t3-icon t3-icon-actions t3-icon-actions-view t3-icon-view-table-collapse";
+                                       } else {
+                                               element.style.display = "none";
+                                               image.className = "t3-icon t3-icon-actions t3-icon-actions-view t3-icon-view-table-expand";
+                                       }
+                               }
+                       }
+               ');
+               $this->doc->form = '<form action="index.php" method="post" name="pageform">';
+               // Setting up the context sensitive menu:
+               $this->doc->getContextMenuCode();
+               // Setting publish access permission for workspace:
+               $this->publishAccess = $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace);
+               // Parent initialization:
+               parent::init();
+       }
+
+       /**
+        * Main function for Workspace Manager module.
+        *
+        * @return      void
+        * @todo Define visibility
+        */
+       public function main() {
+               // See if we need to switch workspace
+               $changeWorkspace = t3lib_div::_GET('changeWorkspace');
+               if ($changeWorkspace != '') {
+                       $GLOBALS['BE_USER']->setWorkspace($changeWorkspace);
+                       $this->content .= $this->doc->wrapScriptTags((('top.location.href="' . $GLOBALS['BACK_PATH']) . t3lib_BEfunc::getBackendScript()) . '";');
+               } else {
+                       // Starting page:
+                       $this->content .= $this->doc->header($GLOBALS['LANG']->getLL('title'));
+                       $this->content .= $this->doc->spacer(5);
+                       // Get usernames and groupnames
+                       $be_group_Array = t3lib_BEfunc::getListGroupNames('title,uid');
+                       $groupArray = array_keys($be_group_Array);
+                       // Need 'admin' field for t3lib_iconWorks::getIconImage()
+                       $this->be_user_Array_full = ($this->be_user_Array = t3lib_BEfunc::getUserNames('username,usergroup,usergroup_cached_list,uid,admin,workspace_perms'));
+                       if (!$GLOBALS['BE_USER']->isAdmin()) {
+                               $this->be_user_Array = t3lib_BEfunc::blindUserNames($this->be_user_Array, $groupArray, 1);
+                       }
+                       // Build top menu:
+                       $menuItems = array();
+                       $menuItems[] = array(
+                               'label' => $GLOBALS['LANG']->getLL('menuitem_review'),
+                               'content' => $this->moduleContent_publish()
+                       );
+                       $menuItems[] = array(
+                               'label' => $GLOBALS['LANG']->getLL('menuitem_workspaces'),
+                               'content' => $this->moduleContent_workspaceList()
+                       );
+                       // Add hidden fields and create tabs:
+                       $content = $this->doc->getDynTabMenu($menuItems, 'user_ws');
+                       $this->content .= $this->doc->section('', $content, 0, 1);
+                       // Setting up the buttons and markers for docheader
+                       $docHeaderButtons = $this->getButtons();
+               }
+               $markers['CONTENT'] = $this->content;
+               // Build the <body> for the module
+               $this->content = $this->doc->startPage($GLOBALS['LANG']->getLL('title'));
+               $this->content .= $this->doc->moduleBody($this->pageinfo, $docHeaderButtons, $markers);
+               $this->content .= $this->doc->endPage();
+               $this->content = $this->doc->insertStylesAndJS($this->content);
+       }
+
+       /**
+        * Print module content. Called as last thing in the global scope.
+        *
+        * @return      void
+        * @todo Define visibility
+        */
+       public function printContent() {
+               echo $this->content;
+       }
+
+       /**
+        * Create the panel of buttons for submitting the form or otherwise perform operations.
+        *
+        * @return      array   all available buttons as an assoc. array
+        */
+       protected function getButtons() {
+               $buttons = array(
+                       'new_record' => ''
+               );
+               $newWkspUrl = 'workspaceforms.php?action=new';
+               // workspace creation link
+               if ($GLOBALS['BE_USER']->isAdmin() || 0 != ($GLOBALS['BE_USER']->groupData['workspace_perms'] & 4)) {
+                       $buttons['new_record'] = ((((((('<a href="' . $newWkspUrl) . '">') . '<img ') . t3lib_iconWorks::skinImg($GLOBALS['BACK_PATH'], 'gfx/add_workspaces.gif')) . ' alt="') . $GLOBALS['LANG']->getLL('img_title_create_new_workspace')) . '" id="ver-wl-new-workspace-icon" />') . '</a>';
+               }
+               return $buttons;
+       }
+
+       /*********************************
+        *
+        * Module content: Publish
+        *
+        *********************************/
+       /**
+        * Rendering the content for the publish and review panel in the workspace manager
+        *
+        * @return      string          HTML content
+        * @todo Define visibility
+        */
+       public function moduleContent_publish() {
+               // Initialize:
+               $content = '';
+               $details = t3lib_div::_GP('details');
+               // Create additional menus:
+               $menu = '';
+               if ($GLOBALS['BE_USER']->workspace === 0) {
+                       $menu .= t3lib_BEfunc::getFuncMenu(0, 'SET[filter]', $this->MOD_SETTINGS['filter'], $this->MOD_MENU['filter']);
+                       $menu .= t3lib_BEfunc::getFuncMenu(0, 'SET[display]', $this->MOD_SETTINGS['display'], $this->MOD_MENU['display']);
+               }
+               $menu .= t3lib_BEfunc::getFuncMenu(0, 'SET[diff]', $this->MOD_SETTINGS['diff'], $this->MOD_MENU['diff']);
+               if ($GLOBALS['BE_USER']->workspace !== 0) {
+                       $menu .= ((t3lib_BEfunc::getFuncCheck(0, 'SET[expandSubElements]', $this->MOD_SETTINGS['expandSubElements'], '', '', 'id="checkExpandSubElements"') . ' <label for="checkExpandSubElements">') . $GLOBALS['LANG']->getLL('label_showsubelements')) . '</label> ';
+               }
+               // Create header:
+               $title = '';
+               $description = '';
+               switch ($GLOBALS['BE_USER']->workspace) {
+               case 0:
+                       $title = ((t3lib_iconWorks::getIconImage('sys_workspace', array(), $this->doc->backPath, ' align="top"') . '[') . $GLOBALS['LANG']->getLL('shortcut_onlineWS')) . ']';
+                       $description = $GLOBALS['LANG']->getLL('workspace_description_live');
+                       break;
+               case -1:
+                       $title = ((t3lib_iconWorks::getIconImage('sys_workspace', array(), $this->doc->backPath, ' align="top"') . '[') . $GLOBALS['LANG']->getLL('shortcut_offlineWS')) . ']';
+                       $description = $GLOBALS['LANG']->getLL('workspace_description_draft');
+                       break;
+               case -99:
+                       $title = (($this->doc->icons(3) . '[') . $GLOBALS['LANG']->getLL('shortcut_noWSfound')) . ']';
+                       $description = $GLOBALS['LANG']->getLL('workspace_description_no_access');
+                       break;
+               default:
+                       $title = (((t3lib_iconWorks::getIconImage('sys_workspace', $GLOBALS['BE_USER']->workspaceRec, $this->doc->backPath, ' align="top"') . '[') . $GLOBALS['BE_USER']->workspace) . '] ') . t3lib_BEfunc::getRecordTitle('sys_workspace', $GLOBALS['BE_USER']->workspaceRec, TRUE);
+                       $description = $GLOBALS['BE_USER']->workspaceRec['description'];
+                       break;
+               }
+               // Buttons for publish / swap:
+               $actionLinks = '';
+               if ($GLOBALS['BE_USER']->workspace !== 0) {
+                       if ($this->publishAccess) {
+                               $confirmation = $GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL($GLOBALS['BE_USER']->workspaceRec['publish_access'] & 1 ? 'submit_publish_workspace_confirmation_1' : 'submit_publish_workspace_confirmation_2'));
+                               $actionLinks .= ((('<input type="submit" name="_publish" value="' . $GLOBALS['LANG']->getLL('submit_publish_workspace')) . '" onclick="if (confirm(') . $confirmation) . ')) window.location.href=\'publish.php?swap=0\';return false"/>';
+                               if ($GLOBALS['BE_USER']->workspaceSwapAccess()) {
+                                       $confirmation = $GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL($GLOBALS['BE_USER']->workspaceRec['publish_access'] & 1 ? 'submit_swap_workspace_confirmation_1' : 'submit_swap_workspace_confirmation_2'));
+                                       $actionLinks .= ((('<input type="submit" name="_swap" value="' . $GLOBALS['LANG']->getLL('submit_swap_workspace')) . '" onclick="if (confirm(') . $confirmation) . ')) window.location.href=\'publish.php?swap=1\';return false ;" />';
+                               }
+                       } else {
+                               $actionLinks .= $this->doc->icons(1) . $GLOBALS['LANG']->getLL('no_publish_permission');
+                       }
+                       // Preview of workspace link
+                       if (t3lib_div::_POST('_previewLink')) {
+                               $ttlHours = intval($GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours'));
+                               $ttlHours = $ttlHours ? $ttlHours : 24 * 2;
+                               $previewUrl = (((t3lib_BEfunc::getViewDomain($this->id) . '/index.php?ADMCMD_prev=') . t3lib_BEfunc::compilePreviewKeyword('', $GLOBALS['BE_USER']->user['uid'], (60 * 60) * $ttlHours, $GLOBALS['BE_USER']->workspace)) . '&id=') . intval($GLOBALS['BE_USER']->workspaceRec['db_mountpoints']);
+                               $actionLinks .= ((((('<br />Any user can browse the workspace frontend using this link for the next ' . $ttlHours) . ' hours (does not require backend login):<br /><br /><a target="_blank" href="') . htmlspecialchars($previewUrl)) . '">') . $previewUrl) . '</a>';
+                       } else {
+                               $actionLinks .= '<input type="submit" name="_previewLink" value="Generate Workspace Preview Link" />';
+                       }
+               }
+               $wsAccess = $GLOBALS['BE_USER']->checkWorkspace($GLOBALS['BE_USER']->workspaceRec);
+               // Add header to content variable:
+               $content = (((((('
+               <table border="0" cellpadding="0" cellspacing="0" id="t3-user-ws-wsinfotable" class="t3-table t3-table-info">
+                       <tr>
+                               <td class="t3-col-header" nowrap="nowrap">' . $GLOBALS['LANG']->getLL('label_workspace')) . '&nbsp;</th>
+                               <td nowrap="nowrap">') . $title) . '</td>
+                       </tr>
+                       <tr>') . ($description ? ((('
+                               <td class="t3-col-header" nowrap="nowrap">' . $GLOBALS['LANG']->getLL('label_description')) . '&nbsp;</td>
+                               <td>') . $description) . '</td>
+                       </tr>' : '')) . ($GLOBALS['BE_USER']->workspace != -99 && !$details ? (((((((((('
+                       <tr>
+                               <td class="t3-col-header" nowrap="nowrap">' . $GLOBALS['LANG']->getLL('label_options')) . '&nbsp;</td>
+                               <td>') . $menu) . $actionLinks) . '</td>
+                       </tr>
+                       <tr>
+                               <td class="t3-col-header" nowrap="nowrap">') . $GLOBALS['LANG']->getLL('label_status')) . '&nbsp;</td>
+                               <td>') . $GLOBALS['LANG']->getLL('label_access_level')) . ' ') . $GLOBALS['LANG']->getLL(('workspace_list_access_' . $wsAccess['_ACCESS']))) . '</td>
+                       </tr>' : '')) . '
+               </table>
+               <br />
+               ';
+               // Add publishing and review overview:
+               if ($GLOBALS['BE_USER']->workspace != -99) {
+                       if ($details) {
+                               $content .= $this->displayVersionDetails($details);
+                       } else {
+                               $content .= $this->displayWorkspaceOverview();
+                       }
+                       $content .= '<br />';
+               }
+               // Return content:
+               return $content;
+       }
+
+       /**
+        * Display details for a single version from workspace
+        *
+        * @param       string          Version identification, made of table and uid
+        * @return      string          HTML string
+        * @todo Define visibility
+        */
+       public function displayVersionDetails($details) {
+               return ('TODO: Show details for version "' . $details) . '"<hr/><a href="index.php">BACK</a>';
+       }
+
+       /**
+        * Rendering the overview of versions in the current workspace
+        *
+        * @return      string          HTML (table)
+        * @todo Define visibility
+        */
+       public function displayWorkspaceOverview() {
+               // Initialize Workspace ID and filter-value:
+               if ($GLOBALS['BE_USER']->workspace === 0) {
+                       $wsid = $this->MOD_SETTINGS['display'];
+                       // Set wsid to the value from the menu (displaying content of other workspaces)
+                       $filter = $this->MOD_SETTINGS['filter'];
+               } else {
+                       $wsid = $GLOBALS['BE_USER']->workspace;
+                       $filter = 0;
+               }
+               // Instantiate workspace GUI library and generate workspace overview
+               $wslibGuiObj = t3lib_div::makeInstance('wslib_gui');
+               $wslibGuiObj->diff = $this->MOD_SETTINGS['diff'];
+               $wslibGuiObj->expandSubElements = $this->MOD_SETTINGS['expandSubElements'];
+               $wslibGuiObj->alwaysDisplayHeader = TRUE;
+               return $wslibGuiObj->getWorkspaceOverview($this->doc, $wsid, $filter);
+       }
+
+       /********************************
+        *
+        * Module content: Workspace list
+        *
+        ********************************/
+       /**
+        * Rendering of the workspace list
+        *
+        * @return      string          HTML
+        * @todo Define visibility
+        */
+       public function moduleContent_workspaceList() {
+               // Original Kasper's TODO: Workspace listing
+               //
+               //      - LISTING: Shows list of available workspaces for user. Used can see title, description, publication time, freeze-state, db-mount, member users/groups etc. Current workspace is indicated.
+               //      - SWITCHING: Switching between available workspaces is done by a button shown for each in the list
+               //      - ADMIN: Administrator of a workspace can click an edit-button linking to a form where he can edit the workspace. Users and groups should be selected based on some filtering so he cannot select groups he is not a member off himself (or some other rule... like for permission display with blinded users/groups)
+               //      - CREATE: If allowed, the user can create a new workspace which brings up a form where he can enter basic data. This is saved by a local instance of tcemain with forced admin-rights (creation in pid=0!).
+               return $this->workspaceList_displayUserWorkspaceList();
+       }
+
+       /**
+        * Generates HTML to display a list of workspaces.
+        *
+        * @return      string          Generated HTML code
+        * @todo Define visibility
+        */
+       public function workspaceList_displayUserWorkspaceList() {
+               // table header
+               $content = $this->workspaceList_displayUserWorkspaceListHeader();
+               // get & walk workspace list generating content
+               $wkspList = $this->workspaceList_getUserWorkspaceList();
+               $rowNum = 1;
+               foreach ($wkspList as $wksp) {
+                       $currentWksp = $GLOBALS['BE_USER']->workspace == $wksp['uid'];
+                       // Each workspace data occupies two rows:
+                       // (1) Folding + Icons + Title + Description
+                       // (2) Information about workspace (initially hidden)
+                       $cssClass = $currentWksp ? 't3-row t3-row-active bgColor3' : 't3-row bgColor4';
+                       // Start first row
+                       $content .= ('<tr class="' . $cssClass) . '">';
+                       // row #1, column #1: expand icon
+                       $content .= (((('<td>' . '<a href="javascript:expandCollapse(') . $rowNum) . ')">') . t3lib_iconWorks::getSpriteIcon('actions-view-table-expand', array(
+                               'title' => $GLOBALS['LANG']->getLL('img_title_show_more'),
+                               'id' => ('spanw1_' . $rowNum)
+                       ))) . '</a></td>';
+                       // row #1, column #2: icon panel
+                       $content .= '<td nowrap="nowrap">';
+                       // Mozilla Firefox will attempt wrap due to `width="1"` on topmost column
+                       $content .= $this->workspaceList_displayIcons($currentWksp, $wksp);
+                       $content .= '</td>';
+                       // row #1, column #3: current workspace indicator
+                       $content .= '<td nowrap="nowrap" style="text-align: center">';
+                       // Mozilla Firefox will attempt wrap due to `width="1"` on topmost column
+                       $content .= !$currentWksp ? '&nbsp;' : ((((('<img ' . t3lib_iconWorks::skinImg($GLOBALS['BACK_PATH'], 'gfx/icon_ok.gif', 'width="18" height="16"')) . ' id="wl_') . $rowNum) . 'i" border="0" hspace="1" alt="') . $GLOBALS['LANG']->getLL('img_title_current_workspace')) . '" />';
+                       $content .= '</td>';
+                       // row #1, column #4 and 5: title and description
+                       $content .= (((('<td nowrap="nowrap">' . htmlspecialchars($wksp['title'])) . '</td>') . '<td>') . nl2br(htmlspecialchars($wksp['description']))) . '</td>';
+                       $content .= '</tr>';
+                       // row #2, column #1 and #2
+                       $content .= ('<tr id="wl_' . $rowNum) . '" class="bgColor" style="display: none">';
+                       $content .= '<td colspan="2" style="border-right: none;">&nbsp;</td>';
+                       // row #2, column #3, #4 and #4
+                       $content .= ('<td colspan="3" style="border-left: none;">' . $this->workspaceList_formatWorkspaceData($wksp)) . '</td>';
+                       $content .= '</tr>';
+                       $rowNum++;
+               }
+               $content .= '</table>';
+               return $content;
+       }
+
+       /**
+        * Retrieves a list of workspaces where user has access.
+        *
+        * @return      array           A list of workspaces available to the current BE user
+        * @todo Define visibility
+        */
+       public function workspaceList_getUserWorkspaceList() {
+               // Get list of all workspaces. Note: system workspaces will be always displayed before custom ones!
+               $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_workspace', 'pid=0' . t3lib_BEfunc::deleteClause('sys_workspace'), '', 'title');
+               $availableWorkspaces = array();
+               // Live
+               $wksp = $this->workspaceList_createFakeWorkspaceRecord(0);
+               $wksp = $GLOBALS['BE_USER']->checkWorkspace($wksp);
+               if (FALSE !== $wksp) {
+                       $availableWorkspaces[] = $wksp;
+               }
+               // Draft
+               $wksp = $this->workspaceList_createFakeWorkspaceRecord(-1);
+               $wksp = $GLOBALS['BE_USER']->checkWorkspace($wksp);
+               if (FALSE !== $wksp) {
+                       $availableWorkspaces[] = $wksp;
+               }
+               // Custom
+               foreach ($workspaces as $rec) {
+                       // see if user can access this workspace in any way
+                       if (FALSE !== ($result = $GLOBALS['BE_USER']->checkWorkspace($rec))) {
+                               $availableWorkspaces[] = $result;
+                       }
+               }
+               return $availableWorkspaces;
+       }
+
+       /**
+        * Create inner information panel for workspace list. This panel is
+        * initially hidden and becomes visible when user click on the expand
+        * icon on the very left of workspace list against the workspace he
+        * wants to explore.
+        *
+        * @param       array           Workspace information
+        * @return      string          Formatted workspace information
+        * @todo Define visibility
+        */
+       public function workspaceList_formatWorkspaceData(&$wksp) {
+               $content = ((((((((((('<table cellspacing="0" cellpadding="0" width="100%" class="ver-wl-details-table">' . '<tr><td class="ver-wl-details-label"><strong>') . $GLOBALS['LANG']->getLL('workspace_list_label_file_mountpoints')) . '</strong></td>') . '<td class="ver-wl-details">') . $this->workspaceList_getFileMountPoints($wksp)) . '</td></tr>') . '<tr><td class="ver-wl-details-label"><strong>') . $GLOBALS['LANG']->getLL('workspace_list_label_db_mountpoints')) . '</strong></td>') . '<td class="ver-wl-details">') . $this->workspaceList_getWebMountPoints($wksp)) . '</td></tr>';
+               if ($wksp['uid'] > 0) {
+                       // Displaying information below makes sence only for custom workspaces
+                       $content .= (((((((((((((((((((((((((((('<tr><td class="ver-wl-details-label"><strong>' . $GLOBALS['LANG']->getLL('workspace_list_label_frozen')) . '</strong></td>') . '<td class="ver-wl-details">') . $GLOBALS['LANG']->getLL(($wksp['freeze'] ? 'workspace_list_label_frozen_yes' : 'workspace_list_label_frozen_no'))) . '</td></tr>') . '<tr><td class="ver-wl-details-label"><strong>') . $GLOBALS['LANG']->getLL('workspace_list_label_publish_date')) . '</strong></td>') . '<td class="ver-wl-details">') . ($wksp['publish_time'] == 0 ? '&nbsp;&ndash;' : t3lib_BEfunc::datetime($wksp['publish_time']))) . '</td></tr>') . '<tr><td class="ver-wl-details-label"><strong>') . $GLOBALS['LANG']->getLL('workspace_list_label_unpublish_date')) . '</strong></td>') . '<td class="ver-wl-details">') . ($wksp['unpublish_time'] == 0 ? '&nbsp;&ndash;' : t3lib_BEfunc::datetime($wksp['unpublish_time']))) . '</td></tr>') . '<tr><td class="ver-wl-details-label"><strong>') . $GLOBALS['LANG']->getLL('workspace_list_label_your_access')) . '</strong></td>') . '<td class="ver-wl-details">') . $GLOBALS['LANG']->getLL(('workspace_list_access_' . $wksp['_ACCESS']))) . '</td></tr>') . '<tr><td class="ver-wl-details-label"><strong>') . $GLOBALS['LANG']->getLL('workspace_list_label_workspace_users')) . '</strong></td>') . '<td class="ver-wl-details">') . $this->workspaceList_getUserList($wksp)) . '</td></tr>';
+               } elseif ($GLOBALS['BE_USER']->isAdmin()) {
+                       // show users for draft/live workspace only to admin users
+                       $content .= (((('<tr><td class="ver-wl-details-label"><strong>' . $GLOBALS['LANG']->getLL('workspace_list_label_workspace_users')) . '</strong></td>') . '<td class="ver-wl-details">') . $this->workspaceList_getUserList($wksp)) . '</td></tr>';
+               }
+               $content .= '</table>';
+               return $content;
+       }
+
+       /**
+        * Retrieves and formats database mount points lists.
+        *
+        * @param       array           &$wksp  Workspace record
+        * @return      string          Generated HTML
+        * @todo Define visibility
+        */
+       public function workspaceList_getWebMountPoints(&$wksp) {
+               if ($wksp['uid'] == -1) {
+                       // draft workspace
+                       return $GLOBALS['LANG']->getLL('workspace_list_db_mount_point_draft');
+               } elseif ($wksp['uid'] == 0) {
+                       // live workspace
+                       return $GLOBALS['LANG']->getLL('workspace_list_db_mount_point_live');
+               }
+               // -- here only if obtaining mount points for custom workspaces
+               // We need to fetch user's mount point list (including MPS mounted from groups).
+               // This list must not be affects by current user's workspace. It means we cannot use
+               // $GLOBALS['BE_USER']->isInWebMount() to check mount points.
+               $mountpointList = $GLOBALS['BE_USER']->groupData['webmounts'];
+               // If there are DB mountpoints in the workspace record,
+               // then only show the ones that are allowed there (and that are in the users' webmounts)
+               if (trim($wksp['db_mountpoints'])) {
+                       $userMountpoints = explode(',', $mountpointList);
+                       // now filter the users' to only keep the mountpoints
+                       // that are also in the workspaces' db_mountpoints
+                       $workspaceMountpoints = explode(',', $wksp['db_mountpoints']);
+                       $filteredMountpoints = array_intersect($userMountpoints, $workspaceMountpoints);
+                       $mountpointList = implode(',', $filteredMountpoints);
+               }
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'pages', ('deleted = 0 AND uid IN (' . $GLOBALS['TYPO3_DB']->cleanIntList($mountpointList)) . ')', '', 'title');
+               $content = array();
+               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                       // will show UID on hover. Just convinient to user.
+                       $content[] = ((((t3lib_iconWorks::getSpriteIconForRecord('pages', $row) . '<span title="UID: ') . $row['uid']) . '">') . $row['title']) . '</span>';
+               }
+               if (count($content)) {
+                       return implode('<br />', $content);
+               } else {
+                       // no mount points
+                       return $GLOBALS['LANG']->getLL('workspace_list_db_mount_point_custom');
+               }
+       }
+
+       /**
+        * Retrieves and formats file mount points lists.
+        *
+        * @param       array           &$wksp  Workspace record
+        * @return      string          Generated HTML
+        * @todo Define visibility
+        */
+       public function workspaceList_getFileMountPoints(&$wksp) {
+               if ($wksp['uid'] == -1) {
+                       // draft workspace - none!
+                       return $GLOBALS['LANG']->getLL('workspace_list_file_mount_point_draft');
+               } elseif ($wksp['uid'] == 0) {
+                       // live workspace
+                       return $GLOBALS['LANG']->getLL('workspace_list_file_mount_point_live');
+               }
+               // -- here only if displaying information for custom workspace
+               // We need to fetch user's mount point list (including MPS mounted from groups).
+               // This list must not be affects by current user's workspace. It means we cannot use
+               // $GLOBALS['BE_USER']->isInWebMount() to check mount points.
+               $mountpointList = implode(',', $GLOBALS['BE_USER']->groupData['filemounts']);
+               // If there are file mountpoints in the workspace record,
+               // then only show the ones that are allowed there (and that are in the users' file mounts)
+               if (trim($wksp['file_mountpoints'])) {
+                       $userMountpoints = explode(',', $mountpointList);
+                       // now filter the users' to only keep the mountpoints
+                       // that are also in the workspaces' file_mountpoints
+                       $workspaceMountpoints = explode(',', $wksp['file_mountpoints']);
+                       $filteredMountpoints = array_intersect($userMountpoints, $workspaceMountpoints);
+                       $mountpointList = implode(',', $filteredMountpoints);
+               }
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'sys_filemounts', ('deleted = 0 AND hidden=0 AND uid IN (' . $GLOBALS['TYPO3_DB']->cleanIntList($mountpointList)) . ')', '', 'title');
+               $content = array();
+               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                       // will show UID on hover. Just convinient to user.
+                       $content[] = ((((t3lib_iconWorks::getSpriteIconForRecord('sys_filemounts', $row) . '<span title="UID: ') . $row['uid']) . '">') . $row['title']) . '</span>';
+               }
+               if (count($content)) {
+                       return implode('<br />', $content);
+               } else {
+                       // no mount points
+                       return $GLOBALS['LANG']->getLL('workspace_list_file_mount_point_custom');
+               }
+       }
+
+       /**
+        * Creates a header for the workspace list table. This function only makes
+        * <code>workspaceList_displayUserWorkspaceList()</code> smaller.
+        *
+        * @return      string          Generated content
+        * @todo Define visibility
+        */
+       public function workspaceList_displayUserWorkspaceListHeader() {
+               // TODO CSH lables?
+               return ((((('<table border="0" cellpadding="0" cellspacing="0" class="workspace-overview">
+                       <tr class="t3-row-header">
+                               <td width="1">&nbsp;</td>
+                               <td width="1">&nbsp;</td>
+                               <td nowrap="nowrap">' . $GLOBALS['LANG']->getLL('workspace_list_label_current_workspace')) . '</td>
+                               <td nowrap="nowrap">') . $GLOBALS['LANG']->getLL('workspace_list_label_workspace_title')) . '</td>
+                               <td nowrap="nowrap">') . $GLOBALS['LANG']->getLL('workspace_list_label_workspace_description')) . '</td>
+                       </tr>';
+       }
+
+       /**
+        * Generates a list of <code>&lt;option&gt;</code> tags with user names.
+        *
+        * @param       array           Workspace record
+        * @return      string          Generated content
+        * @todo Define visibility
+        */
+       public function workspaceList_getUserList(&$wksp) {
+               if ($wksp['uid'] > 0) {
+                       // custom workspaces
+                       $content = $this->workspaceList_getUserListWithAccess($wksp['adminusers'], $GLOBALS['LANG']->getLL('workspace_list_label_owners'));
+                       // owners
+                       $content .= $this->workspaceList_getUserListWithAccess($wksp['members'], $GLOBALS['LANG']->getLL('workspace_list_label_members'));
+                       // members
+                       $content .= $this->workspaceList_getUserListWithAccess($wksp['reviewers'], $GLOBALS['LANG']->getLL('workspace_list_label_reviewers'));
+                       // reviewers
+                       if ($content != '') {
+                               $content = ('<table cellpadding="0" cellspacing="1" width="100%" class="lrPadding workspace-overview">' . $content) . '</table>';
+                       } else {
+                               $content = $GLOBALS['LANG']->getLL($wksp['uid'] > 0 ? 'workspace_list_access_admins_only' : 'workspace_list_access_anyone');
+                       }
+               } else {
+                       // live and draft workspace
+                       $content = $this->workspaceList_getUserListForSysWorkspace($wksp);
+               }
+               return $content;
+       }
+
+       /**
+        * Generates a list of user names that has access to the system workspace.
+        *
+        * @param       array           &$wksp  Workspace record
+        * @return      string          Generated content
+        * @todo Define visibility
+        */
+       public function workspaceList_getUserListForSysWorkspace(&$wksp) {
+               $option = $wksp['uid'] == 0 ? 1 : 2;
+               $content_array = array();
+               foreach ($this->be_user_Array_full as $uid => $user) {
+                       if ($user['admin'] != 0 || 0 != ($user['workspace_perms'] & $option)) {
+                               if ($uid == $GLOBALS['BE_USER']->user['uid']) {
+                                       // highlight current user
+                                       $tag0 = '<span class="ver-wl-current-user">';
+                                       $tag1 = '</span>';
+                               } else {
+                                       $tag0 = ($tag1 = '');
+                               }
+                               $content_array[] = (($this->doc->wrapClickMenuOnIcon(t3lib_iconWorks::getIconImage('be_users', $uid, $GLOBALS['BACK_PATH'], ((' align="middle" alt="UID: ' . $uid) . '"')), 'be_users', $uid, 2) . $tag0) . htmlspecialchars($user['username'])) . $tag1;
+                       }
+               }
+               return implode('<br />', $content_array);
+       }
+
+       /**
+        * Generates a list of user names that has access to the workspace.
+        *
+        * @param       array           A list of user IDs separated by comma
+        * @param       string          Access string
+        * @return      string          Generated content
+        * @todo Define visibility
+        */
+       public function workspaceList_getUserListWithAccess(&$list, $access) {
+               $content_array = array();
+               if ($list != '') {
+                       $userIDs = explode(',', $list);
+                       // get user names and sort
+                       $regExp = '/^(be_[^_]+)_(\\d+)$/';
+                       $groups = FALSE;
+                       foreach ($userIDs as $userUID) {
+                               $id = $userUID;
+                               if (preg_match($regExp, $userUID)) {
+                                       $table = preg_replace($regExp, '\\1', $userUID);
+                                       $id = intval(preg_replace($regExp, '\\2', $userUID));
+                                       if ($table == 'be_users') {
+                                               // user
+                                               $icon = $GLOBALS['TCA']['be_users']['typeicons'][$this->be_user_Array[$id]['admin']];
+                                               if ($id == $GLOBALS['BE_USER']->user['uid']) {
+                                                       // highlight current user
+                                                       $tag0 = '<span class="ver-wl-current-user">';
+                                                       $tag1 = '</span>';
+                                               } else {
+                                                       $tag0 = ($tag1 = '');
+                                               }
+                                               $content_array[] = (($this->doc->wrapClickMenuOnIcon(t3lib_iconWorks::getIconImage($table, $this->be_user_Array[$id], $GLOBALS['BACK_PATH'], ((' align="middle" alt="UID: ' . $id) . '"')), $table, $id, 2) . $tag0) . htmlspecialchars($this->be_user_Array_full[$id]['username'])) . $tag1;
+                                       } else {
+                                               // group
+                                               if (FALSE === $groups) {
+                                                       $groups = t3lib_BEfunc::getGroupNames();
+                                               }
+                                               $content_array[] = $this->doc->wrapClickMenuOnIcon(t3lib_iconWorks::getIconImage($table, $groups[$id], $GLOBALS['BACK_PATH'], ((' align="middle" alt="UID: ' . $id) . '"')), $table, $id, 2) . $groups[$id]['title'];
+                                       }
+                               } else {
+                                       // user id
+                                       if ($userUID == $GLOBALS['BE_USER']->user['uid']) {
+                                               // highlight current user
+                                               $tag0 = '<span class="ver-wl-current-user">';
+                                               $tag1 = '</span>';
+                                       } else {
+                                               $tag0 = ($tag1 = '');
+                                       }
+                                       $content_array[] = ((t3lib_iconWorks::getIconImage('be_users', $this->be_user_Array[$id], $GLOBALS['BACK_PATH'], ((' align="middle" alt="UID: ' . $id) . '"')) . $tag0) . htmlspecialchars($this->be_user_Array_full[$userUID]['username'])) . $tag1;
+                               }
+                       }
+                       sort($content_array);
+               } else {
+                       $content_array[] = '&nbsp;&ndash;';
+               }
+               $content = '<tr><td class="ver-wl-details-label ver-wl-details-user-list-label">';
+               // TODO CSH lable explaining access here?
+               $content .= ('<strong>' . $access) . '</strong></td>';
+               $content .= ('<td class="ver-wl-details">' . implode('<br />', $content_array)) . '</td></tr>';
+               return $content;
+       }
+
+       /**
+        * Creates a list of icons for workspace.
+        *
+        * @param       boolean         <code>TRUE</code> if current workspace
+        * @param       array           Workspace record
+        * @return      string          Generated content
+        * @todo Define visibility
+        */
+       public function workspaceList_displayIcons($currentWorkspace, &$wksp) {
+               $content = '';
+               // `edit workspace` button
+               if ($this->workspaceList_hasEditAccess($wksp)) {
+                       // User can modify workspace parameters, display corresponding link and icon
+                       $editUrl = 'workspaceforms.php?action=edit&amp;wkspId=' . $wksp['uid'];
+                       $content .= ((((('<a href="' . $editUrl) . '" title="') . $GLOBALS['LANG']->getLL('workspace_list_icon_title_edit_workspace')) . '"/>') . t3lib_iconWorks::getSpriteIcon('actions-document-open')) . '</a>';
+               } else {
+                       // User can NOT modify workspace parameters, display space
+                       // Get only withdth and height from skinning API
+                       $content .= ('<img src="clear.gif" ' . t3lib_iconWorks::skinImg($GLOBALS['BACK_PATH'], 'gfx/edit2.gif', 'width="11" height="12"', 2)) . ' border="0" alt="" hspace="1" align="middle" />';
+               }
+               // `switch workspace` button
+               if (!$currentWorkspace) {
+                       // Workspace switching button
+                       $content .= ((((((('<a href="' . t3lib_div::getIndpEnv('SCRIPT_NAME')) . '?changeWorkspace=') . $wksp['uid']) . '" title="') . $GLOBALS['LANG']->getLL('workspace_list_icon_title_switch_workspace')) . '"/>') . t3lib_iconWorks::getSpriteIcon('actions-version-swap-workspace')) . '</a>';
+               } else {
+                       // Current workspace: empty space instead of workspace switching button
+                       //
+                       // Here get only width and height from skinning API
+                       $content .= ('<img src="clear.gif" ' . t3lib_iconWorks::skinImg($GLOBALS['BACK_PATH'], 'gfx/switch.png', 'width="18" height="16"', 2)) . ' border="0" alt="" hspace="1" align="middle" alt="" />';
+               }
+               return $content;
+       }
+
+       /**
+        * Checks if user has edit access to workspace. Access is granted if
+        * workspace is custom and user is admin or the the owner of the workspace.
+        * This function assumes that <code>$wksp</code> were passed through
+        * <code>$GLOBALS['BE_USER']->checkWorkspace()</code> function to obtain
+        * <code>_ACCESS</code> attribute of the workspace.
+        *
+        * @param       array           Workspace record
+        * @return      boolean         <code>TRUE</code> if user can modify workspace parameters
+        * @todo Define visibility
+        */
+       public function workspaceList_hasEditAccess(&$wksp) {
+               $access =& $wksp['_ACCESS'];
+               return $wksp['uid'] > 0 && ($access == 'admin' || $access == 'owner');
+       }
+
+       /**
+        * Creates a fake workspace record for system workspaces. Record contains
+        * all fields found in <code>sys_workspaces</code>.
+        *
+        * @param       integer         System workspace ID. Currently <code>0</code> and <code>-1</code> are accepted.
+        * @return      array           Generated record (see <code>sys_workspaces</code> for structure)
+        * @todo Define visibility
+        */
+       public function workspaceList_createFakeWorkspaceRecord($uid) {
+               $record = array(
+                       'uid' => $uid,
+                       'pid' => 0,
+                       // always 0!
+                       'tstamp' => 0,
+                       // does not really matter
+                       'deleted' => 0,
+                       'title' => $uid == 0 ? ('[' . $GLOBALS['LANG']->getLL('shortcut_onlineWS')) . ']' : ('[' . $GLOBALS['LANG']->getLL('shortcut_offlineWS')) . ']',
+                       'description' => $uid == 0 ? $GLOBALS['LANG']->getLL('shortcut_onlineWS') : $GLOBALS['LANG']->getLL('shortcut_offlineWS'),
+                       'adminusers' => '',
+                       'members' => '',
+                       'reviewers' => '',
+                       'db_mountpoints' => '',
+                       // TODO get mount points from user profile
+                       'file_mountpoints' => '',
+                       // TODO get mount points from user profile for live workspace only (uid == 0)
+                       'publish_time' => 0,
+                       'unpublish_time' => 0,
+                       'freeze' => 0,
+                       'live_edit' => $uid == 0,
+                       'vtypes' => 0,
+                       'disable_autocreate' => 0,
+                       'swap_modes' => 0,
+                       'publish_access' => 0,
+                       'stagechg_notification' => 0
+               );
+               return $record;
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/DataHandler/CommandMap.php b/typo3/sysext/version/Classes/DataHandler/CommandMap.php
new file mode 100644 (file)
index 0000000..c0c388a
--- /dev/null
@@ -0,0 +1,908 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010-2011 Oliver Hader <oliver@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script 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.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Handles the t3lib_TCEmain command map and is only used in combination with t3lib_TCEmain.
+ */
+class tx_version_tcemain_CommandMap {
+
+       const SCOPE_WorkspacesSwap = 'SCOPE_WorkspacesSwap';
+       const SCOPE_WorkspacesSetStage = 'SCOPE_WorkspacesSetStage';
+       const SCOPE_WorkspacesClear = 'SCOPE_WorkspacesClear';
+       const KEY_ScopeErrorMessage = 'KEY_ScopeErrorMessage';
+       const KEY_ScopeErrorCode = 'KEY_ScopeErrorCode';
+       const KEY_GetElementPropertiesCallback = 'KEY_GetElementPropertiesCallback';
+       const KEY_GetCommonPropertiesCallback = 'KEY_GetCommonPropertiesCallback';
+       const KEY_ElementConstructCallback = 'KEY_EventConstructCallback';
+       const KEY_ElementCreateChildReferenceCallback = 'KEY_ElementCreateChildReferenceCallback';
+       const KEY_ElementCreateParentReferenceCallback = 'KEY_ElementCreateParentReferenceCallback';
+       const KEY_PurgeWithErrorMessageGetIdCallback = 'KEY_PurgeWithErrorMessageGetIdCallback';
+       const KEY_UpdateGetIdCallback = 'KEY_UpdateGetIdCallback';
+       const KEY_TransformDependentElementsToUseLiveId = 'KEY_TransformDependentElementsToUseLiveId';
+       /**
+        * @var tx_version_tcemain
+        */
+       protected $parent;
+
+       /**
+        * @var t3lib_TCEmain
+        */
+       protected $tceMain;
+
+       /**
+        * @var array
+        */
+       protected $commandMap = array();
+
+       /**
+        * @var string
+        */
+       protected $workspacesSwapMode;
+
+       /**
+        * @var string
+        */
+       protected $workspacesChangeStageMode;
+
+       /**
+        * @var boolean
+        */
+       protected $workspacesConsiderReferences;
+
+       /**
+        * @var array
+        */
+       protected $scopes;
+
+       /**
+        * Creates this object.
+        *
+        * @param t3lib_TCEmain $parent
+        * @param array $commandMap
+        */
+       public function __construct(tx_version_tcemain $parent, t3lib_TCEmain $tceMain, array $commandMap) {
+               $this->setParent($parent);
+               $this->setTceMain($tceMain);
+               $this->set($commandMap);
+               $this->setWorkspacesSwapMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.swapMode'));
+               $this->setWorkspacesChangeStageMode($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.changeStageMode'));
+               $this->setWorkspacesConsiderReferences($this->getTceMain()->BE_USER->getTSConfigVal('options.workspaces.considerReferences'));
+               $this->constructScopes();
+       }
+
+       /**
+        * Gets the command map.
+        *
+        * @return array
+        */
+       public function get() {
+               return $this->commandMap;
+       }
+
+       /**
+        * Sets the command map.
+        *
+        * @param array $commandMap
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function set(array $commandMap) {
+               $this->commandMap = $commandMap;
+               return $this;
+       }
+
+       /**
+        * Gets the parent object.
+        *
+        * @return tx_version_tcemain
+        */
+       public function getParent() {
+               return $this->parent;
+       }
+
+       /**
+        * Sets the parent object.
+        *
+        * @param tx_version_tcemain $parent
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function setParent(tx_version_tcemain $parent) {
+               $this->parent = $parent;
+               return $this;
+       }
+
+       /**
+        * Gets the parent object.
+        *
+        * @return t3lib_TCEmain
+        */
+       public function getTceMain() {
+               return $this->tceMain;
+       }
+
+       /**
+        * Sets the parent object.
+        *
+        * @param t3lib_TCEmain $tceMain
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function setTceMain(t3lib_TCEmain $tceMain) {
+               $this->tceMain = $tceMain;
+               return $this;
+       }
+
+       /**
+        * Sets the workspaces swap mode
+        * (see options.workspaces.swapMode).
+        *
+        * @param string $workspacesSwapMode
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function setWorkspacesSwapMode($workspacesSwapMode) {
+               $this->workspacesSwapMode = (string) $workspacesSwapMode;
+               return $this;
+       }
+
+       /**
+        * Sets the workspaces change stage mode
+        * see options.workspaces.changeStageMode)
+        *
+        * @param string $workspacesChangeStageMode
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function setWorkspacesChangeStageMode($workspacesChangeStageMode) {
+               $this->workspacesChangeStageMode = (string) $workspacesChangeStageMode;
+               return $this;
+       }
+
+       /**
+        * Sets the workspace behaviour to automatically consider references
+        * (see options.workspaces.considerReferences)
+        *
+        * @param boolean $workspacesConsiderReferences
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function setWorkspacesConsiderReferences($workspacesConsiderReferences) {
+               $this->workspacesConsiderReferences = (bool) $workspacesConsiderReferences;
+               return $this;
+       }
+
+       /**
+        * Processes the command map.
+        *
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function process() {
+               $this->resolveWorkspacesSwapDependencies();
+               $this->resolveWorkspacesSetStageDependencies();
+               $this->resolveWorkspacesClearDependencies();
+               return $this;
+       }
+
+       /**
+        * Invokes all items for swapping/publishing with a callback method.
+        *
+        * @param string $callbackMethod
+        * @param array $arguments Optional leading arguments for the callback method
+        * @return void
+        */
+       protected function invokeWorkspacesSwapItems($callbackMethod, array $arguments = array()) {
+               // Traverses the cmd[] array and fetches the accordant actions:
+               foreach ($this->commandMap as $table => $liveIdCollection) {
+                       foreach ($liveIdCollection as $liveId => $commandCollection) {
+                               foreach ($commandCollection as $command => $properties) {
+                                       if (($command === 'version' && isset($properties['action'])) && $properties['action'] === 'swap') {
+                                               if (isset($properties['swapWith']) && t3lib_utility_Math::canBeInterpretedAsInteger($properties['swapWith'])) {
+                                                       call_user_func_array(array($this, $callbackMethod), array_merge($arguments, array($table, $liveId, $properties)));
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Resolves workspaces related dependencies for swapping/publishing of the command map.
+        * Workspaces records that have children or (relative) parents which are versionized
+        * but not published with this request, are removed from the command map. Otherwise
+        * this would produce hanging record sets and lost references.
+        *
+        * @return void
+        */
+       protected function resolveWorkspacesSwapDependencies() {
+               $scope = self::SCOPE_WorkspacesSwap;
+               $dependency = $this->getDependencyUtility($scope);
+               if (t3lib_div::inList('any,pages', $this->workspacesSwapMode)) {
+                       $this->invokeWorkspacesSwapItems('applyWorkspacesSwapBehaviour');
+               }
+               $this->invokeWorkspacesSwapItems('addWorkspacesSwapElements', array($dependency));
+               $this->applyWorkspacesDependencies($dependency, $scope);
+       }
+
+       /**
+        * Applies workspaces behaviour for swapping/publishing and takes care of the swapMode.
+        *
+        * @param string $table
+        * @param integer $liveId
+        * @param array $properties
+        * @return void
+        */
+       protected function applyWorkspacesSwapBehaviour($table, $liveId, array $properties) {
+               $extendedCommandMap = array();
+               $elementList = array();
+               // Fetch accordant elements if the swapMode is 'any' or 'pages':
+               if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages' && $table === 'pages') {
+                       $elementList = $this->getParent()->findPageElementsForVersionSwap($table, $liveId, $properties['swapWith']);
+               }
+               foreach ($elementList as $elementTable => $elementIdArray) {
+                       foreach ($elementIdArray as $elementIds) {
+                               $extendedCommandMap[$elementTable][$elementIds[0]]['version'] = array_merge($properties, array('swapWith' => $elementIds[1]));
+                       }
+               }
+               if (count($elementList) > 0) {
+                       $this->remove($table, $liveId, 'version');
+                       $this->mergeToBottom($extendedCommandMap);
+               }
+       }
+
+       /**
+        * Adds workspaces elements for swapping/publishing.
+        *
+        * @param t3lib_utility_Dependency $dependency
+        * @param string $table
+        * @param integer $liveId
+        * @param array $properties
+        * @return void
+        */
+       protected function addWorkspacesSwapElements(t3lib_utility_Dependency $dependency, $table, $liveId, array $properties) {
+               $elementList = array();
+               // Fetch accordant elements if the swapMode is 'any' or 'pages':
+               if ($this->workspacesSwapMode === 'any' || $this->workspacesSwapMode === 'pages' && $table === 'pages') {
+                       $elementList = $this->getParent()->findPageElementsForVersionSwap($table, $liveId, $properties['swapWith']);
+               }
+               foreach ($elementList as $elementTable => $elementIdArray) {
+                       foreach ($elementIdArray as $elementIds) {
+                               $dependency->addElement($elementTable, $elementIds[1], array('liveId' => $elementIds[0], 'properties' => array_merge($properties, array('swapWith' => $elementIds[1]))));
+                       }
+               }
+               if (count($elementList) === 0) {
+                       $dependency->addElement($table, $properties['swapWith'], array('liveId' => $liveId, 'properties' => $properties));
+               }
+       }
+
+       /**
+        * Invokes all items for staging with a callback method.
+        *
+        * @param string $callbackMethod
+        * @param array $arguments Optional leading arguments for the callback method
+        * @return void
+        */
+       protected function invokeWorkspacesSetStageItems($callbackMethod, array $arguments = array()) {
+               // Traverses the cmd[] array and fetches the accordant actions:
+               foreach ($this->commandMap as $table => $liveIdCollection) {
+                       foreach ($liveIdCollection as $liveIdList => $commandCollection) {
+                               foreach ($commandCollection as $command => $properties) {
+                                       if (($command === 'version' && isset($properties['action'])) && $properties['action'] === 'setStage') {
+                                               if (isset($properties['stageId']) && t3lib_utility_Math::canBeInterpretedAsInteger($properties['stageId'])) {
+                                                       call_user_func_array(array($this, $callbackMethod), array_merge($arguments, array($table, $liveIdList, $properties)));
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Resolves workspaces related dependencies for staging of the command map.
+        * Workspaces records that have children or (relative) parents which are versionized
+        * but not staged with this request, are removed from the command map.
+        *
+        * @return void
+        */
+       protected function resolveWorkspacesSetStageDependencies() {
+               $scope = self::SCOPE_WorkspacesSetStage;
+               $dependency = $this->getDependencyUtility($scope);
+               if (t3lib_div::inList('any,pages', $this->workspacesChangeStageMode)) {
+                       $this->invokeWorkspacesSetStageItems('applyWorkspacesSetStageBehaviour');
+               }
+               $this->invokeWorkspacesSetStageItems('explodeSetStage');
+               $this->invokeWorkspacesSetStageItems('addWorkspacesSetStageElements', array($dependency));
+               $this->applyWorkspacesDependencies($dependency, $scope);
+       }
+
+       /**
+        * Applies workspaces behaviour for staging and takes care of the changeStageMode.
+        *
+        * @param string $table
+        * @param string $liveIdList
+        * @param array $properties
+        * @return void
+        */
+       protected function applyWorkspacesSetStageBehaviour($table, $liveIdList, array $properties) {
+               $extendedCommandMap = array();
+               $liveIds = t3lib_div::trimExplode(',', $liveIdList, TRUE);
+               $elementList = array($table => $liveIds);
+               if (t3lib_div::inList('any,pages', $this->workspacesChangeStageMode)) {
+                       if (count($liveIds) === 1) {
+                               $workspaceRecord = t3lib_BEfunc::getRecord($table, $liveIds[0], 't3ver_wsid');
+                               $workspaceId = $workspaceRecord['t3ver_wsid'];
+                       } else {
+                               $workspaceId = $this->getTceMain()->BE_USER->workspace;
+                       }
+                       if ($table === 'pages') {
+                               // Find all elements from the same ws to change stage
+                               $this->getParent()->findRealPageIds($liveIds);
+                               $this->getParent()->findPageElementsForVersionStageChange($liveIds, $workspaceId, $elementList);
+                       } elseif ($this->workspacesChangeStageMode === 'any') {
+                               // Find page to change stage:
+                               $pageIdList = array();
+                               $this->getParent()->findPageIdsForVersionStateChange($table, $liveIds, $workspaceId, $pageIdList, $elementList);
+                               // Find other elements from the same ws to change stage:
+                               $this->getParent()->findPageElementsForVersionStageChange($pageIdList, $workspaceId, $elementList);
+                       }
+               }
+               foreach ($elementList as $elementTable => $elementIds) {
+                       foreach ($elementIds as $elementId) {
+                               $extendedCommandMap[$elementTable][$elementId]['version'] = $properties;
+                       }
+               }
+               $this->remove($table, $liveIdList, 'version');
+               $this->mergeToBottom($extendedCommandMap);
+       }
+
+       /**
+        * Adds workspaces elements for staging.
+        *
+        * @param t3lib_utility_Dependency $dependency
+        * @param string $table
+        * @param string $liveIdList
+        * @param array $properties
+        * @return void
+        */
+       protected function addWorkspacesSetStageElements(t3lib_utility_Dependency $dependency, $table, $liveIdList, array $properties) {
+               $dependency->addElement($table, $liveIdList, array('properties' => $properties));
+       }
+
+       /**
+        * Resolves workspaces related dependencies for clearing/flushing of the command map.
+        * Workspaces records that have children or (relative) parents which are versionized
+        * but not cleared/flushed with this request, are removed from the command map.
+        *
+        * @return void
+        */
+       protected function resolveWorkspacesClearDependencies() {
+               $scope = self::SCOPE_WorkspacesClear;
+               $dependency = $this->getDependencyUtility($scope);
+               // Traverses the cmd[] array and fetches the accordant actions:
+               foreach ($this->commandMap as $table => $liveIdCollection) {
+                       foreach ($liveIdCollection as $liveId => $commandCollection) {
+                               foreach ($commandCollection as $command => $properties) {
+                                       if (($command === 'version' && isset($properties['action'])) && ($properties['action'] === 'clearWSID' || $properties['action'] === 'flush')) {
+                                               $dependency->addElement($table, $liveId, array('properties' => $properties));
+                                       }
+                               }
+                       }
+               }
+               $this->applyWorkspacesDependencies($dependency, $scope);
+       }
+
+       /**
+        * Explodes id-lists in the command map for staging actions.
+        *
+        * @throws RuntimeException
+        * @param string $table
+        * @param string $liveIdList
+        * @param array $properties
+        * @return void
+        */
+       protected function explodeSetStage($table, $liveIdList, array $properties) {
+               $extractedCommandMap = array();
+               $liveIds = t3lib_div::trimExplode(',', $liveIdList, TRUE);
+               if (count($liveIds) > 1) {
+                       foreach ($liveIds as $liveId) {
+                               if (isset($this->commandMap[$table][$liveId]['version'])) {
+                                       throw new RuntimeException(((('Command map for [' . $table) . '][') . $liveId) . '][version] was already set.', 1289391048);
+                               }
+                               $extractedCommandMap[$table][$liveId]['version'] = $properties;
+                       }
+                       $this->remove($table, $liveIdList, 'version');
+                       $this->mergeToBottom($extractedCommandMap);
+               }
+       }
+
+       /**
+        * Applies the workspaces dependencies and removes incomplete structures or automatically
+        * completes them, depending on the options.workspaces.considerReferences setting
+        *
+        * @param t3lib_utility_Dependency $dependency
+        * @param string $scope
+        * @return void
+        */
+       protected function applyWorkspacesDependencies(t3lib_utility_Dependency $dependency, $scope) {
+               $transformDependentElementsToUseLiveId = $this->getScopeData($scope, self::KEY_TransformDependentElementsToUseLiveId);
+               $elementsToBeVersionized = $dependency->getElements();
+               // Use the uid of the live record instead of the workspace record:
+               if ($transformDependentElementsToUseLiveId) {
+                       $elementsToBeVersionized = $this->transformDependentElementsToUseLiveId($elementsToBeVersionized);
+               }
+               $outerMostParents = $dependency->getOuterMostParents();
+               /** @var $outerMostParent t3lib_utility_Dependency_Element */
+               foreach ($outerMostParents as $outerMostParent) {
+                       $dependentElements = $dependency->getNestedElements($outerMostParent);
+                       if ($transformDependentElementsToUseLiveId) {
+                               $dependentElements = $this->transformDependentElementsToUseLiveId($dependentElements);
+                       }
+                       // Gets the difference (intersection) between elements that were submitted by the user
+                       // and the evaluation of all dependent records that should be used for this action instead:
+                       $intersectingElements = array_intersect_key($dependentElements, $elementsToBeVersionized);
+                       if (count($intersectingElements) > 0) {
+                               // If at least one element intersects but not all, throw away all elements of the depdendent structure:
+                               if (count($intersectingElements) !== count($dependentElements) && $this->workspacesConsiderReferences === FALSE) {
+                                       $this->purgeWithErrorMessage($intersectingElements, $scope);
+                               } else {
+                                       $this->update(current($intersectingElements), $dependentElements, $scope);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Purges incomplete structures from the command map and triggers an error message.
+        *
+        * @param array $elements
+        * @param string $scope
+        * @return void
+        */
+       protected function purgeWithErrorMessage(array $elements, $scope) {
+               /** @var $element t3lib_utility_Dependency_Element */
+               foreach ($elements as $element) {
+                       $table = $element->getTable();
+                       $id = $this->processCallback($this->getScopeData($scope, self::KEY_PurgeWithErrorMessageGetIdCallback), array($element));
+                       $this->remove($table, $id, 'version');
+                       $this->getTceMain()->log($table, $id, 5, 0, 1, $this->getScopeData($scope, self::KEY_ScopeErrorMessage), $this->getScopeData($scope, self::KEY_ScopeErrorCode), array(
+                               t3lib_BEfunc::getRecordTitle($table, t3lib_BEfunc::getRecord($table, $id)),
+                               $table,
+                               $id
+                       ));
+               }
+       }
+
+       /**
+        * Updates the command map accordant to valid structures and takes care of the correct order.
+        *
+        * @param t3lib_utility_Dependency_Element $intersectingElement
+        * @param array $elements
+        * @param string $scope
+        * @return void
+        */
+       protected function update(t3lib_utility_Dependency_Element $intersectingElement, array $elements, $scope) {
+               $orderedCommandMap = array();
+               $commonProperties = array();
+               if ($this->getScopeData($scope, self::KEY_GetCommonPropertiesCallback)) {
+                       $commonProperties = $this->processCallback($this->getScopeData($scope, self::KEY_GetCommonPropertiesCallback), array($intersectingElement));
+               }
+               /** @var $element t3lib_utility_Dependency_Element */
+               foreach ($elements as $element) {
+                       $table = $element->getTable();
+                       $id = $this->processCallback($this->getScopeData($scope, self::KEY_UpdateGetIdCallback), array($element));
+                       $this->remove($table, $id, 'version');
+                       $orderedCommandMap[$table][$id]['version'] = $commonProperties;
+                       if ($this->getScopeData($scope, self::KEY_GetElementPropertiesCallback)) {
+                               $orderedCommandMap[$table][$id]['version'] = array_merge($commonProperties, $this->processCallback($this->getScopeData($scope, self::KEY_GetElementPropertiesCallback), array($element)));
+                       }
+               }
+               // Ensure that ordered command map is on top of the command map:
+               $this->mergeToTop($orderedCommandMap);
+       }
+
+       /**
+        * Merges command map elements to the top of the current command map..
+        *
+        * @param array $commandMap
+        * @return void
+        */
+       protected function mergeToTop(array $commandMap) {
+               $this->commandMap = t3lib_div::array_merge_recursive_overrule($commandMap, $this->commandMap);
+       }
+
+       /**
+        * Merges command map elements to the bottom of the current command map.
+        *
+        * @param array $commandMap
+        * @return void
+        */
+       protected function mergeToBottom(array $commandMap) {
+               $this->commandMap = t3lib_div::array_merge_recursive_overrule($this->commandMap, $commandMap);
+       }
+
+       /**
+        * Removes an element from the command map.
+        *
+        * @param string $table
+        * @param string $id
+        * @param string $command (optional)
+        * @return void
+        */
+       protected function remove($table, $id, $command = NULL) {
+               if (is_string($command)) {
+                       unset($this->commandMap[$table][$id][$command]);
+               } else {
+                       unset($this->commandMap[$table][$id]);
+               }
+       }
+
+       /**
+        * Callback to get the liveId of an dependent element.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return integer
+        */
+       protected function getElementLiveIdCallback(t3lib_utility_Dependency_Element $element) {
+               return $element->getDataValue('liveId');
+       }
+
+       /**
+        * Callback to get the real id of an dependent element.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return integer
+        */
+       protected function getElementIdCallback(t3lib_utility_Dependency_Element $element) {
+               return $element->getId();
+       }
+
+       /**
+        * Callback to get the specific properties of a dependent element for swapping/publishing.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return array
+        */
+       protected function getElementSwapPropertiesCallback(t3lib_utility_Dependency_Element $element) {
+               return array(
+                       'swapWith' => $element->getId()
+               );
+       }
+
+       /**
+        * Callback to get common properties of dependent elements for clearing.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return array
+        */
+       protected function getCommonClearPropertiesCallback(t3lib_utility_Dependency_Element $element) {
+               $commonSwapProperties = array();
+               $elementProperties = $element->getDataValue('properties');
+               if (isset($elementProperties['action'])) {
+                       $commonSwapProperties['action'] = $elementProperties['action'];
+               }
+               return $commonSwapProperties;
+       }
+
+       /**
+        * Callback to get common properties of dependent elements for swapping/publishing.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return array
+        */
+       protected function getCommonSwapPropertiesCallback(t3lib_utility_Dependency_Element $element) {
+               $commonSwapProperties = array();
+               $elementProperties = $element->getDataValue('properties');
+               if (isset($elementProperties['action'])) {
+                       $commonSwapProperties['action'] = $elementProperties['action'];
+               }
+               if (isset($elementProperties['swapIntoWS'])) {
+                       $commonSwapProperties['swapIntoWS'] = $elementProperties['swapIntoWS'];
+               }
+               return $commonSwapProperties;
+       }
+
+       /**
+        * Callback to get the specific properties of a dependent element for staging.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return array
+        */
+       protected function getElementSetStagePropertiesCallback(t3lib_utility_Dependency_Element $element) {
+               return $this->getCommonSetStagePropertiesCallback($element);
+       }
+
+       /**
+        * Callback to get common properties of dependent elements for staging.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return array
+        */
+       protected function getCommonSetStagePropertiesCallback(t3lib_utility_Dependency_Element $element) {
+               $commonSetStageProperties = array();
+               $elementProperties = $element->getDataValue('properties');
+               if (isset($elementProperties['stageId'])) {
+                       $commonSetStageProperties['stageId'] = $elementProperties['stageId'];
+               }
+               if (isset($elementProperties['comment'])) {
+                       $commonSetStageProperties['comment'] = $elementProperties['comment'];
+               }
+               if (isset($elementProperties['action'])) {
+                       $commonSetStageProperties['action'] = $elementProperties['action'];
+               }
+               if (isset($elementProperties['notificationAlternativeRecipients'])) {
+                       $commonSetStageProperties['notificationAlternativeRecipients'] = $elementProperties['notificationAlternativeRecipients'];
+               }
+               return $commonSetStageProperties;
+       }
+
+       /**
+        * Gets an instance of the depency resolver utility.
+        *
+        * @param string $scope Scope identifier
+        * @return t3lib_utility_Dependency
+        */
+       protected function getDependencyUtility($scope) {
+               /** @var $dependency t3lib_utility_Dependency */
+               $dependency = t3lib_div::makeInstance('t3lib_utility_Dependency');
+               $dependency->setOuterMostParentsRequireReferences(TRUE);
+               if ($this->getScopeData($scope, self::KEY_ElementConstructCallback)) {
+                       $dependency->setEventCallback(t3lib_utility_Dependency_Element::EVENT_Construct, $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementConstructCallback)));
+               }
+               if ($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback)) {
+                       $dependency->setEventCallback(t3lib_utility_Dependency_Element::EVENT_CreateChildReference, $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateChildReferenceCallback)));
+               }
+               if ($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback)) {
+                       $dependency->setEventCallback(t3lib_utility_Dependency_Element::EVENT_CreateParentReference, $this->getDependencyCallback($this->getScopeData($scope, self::KEY_ElementCreateParentReferenceCallback)));
+               }
+               return $dependency;
+       }
+
+       /**
+        * Callback to determine whether a new child reference shall be considered in the dependency resolver utility.
+        *
+        * @param array $callerArguments
+        * @param array $targetArgument
+        * @param t3lib_utility_Dependency_Element $caller
+        * @param string $eventName
+        * @return string Skip response (if required)
+        */
+       public function createNewDependentElementChildReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
+               /** @var $reference t3lib_utility_Dependency_Reference */
+               $reference = $callerArguments['reference'];
+               $fieldCOnfiguration = t3lib_BEfunc::getTcaFieldConfiguration($caller->getTable(), $reference->getField());
+               if (!$fieldCOnfiguration || !t3lib_div::inList('field,list', $this->getTceMain()->getInlineFieldType($fieldCOnfiguration))) {
+                       return t3lib_utility_Dependency_Element::RESPONSE_Skip;
+               }
+       }
+
+       /**
+        * Callback to determine whether a new parent reference shall be considered in the dependency resolver utility.
+        *
+        * @param array $callerArguments
+        * @param array $targetArgument
+        * @param t3lib_utility_Dependency_Element $caller
+        * @param string $eventName
+        * @return string Skip response (if required)
+        */
+       public function createNewDependentElementParentReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
+               /** @var $reference t3lib_utility_Dependency_Reference */
+               $reference = $callerArguments['reference'];
+               $fieldCOnfiguration = t3lib_BEfunc::getTcaFieldConfiguration($reference->getElement()->getTable(), $reference->getField());
+               if (!$fieldCOnfiguration || !t3lib_div::inList('field,list', $this->getTceMain()->getInlineFieldType($fieldCOnfiguration))) {
+                       return t3lib_utility_Dependency_Element::RESPONSE_Skip;
+               }
+       }
+
+       /**
+        * Callback to determine whether a new child reference shall be considered in the dependency resolver utility.
+        * Only elements that are a delete placeholder are considered.
+        *
+        * @param array $callerArguments
+        * @param array $targetArgument
+        * @param t3lib_utility_Dependency_Element $caller
+        * @param string $eventName
+        * @return string Skip response (if required)
+        */
+       public function createClearDependentElementChildReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
+               $response = $this->createNewDependentElementChildReferenceCallback($callerArguments, $targetArgument, $caller, $eventName);
+               if (empty($response)) {
+                       /** @var $reference t3lib_utility_Dependency_Reference */
+                       $reference = $callerArguments['reference'];
+                       $record = $reference->getElement()->getRecord();
+                       if ($record['t3ver_state'] != 2) {
+                               $response = t3lib_utility_Dependency_Element::RESPONSE_Skip;
+                       }
+               }
+               return $response;
+       }
+
+       /**
+        * Callback to determine whether a new parent reference shall be considered in the dependency resolver utility.
+        * Only elements that are a delete placeholder are considered.
+        *
+        * @param array $callerArguments
+        * @param array $targetArgument
+        * @param t3lib_utility_Dependency_Element $caller
+        * @param string $eventName
+        * @return string Skip response (if required)
+        */
+       public function createClearDependentElementParentReferenceCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller, $eventName) {
+               $response = $this->createNewDependentElementParentReferenceCallback($callerArguments, $targetArgument, $caller, $eventName);
+               if (empty($response)) {
+                       /** @var $reference t3lib_utility_Dependency_Reference */
+                       $reference = $callerArguments['reference'];
+                       $record = $reference->getElement()->getRecord();
+                       if ($record['t3ver_state'] != 2) {
+                               $response = t3lib_utility_Dependency_Element::RESPONSE_Skip;
+                       }
+               }
+               return $response;
+       }
+
+       /**
+        * Callback to add additional data to new elements created in the dependency resolver utility.
+        *
+        * @param t3lib_utility_Dependency_Element $caller
+        * @param array $callerArguments
+        * @param array $targetArgument
+        * @return void
+        */
+       public function createNewDependentElementCallback(array $callerArguments, array $targetArgument, t3lib_utility_Dependency_Element $caller) {
+               if ($caller->hasDataValue('liveId') === FALSE) {
+                       $liveId = t3lib_BEfunc::getLiveVersionIdOfRecord($caller->getTable(), $caller->getId());
+                       if (is_null($liveId) === FALSE) {
+                               $caller->setDataValue('liveId', $liveId);
+                       }
+               }
+       }
+
+       /**
+        * Transforms dependent elements to use the liveId as array key.
+        *
+        * @param array $elements Depedent elements, each of type t3lib_utility_Dependency_Element
+        * @return array
+        */
+       protected function transformDependentElementsToUseLiveId(array $elements) {
+               $transformedElements = array();
+               /** @var $element t3lib_utility_Dependency_Element */
+               foreach ($elements as $element) {
+                       $elementName = t3lib_utility_Dependency_Element::getIdentifier($element->getTable(), $element->getDataValue('liveId'));
+                       $transformedElements[$elementName] = $element;
+               }
+               return $transformedElements;
+       }
+
+       /**
+        * Constructs the scope settings.
+        * Currently the scopes for swapping/publishing and staging are available.
+        *
+        * @return void
+        */
+       protected function constructScopes() {
+               $this->scopes = array(
+                       // settings for publishing and swapping:
+                       self::SCOPE_WorkspacesSwap => array(
+                               // error message and error code
+                               self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be swapped or published independently, because it is related to other new or modified records.',
+                               self::KEY_ScopeErrorCode => 1288283630,
+                               // callback functons used to modify the commandMap
+                               // + element properties are specific for each element
+                               // + common properties are the same for all elements
+                               self::KEY_GetElementPropertiesCallback => 'getElementSwapPropertiesCallback',
+                               self::KEY_GetCommonPropertiesCallback => 'getCommonSwapPropertiesCallback',
+                               // callback function used, when a new element to be checked is added
+                               self::KEY_ElementConstructCallback => 'createNewDependentElementCallback',
+                               // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
+                               self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback',
+                               self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback',
+                               // callback function used to get the correct record uid to be used in the error message
+                               self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementLiveIdCallback',
+                               // callback function used to fetch the correct record uid on modifying the commandMap
+                               self::KEY_UpdateGetIdCallback => 'getElementLiveIdCallback',
+                               // setting whether to use the uid of the live record instead of the workspace record
+                               self::KEY_TransformDependentElementsToUseLiveId => TRUE
+                       ),
+                       // settings for modifying the stage:
+                       self::SCOPE_WorkspacesSetStage => array(
+                               // error message and error code
+                               self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be sent to another stage independently, because it is related to other new or modified records.',
+                               self::KEY_ScopeErrorCode => 1289342524,
+                               // callback functons used to modify the commandMap
+                               // + element properties are specific for each element
+                               // + common properties are the same for all elements
+                               self::KEY_GetElementPropertiesCallback => 'getElementSetStagePropertiesCallback',
+                               self::KEY_GetCommonPropertiesCallback => 'getCommonSetStagePropertiesCallback',
+                               // callback function used, when a new element to be checked is added
+                               self::KEY_ElementConstructCallback => NULL,
+                               // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
+                               self::KEY_ElementCreateChildReferenceCallback => 'createNewDependentElementChildReferenceCallback',
+                               self::KEY_ElementCreateParentReferenceCallback => 'createNewDependentElementParentReferenceCallback',
+                               // callback function used to get the correct record uid to be used in the error message
+                               self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementIdCallback',
+                               // callback function used to fetch the correct record uid on modifying the commandMap
+                               self::KEY_UpdateGetIdCallback => 'getElementIdCallback',
+                               // setting whether to use the uid of the live record instead of the workspace record
+                               self::KEY_TransformDependentElementsToUseLiveId => FALSE
+                       ),
+                       // settings for clearing and flushing:
+                       self::SCOPE_WorkspacesClear => array(
+                               // error message and error code
+                               self::KEY_ScopeErrorMessage => 'Record "%s" (%s:%s) cannot be flushed independently, because it is related to other new or modified records.',
+                               self::KEY_ScopeErrorCode => 1300467990,
+                               // callback functons used to modify the commandMap
+                               // + element properties are specific for each element
+                               // + common properties are the same for all elements
+                               self::KEY_GetElementPropertiesCallback => NULL,
+                               self::KEY_GetCommonPropertiesCallback => 'getCommonClearPropertiesCallback',
+                               // callback function used, when a new element to be checked is added
+                               self::KEY_ElementConstructCallback => NULL,
+                               // callback function used to determine whether an element is a valid child or parent reference (e.g. IRRE)
+                               self::KEY_ElementCreateChildReferenceCallback => 'createClearDependentElementChildReferenceCallback',
+                               self::KEY_ElementCreateParentReferenceCallback => 'createClearDependentElementParentReferenceCallback',
+                               // callback function used to get the correct record uid to be used in the error message
+                               self::KEY_PurgeWithErrorMessageGetIdCallback => 'getElementIdCallback',
+                               // callback function used to fetch the correct record uid on modifying the commandMap
+                               self::KEY_UpdateGetIdCallback => 'getElementIdCallback',
+                               // setting whether to use the uid of the live record instead of the workspace record
+                               self::KEY_TransformDependentElementsToUseLiveId => FALSE
+                       )
+               );
+       }
+
+       /**
+        * Gets data for a particular scope.
+        *
+        * @throws RuntimeException
+        * @param string $scope Scope identifier
+        * @param string $key
+        * @return string
+        */
+       protected function getScopeData($scope, $key) {
+               if (!isset($this->scopes[$scope])) {
+                       throw new RuntimeException(('Scope "' . $scope) . '" is not defined.', 1289342187);
+               }
+               return $this->scopes[$scope][$key];
+       }
+
+       /**
+        * Gets a new callback to be used in the dependency resolver utility.
+        *
+        * @param string $method
+        * @param array $targetArguments
+        * @return t3lib_utility_Dependency_Callback
+        */
+       protected function getDependencyCallback($method, array $targetArguments = array()) {
+               return t3lib_div::makeInstance('t3lib_utility_Dependency_Callback', $this, $method, $targetArguments);
+       }
+
+       /**
+        * Processes a local callback inside this object.
+        *
+        * @param string $method
+        * @param array $callbackArguments
+        * @return mixed
+        */
+       protected function processCallback($method, array $callbackArguments) {
+               return call_user_func_array(array($this, $method), $callbackArguments);
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Dependency/DependencyEntityFactory.php b/typo3/sysext/version/Classes/Dependency/DependencyEntityFactory.php
new file mode 100644 (file)
index 0000000..a0a983d
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010-2011 Oliver Hader <oliver@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script 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.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Object to create and keep track of element or reference entities.
+ */
+class t3lib_utility_Dependency_Factory {
+
+       /**
+        * @var array
+        */
+       protected $elements = array();
+
+       /**
+        * @var array
+        */
+       protected $references = array();
+
+       /**
+        * Gets and registers a new element.
+        *
+        * @param string $table
+        * @param integer $id
+        * @param array $data (optional)
+        * @param t3lib_utility_Dependency $dependency
+        * @return t3lib_utility_Dependency_Element
+        */
+       public function getElement($table, $id, array $data = array(), t3lib_utility_Dependency $dependency) {
+               $elementName = ($table . ':') . $id;
+               if (!isset($this->elements[$elementName])) {
+                       $this->elements[$elementName] = t3lib_div::makeInstance('t3lib_utility_Dependency_Element', $table, $id, $data, $dependency);
+               }
+               return $this->elements[$elementName];
+       }
+
+       /**
+        * Gets and registers a new reference.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @param string $field
+        * @return t3lib_utility_Dependency_Reference
+        */
+       public function getReference(t3lib_utility_Dependency_Element $element, $field) {
+               $referenceName = ($element->__toString() . '.') . $field;
+               if (!isset($this->references[$referenceName][$field])) {
+                       $this->references[$referenceName][$field] = t3lib_div::makeInstance('t3lib_utility_Dependency_Reference', $element, $field);
+               }
+               return $this->references[$referenceName][$field];
+       }
+
+       /**
+        * Gets and registers a new reference.
+        *
+        * @param string $table
+        * @param integer $id
+        * @param string $field
+        * @param array $data (optional
+        * @param t3lib_utility_Dependency $dependency
+        * @return t3lib_utility_Dependency_Reference
+        * @see getElement
+        * @see getReference
+        */
+       public function getReferencedElement($table, $id, $field, array $data = array(), t3lib_utility_Dependency $dependency) {
+               return $this->getReference($this->getElement($table, $id, $data, $dependency), $field);
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Dependency/DependencyResolver.php b/typo3/sysext/version/Classes/Dependency/DependencyResolver.php
new file mode 100644 (file)
index 0000000..65ba1ad
--- /dev/null
@@ -0,0 +1,184 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010-2011 Oliver Hader <oliver@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script 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.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Object to handle and determine dependent references of elements.
+ */
+class t3lib_utility_Dependency {
+
+       /**
+        * @var t3lib_utility_Dependency_Factory
+        */
+       protected $factory;
+
+       /**
+        * @var array
+        */
+       protected $elements = array();
+
+       /**
+        * @var array
+        */
+       protected $eventCallbacks = array();
+
+       /**
+        * @var boolean
+        */
+       protected $outerMostParentsRequireReferences = FALSE;
+
+       /**
+        * @var array
+        */
+       protected $outerMostParents;
+
+       /**
+        * Sets a callback for a particular event.
+        *
+        * @param string $eventName
+        * @param t3lib_utility_Dependency_Callback $callback
+        * @return t3lib_utility_Dependency
+        */
+       public function setEventCallback($eventName, t3lib_utility_Dependency_Callback $callback) {
+               $this->eventCallbacks[$eventName] = $callback;
+               return $this;
+       }
+
+       /**
+        * Executes a registered callback (if any) for a particular event.
+        *
+        * @param string $eventName
+        * @param object $caller
+        * @param array $callerArguments
+        * @return mixed
+        */
+       public function executeEventCallback($eventName, $caller, array $callerArguments = array()) {
+               if (isset($this->eventCallbacks[$eventName])) {
+                       /** @var $callback t3lib_utility_Dependency_Callback */
+                       $callback = $this->eventCallbacks[$eventName];
+                       return $callback->execute($callerArguments, $caller, $eventName);
+               }
+       }
+
+       /**
+        * Sets the condition that outermost parents required at least one child or parent reference.
+        *
+        * @param boolean $outerMostParentsRequireReferences
+        * @return t3lib_utility_Dependency
+        */
+       public function setOuterMostParentsRequireReferences($outerMostParentsRequireReferences) {
+               $this->outerMostParentsRequireReferences = (bool) $outerMostParentsRequireReferences;
+               return $this;
+       }
+
+       /**
+        * Adds an element to be checked for dependent references.
+        *
+        * @param string $table
+        * @param integer $id
+        * @param array $data
+        * @return t3lib_utility_Dependency_Element
+        */
+       public function addElement($table, $id, array $data = array()) {
+               $element = $this->getFactory()->getElement($table, $id, $data, $this);
+               $elementName = $element->__toString();
+               $this->elements[$elementName] = $element;
+               return $element;
+       }
+
+       /**
+        * Gets the outermost parents that define complete dependent structure each.
+        *
+        * @return array
+        */
+       public function getOuterMostParents() {
+               if (!isset($this->outerMostParents)) {
+                       $this->outerMostParents = array();
+                       /** @var $element t3lib_utility_Dependency_Element */
+                       foreach ($this->elements as $element) {
+                               $this->processOuterMostParent($element);
+                       }
+               }
+               return $this->outerMostParents;
+       }
+
+       /**
+        * Processes and registers the outermost parents accordant to the registered elements.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @return void
+        */
+       protected function processOuterMostParent(t3lib_utility_Dependency_Element $element) {
+               if ($this->outerMostParentsRequireReferences === FALSE || $element->hasReferences()) {
+                       $outerMostParent = $element->getOuterMostParent();
+                       if ($outerMostParent !== FALSE) {
+                               $outerMostParentName = $outerMostParent->__toString();
+                               if (!isset($this->outerMostParents[$outerMostParentName])) {
+                                       $this->outerMostParents[$outerMostParentName] = $outerMostParent;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Gets all nested elements (including the parent) of a particular outermost parent element.
+        *
+        * @throws RuntimeException
+        * @param t3lib_utility_Dependency_Element $outerMostParent
+        * @return array
+        */
+       public function getNestedElements(t3lib_utility_Dependency_Element $outerMostParent) {
+               $outerMostParentName = $outerMostParent->__toString();
+               if (!isset($this->outerMostParents[$outerMostParentName])) {
+                       throw new RuntimeException(('Element "' . $outerMostParentName) . '" was detected as outermost parent.', 1289318609);
+               }
+               $nestedStructure = array_merge(array($outerMostParentName => $outerMostParent), $outerMostParent->getNestedChildren());
+               return $nestedStructure;
+       }
+
+       /**
+        * Gets the registered elements.
+        *
+        * @return array
+        */
+       public function getElements() {
+               return $this->elements;
+       }
+
+       /**
+        * Gets an instance of the factory to keep track of element or reference entities.
+        *
+        * @return t3lib_utility_Dependency_Factory
+        */
+       public function getFactory() {
+               if (!isset($this->factory)) {
+                       $this->factory = t3lib_div::makeInstance('t3lib_utility_Dependency_Factory');
+               }
+               return $this->factory;
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Dependency/ElementEntity.php b/typo3/sysext/version/Classes/Dependency/ElementEntity.php
new file mode 100644 (file)
index 0000000..88d8edc
--- /dev/null
@@ -0,0 +1,310 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010-2011 Oliver Hader <oliver@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script 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.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Object to hold information on a dependent database element in abstract.
+ */
+class t3lib_utility_Dependency_Element {
+
+       const REFERENCES_ChildOf = 'childOf';
+       const REFERENCES_ParentOf = 'parentOf';
+       const EVENT_Construct = 't3lib_utility_Dependency_Element::construct';
+       const EVENT_CreateChildReference = 't3lib_utility_Dependency_Element::createChildReference';
+       const EVENT_CreateParentReference = 't3lib_utility_Dependency_Element::createParentReference';
+       const RESPONSE_Skip = 't3lib_utility_Dependency_Element->skip';
+       /**
+        * @var string
+        */
+       protected $table;
+
+       /**
+        * @var integer
+        */
+       protected $id;
+
+       /**
+        * @var array
+        */
+       protected $data;
+
+       /**
+        * @var array
+        */
+       protected $record;
+
+       /**
+        * @var t3lib_utility_Dependency
+        */
+       protected $dependency;
+
+       /**
+        * @var array
+        */
+       protected $children;
+
+       /**
+        * @var array
+        */
+       protected $parents;
+
+       /**
+        * @var boolean
+        */
+       protected $traversingParents = FALSE;
+
+       /**
+        * @var t3lib_utility_Dependency_Element
+        */
+       protected $outerMostParent;
+
+       /**
+        * @var array
+        */
+       protected $nestedChildren;
+
+       /**
+        * Creates this object.
+        *
+        * @param string $table
+        * @param integer $id
+        * @param array $data (optional)
+        * @param t3lib_utility_Dependency $dependency
+        */
+       public function __construct($table, $id, array $data = array(), t3lib_utility_Dependency $dependency) {
+               $this->table = $table;
+               $this->id = intval($id);
+               $this->data = $data;
+               $this->dependency = $dependency;
+               $this->dependency->executeEventCallback(self::EVENT_Construct, $this);
+       }
+
+       /**
+        * Gets the table.
+        *
+        * @return string
+        */
+       public function getTable() {
+               return $this->table;
+       }
+
+       /**
+        * Gets the id.
+        *
+        * @return integer
+        */
+       public function getId() {
+               return $this->id;
+       }
+
+       /**
+        * Gets the data.
+        *
+        * @return array
+        */
+       public function getData() {
+               return $this->data;
+       }
+
+       /**
+        * Gets a value for a particular key from the data.
+        *
+        * @param string $key
+        * @return mixed
+        */
+       public function getDataValue($key) {
+               $result = NULL;
+               if ($this->hasDataValue($key)) {
+                       $result = $this->data[$key];
+               }
+               return $result;
+       }
+
+       /**
+        * Sets a value for a particular key in the data.
+        *
+        * @param string $key
+        * @param mixed $value
+        * @return void
+        */
+       public function setDataValue($key, $value) {
+               $this->data[$key] = $value;
+       }
+
+       /**
+        * Determines whether a particular key holds data.
+        *
+        * @param string $key
+        * @return
+        */
+       public function hasDataValue($key) {
+               return isset($this->data[$key]);
+       }
+
+       /**
+        * Converts this object for string representation.
+        *
+        * @return string
+        */
+       public function __toString() {
+               return self::getIdentifier($this->table, $this->id);
+       }
+
+       /**
+        * Gets the parent dependency object.
+        *
+        * @return t3lib_utility_Dependency
+        */
+       public function getDependency() {
+               return $this->dependency;
+       }
+
+       /**
+        * Gets all child references.
+        *
+        * @return array
+        */
+       public function getChildren() {
+               if (!isset($this->children)) {
+                       $this->children = array();
+                       $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_refindex', (('tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->table, 'sys_refindex')) . ' AND recuid=') . $this->id);
+                       if (is_array($rows)) {
+                               foreach ($rows as $row) {
+                                       $reference = $this->getDependency()->getFactory()->getReferencedElement($row['ref_table'], $row['ref_uid'], $row['field'], array(), $this->getDependency());
+                                       $callbackResponse = $this->dependency->executeEventCallback(self::EVENT_CreateChildReference, $this, array('reference' => $reference));
+                                       if ($callbackResponse !== self::RESPONSE_Skip) {
+                                               $this->children[] = $reference;
+                                       }
+                               }
+                       }
+               }
+               return $this->children;
+       }
+
+       /**
+        * Gets all parent references.
+        *
+        * @return array
+        */
+       public function getParents() {
+               if (!isset($this->parents)) {
+                       $this->parents = array();
+                       $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', 'sys_refindex', (('ref_table=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->table, 'sys_refindex')) . ' AND deleted=0 AND ref_uid=') . $this->id);
+                       if (is_array($rows)) {
+                               foreach ($rows as $row) {
+                                       $reference = $this->getDependency()->getFactory()->getReferencedElement($row['tablename'], $row['recuid'], $row['field'], array(), $this->getDependency());
+                                       $callbackResponse = $this->dependency->executeEventCallback(self::EVENT_CreateParentReference, $this, array('reference' => $reference));
+                                       if ($callbackResponse !== self::RESPONSE_Skip) {
+                                               $this->parents[] = $reference;
+                                       }
+                               }
+                       }
+               }
+               return $this->parents;
+       }
+
+       /**
+        * Determines whether there are child or parent references.
+        *
+        * @return boolean
+        */
+       public function hasReferences() {
+               return count($this->getChildren()) > 0 || count($this->getParents()) > 0;
+       }
+
+       /**
+        * Gets the outermost parent element.
+        *
+        * @return t3lib_utility_Dependency_Element
+        */
+       public function getOuterMostParent() {
+               if (!isset($this->outerMostParent)) {
+                       $parents = $this->getParents();
+                       if (count($parents) === 0) {
+                               $this->outerMostParent = $this;
+                       } else {
+                               $this->outerMostParent = FALSE;
+                               /** @var $parent t3lib_utility_Dependency_Reference */
+                               foreach ($parents as $parent) {
+                                       $outerMostParent = $parent->getElement()->getOuterMostParent();
+                                       if ($outerMostParent instanceof t3lib_utility_Dependency_Element) {
+                                               $this->outerMostParent = $outerMostParent;
+                                               break;
+                                       } elseif ($outerMostParent === FALSE) {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+               return $this->outerMostParent;
+       }
+
+       /**
+        * Gets nested children accumulated.
+        *
+        * @return array
+        */
+       public function getNestedChildren() {
+               if (!isset($this->nestedChildren)) {
+                       $this->nestedChildren = array();
+                       $children = $this->getChildren();
+                       /** @var $child t3lib_utility_Dependency_Reference */
+                       foreach ($children as $child) {
+                               $this->nestedChildren = array_merge($this->nestedChildren, array($child->getElement()->__toString() => $child->getElement()), $child->getElement()->getNestedChildren());
+                       }
+               }
+               return $this->nestedChildren;
+       }
+
+       /**
+        * Converts the object for string representation.
+        *
+        * @param string $table
+        * @param integer $id
+        * @return string
+        */
+       static public function getIdentifier($table, $id) {
+               return ($table . ':') . $id;
+       }
+
+       /**
+        * Gets the database record of this element.
+        *
+        * @return array
+        */
+       public function getRecord() {
+               if (!isset($this->record)) {
+                       $this->record = array();
+                       $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', $this->getTable(), 'uid=' . $this->getId());
+                       if (is_array($rows)) {
+                               $this->record = $rows[0];
+                       }
+               }
+               return $this->record;
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Dependency/EventCallback.php b/typo3/sysext/version/Classes/Dependency/EventCallback.php
new file mode 100644 (file)
index 0000000..c655fef
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010-2011 Oliver Hader <oliver@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script 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.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Object to hold information on a callback to a defined object and method.
+ */
+class t3lib_utility_Dependency_Callback {
+
+       /**
+        * @var object
+        */
+       protected $object;
+
+       /**
+        * @var string
+        */
+       protected $method;
+
+       /**
+        * @var array
+        */
+       protected $targetArguments;
+
+       /**
+        * Creates the objects.
+        *
+        * @param object $object
+        * @param string $method
+        * @param array $targetArguments (optional)
+        */
+       public function __construct($object, $method, array $targetArguments = array()) {
+               $this->object = $object;
+               $this->method = $method;
+               $this->targetArguments = $targetArguments;
+               $this->targetArguments['target'] = $object;
+       }
+
+       /**
+        * Executes the callback.
+        *
+        * @param array $callerArguments
+        * @param object $caller
+        * @param string $eventName
+        * @return mixed
+        */
+       public function execute(array $callerArguments = array(), $caller, $eventName) {
+               return call_user_func_array(array($this->object, $this->method), array($callerArguments, $this->targetArguments, $caller, $eventName));
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Dependency/ReferenceEntity.php b/typo3/sysext/version/Classes/Dependency/ReferenceEntity.php
new file mode 100644 (file)
index 0000000..0c902ab
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010-2011 Oliver Hader <oliver@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script 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.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Object to hold reference information of a database field and one accordant element.
+ */
+class t3lib_utility_Dependency_Reference {
+
+       /**
+        * @var t3lib_utility_Dependency_Element
+        */
+       protected $element;
+
+       /**
+        * @var string
+        */
+       protected $field;
+
+       /**
+        * Creates this object.
+        *
+        * @param t3lib_utility_Dependency_Element $element
+        * @param string $field
+        */
+       public function __construct(t3lib_utility_Dependency_Element $element, $field) {
+               $this->element = $element;
+               $this->field = $field;
+       }
+
+       /**
+        * Gets the elements.
+        *
+        * @return t3lib_utility_Dependency_Element
+        */
+       public function getElement() {
+               return $this->element;
+       }
+
+       /**
+        * Gets the field.
+        *
+        * @return string
+        */
+       public function getField() {
+               return $this->field;
+       }
+
+       /**
+        * Converts this object for string representation.
+        *
+        * @return string
+        */
+       public function __toString() {
+               return ($this->element . '.') . $this->field;
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Hook/DataHandlerHook.php b/typo3/sysext/version/Classes/Hook/DataHandlerHook.php
new file mode 100644 (file)
index 0000000..0254f54
--- /dev/null
@@ -0,0 +1,1266 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *  (c) 2010-2011 Benjamin Mack (benni@typo3.org)
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Contains some parts for staging, versioning and workspaces
+ * to interact with the TYPO3 Core Engine
+ */
+class tx_version_tcemain {
+
+       /**
+        * For accumulating information about workspace stages raised
+        * on elements so a single mail is sent as notification.
+        * previously called "accumulateForNotifEmail" in tcemain
+        *
+        * @var array
+        */
+       protected $notificationEmailInfo = array();
+
+       /**
+        * General comment, eg. for staging in workspaces
+        *
+        * @var string
+        */
+       protected $generalComment = '';
+
+       /**
+        * Contains remapped IDs.
+        *
+        * @var array
+        */
+       protected $remappedIds = array();
+
+       /****************************
+        *****  Cmdmap  Hooks  ******
+        ****************************/
+       /**
+        * hook that is called before any cmd of the commandmap is executed
+        *
+        * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
+        * @return void
+        */
+       public function processCmdmap_beforeStart(t3lib_TCEmain $tcemainObj) {
+               // Reset notification array
+               $this->notificationEmailInfo = array();
+               // Resolve dependencies of version/workspaces actions:
+               $tcemainObj->cmdmap = $this->getCommandMap($tcemainObj, $tcemainObj->cmdmap)->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 integer $id the ID of the record
+        * @param mixed $value the value containing the data
+        * @param boolean $commandIsProcessed can be set so that other hooks or
+        * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
+        * @return      void
+        */
+       public function processCmdmap($command, $table, $id, $value, &$commandIsProcessed, t3lib_TCEmain $tcemainObj) {
+               // custom command "version"
+               if ($command == 'version') {
+                       $commandIsProcessed = TRUE;
+                       $action = (string) $value['action'];
+                       switch ($action) {
+                       case 'new':
+                               // check if page / branch versioning is needed,
+                               // or if "element" version can be used
+                               $versionizeTree = -1;
+                               if (isset($value['treeLevels'])) {
+                                       $versionizeTree = t3lib_utility_Math::forceIntegerInRange($value['treeLevels'], -1, 100);
+                               }
+                               if ($table == 'pages' && $versionizeTree >= 0) {
+                                       $this->versionizePages($id, $value['label'], $versionizeTree, $tcemainObj);
+                               } else {
+                                       $tcemainObj->versionizeRecord($table, $id, $value['label']);
+                               }
+                               break;
+                       case 'swap':
+                               $this->version_swap($table, $id, $value['swapWith'], $value['swapIntoWS'], $tcemainObj);
+                               break;
+                       case 'clearWSID':
+                               $this->version_clearWSID($table, $id, FALSE, $tcemainObj);
+                               break;
+                       case 'flush':
+                               $this->version_clearWSID($table, $id, TRUE, $tcemainObj);
+                               break;
+                       case 'setStage':
+                               $elementIds = t3lib_div::trimExplode(',', $id, TRUE);
+                               foreach ($elementIds as $elementId) {
+                                       $this->version_setStage($table, $elementId, $value['stageId'], isset($value['comment']) && $value['comment'] ? $value['comment'] : $this->generalComment, TRUE, $tcemainObj, $value['notificationAlternativeRecipients']);
+                               }
+                               break;
+                       }
+               }
+       }
+
+       /**
+        * Hook that is called after tcemain made most of its decisions.
+        *
+        * NOTE: This fixes an issue related to moving/creating initial-placeholders - if such a new page
+        * is intended to be place behind a move-placeholder tcemain handles the movement/creation,
+        * but does not respect the wsPlaceholder, which leads the new page to be located at the old location of the
+        * page where it was intended to be placed behind.
+        *
+        * @param string $command
+        * @param string $table
+        * @param int $id
+        * @param mixed $value
+        * @param t3lib_TCEmain $tcemain
+        */
+       public function processCmdmap_postProcess($command, $table, $id, $value, t3lib_TCEmain $tcemain) {
+               if ($command === 'move') {
+                       if ($value < 0) {
+                               $movePlaceHolder = t3lib_BEfunc::getMovePlaceholder($table, abs($value), 'uid');
+                               if ($movePlaceHolder !== FALSE) {
+                                       $destPid = -$movePlaceHolder['uid'];
+                                       $tcemain->moveRecord_raw($table, $id, $destPid);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * hook that is called AFTER all commands of the commandmap was
+        * executed
+        *
+        * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
+        * @return      void
+        */
+       public function processCmdmap_afterFinish(t3lib_TCEmain $tcemainObj) {
+               // Empty accumulation array:
+               foreach ($this->notificationEmailInfo as $notifItem) {
+                       $this->notifyStageChange($notifItem['shared'][0], $notifItem['shared'][1], implode(', ', $notifItem['elements']), 0, $notifItem['shared'][2], $tcemainObj, $notifItem['alternativeRecipients']);
+               }
+               // Reset notification array
+               $this->notificationEmailInfo = array();
+               // Reset remapped IDs
+               $this->remappedIds = array();
+       }
+
+       /**
+        * hook that is called AFTER all commands of the commandmap was
+        * executed
+        *
+        * @param string $table the table of the record
+        * @param integer $id the ID of the record
+        * @param array $record The accordant database record
+        * @param boolean $recordWasDeleted can be set so that other hooks or
+        * @param t3lib_TCEmain $tcemainObj reference to the main tcemain object
+        * @return      void
+        */
+       public function processCmdmap_deleteAction($table, $id, array $record, &$recordWasDeleted, t3lib_TCEmain $tcemainObj) {
+               // only process the hook if it wasn't processed
+               // by someone else before
+               if (!$recordWasDeleted) {
+                       $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 = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $id)) {
+                                       $record = $wsVersion;
+                                       $id = $record['uid'];
+                               }
+                       }
+                       // 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 ($tcemainObj->BE_USER->workspace == 0 || (int) $record['t3ver_wsid'] == $tcemainObj->BE_USER->workspace) {
+                                               $liveRec = t3lib_BEfunc::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...
+                                               if ((((($record['t3ver_state'] == 2 && !empty($liveRec['uid'])) && !empty($tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['action'])) && !empty($tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['swapWith'])) && $tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['action'] === 'swap') && $tcemainObj->cmdmap[$table][$liveRec['uid']]['version']['swapWith'] == $id) {
+                                                       return NULL;
+                                               }
+                                               // Delete those in WS 0 + if their live records state was not "Placeholder".
+                                               if ($record['t3ver_wsid'] == 0 || (int) $liveRec['t3ver_state'] <= 0) {
+                                                       $tcemainObj->deleteEl($table, $id);
+                                               } 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, $tcemainObj);
+                                               }
+                                       } else {
+                                               $tcemainObj->newlog('Tried to delete record from another workspace', 1);
+                                       }
+                               } else {
+                                       $tcemainObj->newlog('Versioning not enabled for record with PID = -1!', 2);
+                               }
+                       } elseif ($res = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($record['pid'], $table)) {
+                               // Look, if record is "online" or in a versionized branch, then delete directly.
+                               if ($res > 0) {
+                                       $tcemainObj->deleteEl($table, $id);
+                               } else {
+                                       $tcemainObj->newlog('Stage of root point did not allow for deletion', 1);
+                               }
+                       } elseif ((int) $record['t3ver_state'] === 3) {
+                               // Placeholders for moving operations are deletable directly.
+                               // Get record which its a placeholder for and reset the t3ver_state of that:
+                               if ($wsRec = t3lib_BEfunc::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...)
+                                       $updateFields = array(
+                                               't3ver_state' => 0
+                                       );
+                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsRec['uid']), $updateFields);
+                               }
+                               $tcemainObj->deleteEl($table, $id);
+                       } else {
+                               // Otherwise, try to delete by versioning:
+                               $copyMappingArray = $tcemainObj->copyMappingArray;
+                               $tcemainObj->versionizeRecord($table, $id, 'DELETED!', TRUE);
+                               // Determine newly created versions:
+                               // (remove placeholders are copied and modified, thus they appear in the copyMappingArray)
+                               $versionizedElements = t3lib_div::arrayDiffAssocRecursive($tcemainObj->copyMappingArray, $copyMappingArray);
+                               // Delete localization overlays:
+                               foreach ($versionizedElements as $versionizedTableName => $versionizedOriginalIds) {
+                                       foreach ($versionizedOriginalIds as $versionizedOriginalId => $_) {
+                                               $tcemainObj->deleteL10nOverlayRecords($versionizedTableName, $versionizedOriginalId);
+                                       }
+                               }
+                       }
+               }
+       }
+
+       /**
+        * hook for t3lib_TCEmain::moveRecord that cares about moving records that
+        * are *not* in the live workspace
+        *
+        * @param string $table the table of the record
+        * @param integer $id the ID of the record
+        * @param integer $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 integer $resolvedPid The final page ID of the record
+        * @param boolean $recordWasMoved can be set so that other hooks or
+        * @param       $table  the table
+        */
+       public function moveRecord($table, $uid, $destPid, array $propArr, array $moveRec, $resolvedPid, &$recordWasMoved, t3lib_TCEmain $tcemainObj) {
+               // Only do something in Draft workspace
+               if ($tcemainObj->BE_USER->workspace !== 0) {
+                       $recordWasMoved = TRUE;
+                       // Get workspace version of the source record, if any:
+                       $WSversion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
+                       // If no version exists and versioningWS is in version 2, a new placeholder is made automatically:
+                       if ((!$WSversion['uid'] && (int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) && (int) $moveRec['t3ver_state'] != 3) {
+                               $tcemainObj->versionizeRecord($table, $uid, 'Placeholder version for moving record');
+                               $WSversion = t3lib_BEfunc::getWorkspaceVersionOfRecord($tcemainObj->BE_USER->workspace, $table, $uid, 'uid,t3ver_oid');
+                       }
+                       // Check workspace permissions:
+                       $workspaceAccessBlocked = array();
+                       // Element was in "New/Deleted/Moved" so it can be moved...
+                       $recIsNewVersion = (int) $moveRec['t3ver_state'] > 0;
+                       $destRes = $tcemainObj->BE_USER->workspaceAllowLiveRecordsInPID($resolvedPid, $table);
+                       $canMoveRecord = $recIsNewVersion || (int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2;
+                       // Workspace source check:
+                       if (!$recIsNewVersion) {
+                               $errorCode = $tcemainObj->BE_USER->workspaceCannotEditRecord($table, $WSversion['uid'] ? $WSversion['uid'] : $uid);
+                               if ($errorCode) {
+                                       $workspaceAccessBlocked['src1'] = ('Record could not be edited in workspace: ' . $errorCode) . ' ';
+                               } elseif (!$canMoveRecord && $tcemainObj->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.
+                       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 (!count($workspaceAccessBlocked)) {
+                               // If the move operation is done on a versioned record, which is
+                               // NOT new/deleted placeholder and versioningWS is in version 2, then...
+                               if (($WSversion['uid'] && !$recIsNewVersion) && (int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
+                                       $this->moveRecord_wsPlaceholders($table, $uid, $destPid, $WSversion['uid'], $tcemainObj);
+                               } else {
+                                       // moving not needed, just behave like in live workspace
+                                       $recordWasMoved = FALSE;
+                               }
+                       } else {
+                               $tcemainObj->newlog('Move attempt failed due to workspace restrictions: ' . implode(' // ', $workspaceAccessBlocked), 1);
+                       }
+               }
+       }
+
+       /****************************
+        *****  Notifications  ******
+        ****************************/
+       /**
+        * Send an email notification to users in workspace
+        *
+        * @param array $stat Workspace access array (from t3lib_userauthgroup::checkWorkspace())
+        * @param integer $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 integer $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 t3lib_TCEmain $tcemainObj TCEmain object
+        * @param array $notificationAlternativeRecipients List of recipients to notify instead of be_users selected by sys_workspace, list is generated by workspace extension module
+        * @return void
+        */
+       protected function notifyStageChange(array $stat, $stageId, $table, $id, $comment, t3lib_TCEmain $tcemainObj, array $notificationAlternativeRecipients = array()) {
+               $workspaceRec = t3lib_BEfunc::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)) {
+                       // Get the new stage title from workspaces library, if workspaces extension is installed
+                       if (t3lib_extMgm::isLoaded('workspaces')) {
+                               $stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+                               $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!?';
+                                       break;
+                               }
+                       }
+                       if (count($notificationAlternativeRecipients) == 0) {
+                               // Compile list of recipients:
+                               $emails = array();
+                               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);
+                                                       $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('log_data,tstamp,userid', 'sys_log', (('action=6 and details_nr=30
+                                                                                       AND tablename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($eTable, 'sys_log')) . '
+                                                                                       AND recuid=') . intval($eUid), '', 'uid DESC');
+                                                       // Find all implicated since the last stage-raise from editing to review:
+                                                       foreach ($rows as $dat) {
+                                                               $data = unserialize($dat['log_data']);
+                                                               $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($dat['userid'], TRUE));
+                                                               if ($data['stage'] == 1) {
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                               break;
+                                       case 0:
+                                               $emails = $this->getEmailsForStageChangeNotification($workspaceRec['members']);
+                                               break;
+                                       default:
+                                               $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
+                                               break;
+                                       }
+                                       break;
+                               case 10:
+                                       $emails = $this->getEmailsForStageChangeNotification($workspaceRec['adminusers'], TRUE);
+                                       $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['reviewers']));
+                                       $emails = t3lib_div::array_merge($emails, $this->getEmailsForStageChangeNotification($workspaceRec['members']));
+                                       break;
+                               }
+                       } else {
+                               $emails = $notificationAlternativeRecipients;
+                       }
+                       // prepare and then send the emails
+                       if (count($emails)) {
+                               // Path to record is found:
+                               list($elementTable, $elementUid) = explode(':', $elementName);
+                               $elementUid = intval($elementUid);
+                               $elementRecord = t3lib_BEfunc::getRecord($elementTable, $elementUid);
+                               $recordTitle = t3lib_BEfunc::getRecordTitle($elementTable, $elementRecord);
+                               if ($elementTable == 'pages') {
+                                       $pageUid = $elementUid;
+                               } else {
+                                       t3lib_BEfunc::fixVersioningPid($elementTable, $elementRecord);
+                                       $pageUid = ($elementUid = $elementRecord['pid']);
+                               }
+                               // fetch the TSconfig settings for the email
+                               // old way, options are TCEMAIN.notificationEmail_body/subject
+                               $TCEmainTSConfig = $tcemainObj->getTCEMAIN_TSconfig($pageUid);
+                               // new way, options are
+                               // pageTSconfig: tx_version.workspaces.stageNotificationEmail.subject
+                               // userTSconfig: page.tx_version.workspaces.stageNotificationEmail.subject
+                               $pageTsConfig = t3lib_BEfunc::getPagesTSconfig($pageUid);
+                               $emailConfig = $pageTsConfig['tx_version.']['workspaces.']['stageNotificationEmail.'];
+                               $markers = array(
+                                       '###RECORD_TITLE###' => $recordTitle,
+                                       '###RECORD_PATH###' => t3lib_BEfunc::getRecordPath($elementUid, '', 20),
+                                       '###SITE_NAME###' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'],
+                                       '###SITE_URL###' => t3lib_div::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###' => $tcemainObj->BE_USER->user['realName'],
+                                       '###USER_FULLNAME###' => $tcemainObj->BE_USER->user['realName'],
+                                       '###USER_USERNAME###' => $tcemainObj->BE_USER->user['username']
+                               );
+                               // add marker for preview links if workspace extension is loaded
+                               if (t3lib_extMgm::isLoaded('workspaces')) {
+                                       $this->workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+                                       // only generate the link if the marker is in the template - prevents database from getting to much entries
+                                       if (t3lib_div::isFirstPartOfStr($emailConfig['message'], 'LLL:')) {
+                                               $tempEmailMessage = $GLOBALS['LANG']->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 $_classRef) {
+                                               $_procObj =& t3lib_div::getUserObj($_classRef);
+                                               $markers = $_procObj->postModifyMarkers($markers, $this);
+                                       }
+                               }
+                               // send an email to each individual user, to ensure the
+                               // multilanguage version of the email
+                               $emailHeaders = $emailConfig['additionalHeaders'];
+                               $emailRecipients = array();
+                               // an array of language objects that are needed
+                               // for emails with different languages
+                               $languageObjects = array(
+                                       $GLOBALS['LANG']->lang => $GLOBALS['LANG']
+                               );
+                               // 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 (t3lib_div::isFirstPartOfStr($emailSubject, 'LLL:') || t3lib_div::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 language */
+                                                       $languageObject = t3lib_div::makeInstance('language');
+                                                       $languageObject->init($recipientLanguage);
+                                                       $languageObjects[$recipientLanguage] = $languageObject;
+                                               } else {
+                                                       $languageObject = $languageObjects[$recipientLanguage];
+                                               }
+                                               if (t3lib_div::isFirstPartOfStr($emailSubject, 'LLL:')) {
+                                                       $emailSubject = $languageObject->sL($emailSubject);
+                                               }
+                                               if (t3lib_div::isFirstPartOfStr($emailMessage, 'LLL:')) {
+                                                       $emailMessage = $languageObject->sL($emailMessage);
+                                               }
+                                       }
+                                       $emailSubject = t3lib_parseHtml::substituteMarkerArray($emailSubject, $markers, '', TRUE, TRUE);
+                                       $emailMessage = t3lib_parseHtml::substituteMarkerArray($emailMessage, $markers, '', TRUE, TRUE);
+                                       // Send an email to the recipient
+                                       t3lib_div::plainMailEncoded($recipientData['email'], $emailSubject, $emailMessage, $emailHeaders);
+                               }
+                               $emailRecipients = implode(',', $emailRecipients);
+                               $tcemainObj->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 tcemain
+        *
+        * @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       boolean         $noTablePrefix If set, the input list are integers and not strings.
+        * @return      array           Array of emails
+        */
+       protected function getEmailsForStageChangeNotification($listOfUsers, $noTablePrefix = FALSE) {
+               $users = t3lib_div::trimExplode(',', $listOfUsers, 1);
+               $emails = array();
+               foreach ($users as $userIdent) {
+                       if ($noTablePrefix) {
+                               $id = intval($userIdent);
+                       } else {
+                               list($table, $id) = t3lib_div::revExplode('_', $userIdent, 2);
+                       }
+                       if ($table === 'be_users' || $noTablePrefix) {
+                               if ($userRecord = t3lib_BEfunc::getRecord('be_users', $id, 'uid,email,lang,realName', t3lib_BEfunc::BEenableFields('be_users'))) {
+                                       if (strlen(trim($userRecord['email']))) {
+                                               $emails[$id] = $userRecord;
+                                       }
+                               }
+                       }
+               }
+               return $emails;
+       }
+
+       /****************************
+        *****  Stage Changes  ******
+        ****************************/
+       /**
+        * Setting stage of record
+        *
+        * @param string $table Table name
+        * @param integer $integer Record UID
+        * @param integer $stageId Stage ID to set
+        * @param string $comment Comment that goes into log
+        * @param boolean $notificationEmailInfo Accumulate state changes in memory for compiled notification email?
+        * @param t3lib_TCEmain $tcemainObj TCEmain object
+        * @param array $notificationAlternativeRecipients comma separated list of recipients to notify instead of normal be_users
+        * @return void
+        */
+       protected function version_setStage($table, $id, $stageId, $comment = '', $notificationEmailInfo = FALSE, t3lib_TCEmain $tcemainObj, array $notificationAlternativeRecipients = array()) {
+               if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
+                       $tcemainObj->newlog('Attempt to set stage for record failed: ' . $errorCode, 1);
+               } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
+                       $record = t3lib_BEfunc::getRecord($table, $id);
+                       $stat = $tcemainObj->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 ($GLOBALS['BE_USER']->workspaceCheckStageForCurrent($record['t3ver_stage'])) {
+                               // Set stage of record:
+                               $updateData = array(
+                                       't3ver_stage' => $stageId
+                               );
+                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData);
+                               $tcemainObj->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!
+                               $tcemainObj->log($table, $id, 6, 0, 0, 'Stage raised...', 30, array('comment' => $comment, 'stage' => $stageId));
+                               if ((int) $stat['stagechg_notification'] > 0) {
+                                       if ($notificationEmailInfo) {
+                                               $this->notificationEmailInfo[((($stat['uid'] . ':') . $stageId) . ':') . $comment]['shared'] = array($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, $tcemainObj, $notificationAlternativeRecipients);
+                                       }
+                               }
+                       } else {
+                               $tcemainObj->newlog(('The member user tried to set a stage value "' . $stageId) . '" that was not allowed', 1);
+                       }
+               } else {
+                       $tcemainObj->newlog('Attempt to set stage for record failed because you do not have edit access', 1);
+               }
+       }
+
+       /*****************************
+        *****  CMD versioning  ******
+        *****************************/
+       /**
+        * Creates a new version of a page including content and possible subpages.
+        *
+        * @param integer $uid Page uid to create new version of.
+        * @param string $label Version label
+        * @param integer $versionizeTree Indicating "treeLevel" - "page" (0) or "branch" (>=1) ["element" type must call versionizeRecord() directly]
+        * @param t3lib_TCEmain $tcemainObj TCEmain object
+        * @return void
+        * @see copyPages()
+        */
+       protected function versionizePages($uid, $label, $versionizeTree, t3lib_TCEmain $tcemainObj) {
+               $uid = intval($uid);
+               // returns the branch
+               $brExist = $tcemainObj->doesBranchExist('', $uid, $tcemainObj->pMap['show'], 1);
+               // Checks if we had permissions
+               if ($brExist != -1) {
+                       // Make list of tables that should come along with a new version of the page:
+                       $verTablesArray = array();
+                       $allTables = array_keys($GLOBALS['TCA']);
+                       foreach ($allTables as $tableName) {
+                               if ($tableName != 'pages' && ($versionizeTree > 0 || $GLOBALS['TCA'][$tableName]['ctrl']['versioning_followPages'])) {
+                                       $verTablesArray[] = $tableName;
+                               }
+                       }
+                       // Remove the possible inline child tables from the tables to be versioniozed automatically:
+                       $verTablesArray = array_diff($verTablesArray, $this->getPossibleInlineChildTablesOfParentTable('pages'));
+                       // Begin to copy pages if we're allowed to:
+                       if ($tcemainObj->BE_USER->workspaceVersioningTypeAccess($versionizeTree)) {
+                               // Versionize this page:
+                               $theNewRootID = $tcemainObj->versionizeRecord('pages', $uid, $label, FALSE, $versionizeTree);
+                               if ($theNewRootID) {
+                                       $this->rawCopyPageContent($uid, $theNewRootID, $verTablesArray, $tcemainObj);
+                                       // If we're going to copy recursively...:
+                                       if ($versionizeTree > 0) {
+                                               // Get ALL subpages to copy (read permissions respected - they should NOT be...):
+                                               $CPtable = $tcemainObj->int_pageTreeInfo(array(), $uid, intval($versionizeTree), $theNewRootID);
+                                               // Now copying the subpages
+                                               foreach ($CPtable as $thePageUid => $thePagePid) {
+                                                       $newPid = $tcemainObj->copyMappingArray['pages'][$thePagePid];
+                                                       if (isset($newPid)) {
+                                                               $theNewRootID = $tcemainObj->copyRecord_raw('pages', $thePageUid, $newPid);
+                                                               $this->rawCopyPageContent($thePageUid, $theNewRootID, $verTablesArray, $tcemainObj);
+                                                       } else {
+                                                               $tcemainObj->newlog('Something went wrong during copying branch (for versioning)', 1);
+                                                               break;
+                                                       }
+                                               }
+                                       }
+                               } else {
+                                       $tcemainObj->newlog('The root version could not be created!', 1);
+                               }
+                       } else {
+                               $tcemainObj->newlog(('Versioning type "' . $versionizeTree) . '" was not allowed in workspace', 1);
+                       }
+               } else {
+                       $tcemainObj->newlog('Could not read all subpages to versionize.', 1);
+               }
+       }
+
+       /**
+        * 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 integer $id UID of the online record to swap
+        * @param integer $swapWith UID of the archived version to swap with!
+        * @param boolean $swapIntoWS If set, swaps online into workspace instead of publishing out of workspace.
+        * @param t3lib_TCEmain $tcemainObj TCEmain object
+        * @return void
+        */
+       protected function version_swap($table, $id, $swapWith, $swapIntoWS = 0, t3lib_TCEmain $tcemainObj) {
+               // First, check if we may actually edit the online record
+               if ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
+                       // Select the two versions:
+                       $curVersion = t3lib_BEfunc::getRecord($table, $id, '*');
+                       $swapVersion = t3lib_BEfunc::getRecord($table, $swapWith, '*');
+                       $movePlh = array();
+                       $movePlhID = 0;
+                       if (is_array($curVersion) && is_array($swapVersion)) {
+                               if ($tcemainObj->BE_USER->workspacePublishAccess($swapVersion['t3ver_wsid'])) {
+                                       $wsAccess = $tcemainObj->BE_USER->checkWorkspace($swapVersion['t3ver_wsid']);
+                                       if (($swapVersion['t3ver_wsid'] <= 0 || !($wsAccess['publish_access'] & 1)) || (int) $swapVersion['t3ver_stage'] === -10) {
+                                               if ($tcemainObj->doesRecordExist($table, $swapWith, 'show') && $tcemainObj->checkRecordUpdateAccess($table, $swapWith)) {
+                                                       if (!$swapIntoWS || $tcemainObj->BE_USER->workspaceSwapAccess()) {
+                                                               // Check if the swapWith record really IS a version of the original!
+                                                               if (((int) $swapVersion['pid'] == -1 && (int) $curVersion['pid'] >= 0) && !strcmp($swapVersion['t3ver_oid'], $id)) {
+                                                                       // Lock file name:
+                                                                       $lockFileName = ((((PATH_site . 'typo3temp/swap_locking/') . $table) . ':') . $id) . '.ser';
+                                                                       if (!@is_file($lockFileName)) {
+                                                                               // Write lock-file:
+                                                                               t3lib_div::writeFileToTypo3tempDir($lockFileName, serialize(array(
+                                                                                       'tstamp' => $GLOBALS['EXEC_TIME'],
+                                                                                       'user' => $tcemainObj->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 (!isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']) && $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 = array();
+                                                                               $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'] = intval($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 t3lib_BEfunc::versioningPlaceholderClause()
+                                                                               $swapVersion['t3ver_wsid'] = 0;
+                                                                               if ($swapIntoWS) {
+                                                                                       if ($t3ver_state['swapVersion'] > 0) {
+                                                                                               $swapVersion['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
+                                                                                       } else {
+                                                                                               $swapVersion['t3ver_wsid'] = intval($curVersion['t3ver_wsid']);
+                                                                                       }
+                                                                               }
+                                                                               $swapVersion['t3ver_tstamp'] = $GLOBALS['EXEC_TIME'];
+                                                                               $swapVersion['t3ver_stage'] = 0;
+                                                                               if (!$swapIntoWS) {
+                                                                                       $swapVersion['t3ver_state'] = 0;
+                                                                               }
+                                                                               // Moving element.
+                                                                               if ((int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
+                                                                                       //  && $t3ver_state['swapVersion']==4   // Maybe we don't need this?
+                                                                                       if ($plhRec = t3lib_BEfunc::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'] = intval($plhRec['pid']);
+                                                                                               $curVersion['t3ver_state'] = intval($swapVersion['t3ver_state']);
+                                                                                               $swapVersion['t3ver_state'] = 0;
+                                                                                               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_procBasedOnFieldType($table, $field, $fieldConf['config'], $curVersion, $swapVersion, $tcemainObj);
+                                                                                       }
+                                                                               }
+                                                                               unset($swapVersion['uid']);
+                                                                               // Modify online version to become offline:
+                                                                               unset($curVersion['uid']);
+                                                                               // Set pid for OFFLINE
+                                                                               $curVersion['pid'] = -1;
+                                                                               $curVersion['t3ver_oid'] = intval($id);
+                                                                               $curVersion['t3ver_wsid'] = $swapIntoWS ? intval($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'] = 0;
+                                                                               }
+                                                                               // Registering and swapping MM relations in current and swap records:
+                                                                               $tcemainObj->version_remapMMForVersionSwap($table, $id, $swapWith);
+                                                                               // Generating proper history data to prepare logging
+                                                                               $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $id, $swapVersion);
+                                                                               $tcemainObj->compareFieldArrayWithCurrentAndUnset($table, $swapWith, $curVersion);
+                                                                               // Execute swapping:
+                                                                               $sqlErrors = array();
+                                                                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $swapVersion);
+                                                                               if ($GLOBALS['TYPO3_DB']->sql_error()) {
+                                                                                       $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
+                                                                               } else {
+                                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($swapWith), $curVersion);
+                                                                                       if ($GLOBALS['TYPO3_DB']->sql_error()) {
+                                                                                               $sqlErrors[] = $GLOBALS['TYPO3_DB']->sql_error();
+                                                                                       } else {
+                                                                                               unlink($lockFileName);
+                                                                                       }
+                                                                               }
+                                                                               if (!count($sqlErrors)) {
+                                                                                       // 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!
+                                                                                                       $tcemainObj->deleteEl($table, $movePlhID, TRUE, TRUE);
+                                                                                               } else {
+                                                                                                       // Otherwise update the movePlaceholder:
+                                                                                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($movePlhID), $movePlh);
+                                                                                                       $tcemainObj->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
+                                                                                               $tcemainObj->deleteEl($table, $id, TRUE);
+                                                                                       }
+                                                                                       $tcemainObj->newlog2((((((($swapIntoWS ? 'Swapping' : 'Publishing') . ' successful for table "') . $table) . '" uid ') . $id) . '=>') . $swapWith, $table, $id, $swapVersion['pid']);
+                                                                                       // Update reference index of the live record:
+                                                                                       $tcemainObj->addRemapStackRefIndex($table, $id);
+                                                                                       // Set log entry for live record:
+                                                                                       $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $swapVersion);
+                                                                                       if ($propArr['_ORIG_pid'] == -1) {
+                                                                                               $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
+                                                                                       } else {
+                                                                                               $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
+                                                                                       }
+                                                                                       $theLogId = $tcemainObj->log($table, $id, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], ($table . ':') . $id), $propArr['event_pid']);
+                                                                                       $tcemainObj->setHistory($table, $id, $theLogId);
+                                                                                       // Update reference index of the offline record:
+                                                                                       $tcemainObj->addRemapStackRefIndex($table, $swapWith);
+                                                                                       // Set log entry for offline record:
+                                                                                       $propArr = $tcemainObj->getRecordPropertiesFromRow($table, $curVersion);
+                                                                                       if ($propArr['_ORIG_pid'] == -1) {
+                                                                                               $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.offline_record_updated');
+                                                                                       } else {
+                                                                                               $label = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_tcemain.xml:version_swap.online_record_updated');
+                                                                                       }
+                                                                                       $theLogId = $tcemainObj->log($table, $swapWith, 2, $propArr['pid'], 0, $label, 10, array($propArr['header'], ($table . ':') . $swapWith), $propArr['event_pid']);
+                                                                                       $tcemainObj->setHistory($table, $swapWith, $theLogId);
+                                                                                       // Clear cache:
+                                                                                       $tcemainObj->clear_cache($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!
+                                                                                               $tcemainObj->deleteEl($table, $swapWith, TRUE, TRUE);
+                                                                                       }
+                                                                               } else {
+                                                                                       $tcemainObj->newlog('During Swapping: SQL errors happened: ' . implode('; ', $sqlErrors), 2);
+                                                                               }
+                                                                       } else {
+                                                                               $tcemainObj->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);
+                                                                       }
+                                                               } else {
+                                                                       $tcemainObj->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);
+                                                               }
+                                                       } else {
+                                                               $tcemainObj->newlog(('Workspace #' . $swapVersion['t3ver_wsid']) . ' does not support swapping.', 1);
+                                                       }
+                                               } else {
+                                                       $tcemainObj->newlog('You cannot publish a record you do not have edit and show permissions for', 1);
+                                               }
+                                       } else {
+                                               $tcemainObj->newlog(('Records in workspace #' . $swapVersion['t3ver_wsid']) . ' can only be published when in "Publish" stage.', 1);
+                                       }
+                               } else {
+                                       $tcemainObj->newlog('User could not publish records from workspace #' . $swapVersion['t3ver_wsid'], 1);
+                               }
+                       } else {
+                               $tcemainObj->newlog('Error: Either online or swap version could not be selected!', 2);
+                       }
+               } else {
+                       $tcemainObj->newlog('Error: You cannot swap versions for a record you do not have access to edit!', 1);
+               }
+       }
+
+       /**
+        * Update relations on version/workspace swapping.
+        *
+        * @param string $table: Record Table
+        * @param string $field: Record field
+        * @param array $conf: TCA configuration of current field
+        * @param array $curVersion: Reference to the current (original) record
+        * @param array $swapVersion: Reference to the record (workspace/versionized) to publish in or swap with
+        * @param t3lib_TCEmain $tcemainObj TCEmain object
+        * @return void
+        */
+       protected function version_swap_procBasedOnFieldType($table, $field, array $conf, array &$curVersion, array &$swapVersion, t3lib_TCEmain $tcemainObj) {
+               $inlineType = $tcemainObj->getInlineFieldType($conf);
+               // Process pointer fields on normalized database:
+               if ($inlineType == 'field') {
+                       // Read relations that point to the current record (e.g. live record):
+                       /** @var $dbAnalysisCur t3lib_loadDBGroup */
+                       $dbAnalysisCur = t3lib_div::makeInstance('t3lib_loadDBGroup');
+                       $dbAnalysisCur->setUpdateReferenceIndex(FALSE);
+                       $dbAnalysisCur->start('', $conf['foreign_table'], '', $curVersion['uid'], $table, $conf);
+                       // Read relations that point to the record to be swapped with e.g. draft record):
+                       /** @var $dbAnalysisSwap t3lib_loadDBGroup */
+                       $dbAnalysisSwap = t3lib_div::makeInstance('t3lib_loadDBGroup');
+                       $dbAnalysisSwap->setUpdateReferenceIndex(FALSE);
+                       $dbAnalysisSwap->start('', $conf['foreign_table'], '', $swapVersion['uid'], $table, $conf);
+                       // Update relations for both (workspace/versioning) sites:
+                       if (count($dbAnalysisCur->itemArray)) {
+                               $dbAnalysisCur->writeForeignField($conf, $curVersion['uid'], $swapVersion['uid']);
+                               $tcemainObj->addRemapAction($table, $curVersion['uid'], array($this, 'writeRemappedForeignField'), array($dbAnalysisCur, $conf, $swapVersion['uid']));
+                       }
+                       if (count($dbAnalysisSwap->itemArray)) {
+                               $dbAnalysisSwap->writeForeignField($conf, $swapVersion['uid'], $curVersion['uid']);
+                               $tcemainObj->addRemapAction($table, $curVersion['uid'], array($this, 'writeRemappedForeignField'), array($dbAnalysisSwap, $conf, $curVersion['uid']));
+                       }
+                       $items = array_merge($dbAnalysisCur->itemArray, $dbAnalysisSwap->itemArray);
+                       foreach ($items as $item) {
+                               $tcemainObj->addRemapStackRefIndex($item['table'], $item['id']);
+                       }
+               } elseif ($inlineType == 'list') {
+                       $tempValue = $curVersion[$field];
+                       $curVersion[$field] = $swapVersion[$field];
+                       $swapVersion[$field] = $tempValue;
+               }
+       }
+
+       /**
+        * Writes remapped foreign field (IRRE).
+        *
+        * @param t3lib_loadDBGroup $dbAnalysis Instance that holds the sorting order of child records
+        * @param array $configuration The TCA field configuration
+        * @param integer $parentId The uid of the parent record
+        * @return void
+        */
+       public function writeRemappedForeignField(t3lib_loadDBGroup $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);
+       }
+
+       /**
+        * Release version from this workspace (and into "Live" workspace but as an offline version).
+        *
+        * @param string $table Table name
+        * @param integer $id Record UID
+        * @param boolean $flush If set, will completely delete element
+        * @param t3lib_TCEmain $tcemainObj TCEmain object
+        * @return      void
+        */
+       protected function version_clearWSID($table, $id, $flush = FALSE, t3lib_TCEmain $tcemainObj) {
+               if ($errorCode = $tcemainObj->BE_USER->workspaceCannotEditOfflineVersion($table, $id)) {
+                       $tcemainObj->newlog('Attempt to reset workspace for record failed: ' . $errorCode, 1);
+               } elseif ($tcemainObj->checkRecordUpdateAccess($table, $id)) {
+                       if ($liveRec = t3lib_BEfunc::getLiveVersionOfRecord($table, $id, 'uid,t3ver_state')) {
+                               // Clear workspace ID:
+                               $updateData = array(
+                                       't3ver_wsid' => 0,
+                                       't3ver_tstamp' => $GLOBALS['EXEC_TIME']
+                               );
+                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($id), $updateData);
+                               // Clear workspace ID for live version AND DELETE IT as well because it is a new record!
+                               if ((int) $liveRec['t3ver_state'] == 1 || (int) $liveRec['t3ver_state'] == 2) {
+                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($liveRec['uid']), $updateData);
+                                       // THIS assumes that the record was placeholder ONLY for ONE record (namely $id)
+                                       $tcemainObj->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 = t3lib_BEfunc::getRecord($table, $id);
+                               if ($flush || ((int) $wsRec['t3ver_state'] == 1 || (int) $wsRec['t3ver_state'] == 2)) {
+                                       $tcemainObj->deleteEl($table, $id, TRUE, TRUE);
+                               }
+                               // Remove the move-placeholder if found for live record.
+                               if ((int) $GLOBALS['TCA'][$table]['ctrl']['versioningWS'] >= 2) {
+                                       if ($plhRec = t3lib_BEfunc::getMovePlaceholder($table, $liveRec['uid'], 'uid')) {
+                                               $tcemainObj->deleteEl($table, $plhRec['uid'], TRUE, TRUE);
+                                       }
+                               }
+                       }
+               } else {
+                       $tcemainObj->newlog('Attempt to reset workspace for record failed because you do not have edit access', 1);
+               }
+       }
+
+       /*******************************
+        *****  helper functions  ******
+        *******************************/
+       /**
+        * Copies all records from tables in $copyTablesArray from page with $old_pid to page with $new_pid
+        * Uses raw-copy for the operation (meant for versioning!)
+        *
+        * @param integer $oldPageId Current page id.
+        * @param integer $newPageId New page id
+        * @param array $copyTablesArray Array of tables from which to copy
+        * @param t3lib_TCEmain $tcemainObj TCEmain object
+        * @return void
+        * @see versionizePages()
+        */
+       protected function rawCopyPageContent($oldPageId, $newPageId, array $copyTablesArray, t3lib_TCEmain $tcemainObj) {
+               if ($newPageId) {
+                       foreach ($copyTablesArray as $table) {
+                               // all records under the page is copied.
+                               if (($table && is_array($GLOBALS['TCA'][$table])) && $table !== 'pages') {
+                                       $mres = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, ('pid=' . intval($oldPageId)) . $tcemainObj->deleteClause($table));
+                                       while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($mres)) {
+                                               // Check, if this record has already been copied by a parent record as relation:
+                                               if (!$tcemainObj->copyMappingArray[$table][$row['uid']]) {
+                                                       // Copying each of the underlying records (method RAW)
+                                                       $tcemainObj->copyRecord_raw($table, $row['uid'], $newPageId);
+                                               }
+                                       }
+                                       $GLOBALS['TYPO3_DB']->sql_free_result($mres);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Finds all elements for swapping versions in workspace
+        *
+        * @param string $table Table name of the original element to swap
+        * @param integer $id UID of the original element to swap (online)
+        * @param integer $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 = t3lib_BEfunc::getRecord($table, $offlineId, 't3ver_wsid');
+               $workspaceId = $rec['t3ver_wsid'];
+               $elementData = array();
+               if ($workspaceId != 0) {
+                       // Get page UID for LIVE and workspace
+                       if ($table != 'pages') {
+                               $rec = t3lib_BEfunc::getRecord($table, $id, 'pid');
+                               $pageId = $rec['pid'];
+                               $rec = t3lib_BEfunc::getRecord('pages', $pageId);
+                               t3lib_BEfunc::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') {
+                                       $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('A.uid AS offlineUid, B.uid AS uid', (($table . ' A,') . $table) . ' B', ((((('A.pid=-1 AND B.pid=' . $pageId) . ' AND A.t3ver_wsid=') . $workspaceId) . ' AND B.uid=A.t3ver_oid') . t3lib_BEfunc::deleteClause($table, 'A')) . t3lib_BEfunc::deleteClause($table, 'B'));
+                                       while (FALSE != ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
+                                               $elementData[$table][] = array($row[1], $row[0]);
+                                       }
+                                       $GLOBALS['TYPO3_DB']->sql_free_result($res);
+                               }
+                       }
+                       if ($offlinePageId && $offlinePageId != $pageId) {
+                               $elementData['pages'][] = array($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 integer $workspaceId Workspace ID
+        * @param array $elementList List of found elements. Key is table name, value is array of element UIDs
+        * @return void
+        */
+       public function findPageElementsForVersionStageChange(array $pageIdList, $workspaceId, array &$elementList) {
+               if ($workspaceId != 0) {
+                       // Traversing all tables supporting versioning:
+                       foreach ($GLOBALS['TCA'] as $table => $cfg) {
+                               if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS'] && $table !== 'pages') {
+                                       $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT A.uid', (($table . ' A,') . $table) . ' B', (((((('A.pid=-1' . ' AND A.t3ver_wsid=') . $workspaceId) . ' AND B.pid IN (') . implode(',', $pageIdList)) . ') AND A.t3ver_oid=B.uid') . t3lib_BEfunc::deleteClause($table, 'A')) . t3lib_BEfunc::deleteClause($table, 'B'));
+                                       while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
+                                               $elementList[$table][] = $row[0];
+                                       }
+                                       $GLOBALS['TYPO3_DB']->sql_free_result($res);
+                                       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 integer $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
+        * @return void
+        */
+       public function findPageIdsForVersionStateChange($table, array $idList, $workspaceId, array &$pageIdList, array &$elementList) {
+               if ($workspaceId != 0) {
+                       $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('DISTINCT B.pid', (($table . ' A,') . $table) . ' B', (((((('A.pid=-1' . ' AND A.t3ver_wsid=') . $workspaceId) . ' AND A.uid IN (') . implode(',', $idList)) . ') AND A.t3ver_oid=B.uid') . t3lib_BEfunc::deleteClause($table, 'A')) . t3lib_BEfunc::deleteClause($table, 'B'));
+                       while (FALSE !== ($row = $GLOBALS['TYPO3_DB']->sql_fetch_row($res))) {
+                               $pageIdList[] = $row[0];
+                               // Find ws version
+                               // Note: cannot use t3lib_BEfunc::getRecordWSOL()
+                               // here because it does not accept workspace id!
+                               $rec = t3lib_BEfunc::getRecord('pages', $row[0]);
+                               t3lib_BEfunc::workspaceOL('pages', $rec, $workspaceId);
+                               if ($rec['_ORIG_uid']) {
+                                       $elementList['pages'][$row[0]] = $rec['_ORIG_uid'];
+                               }
+                       }
+                       $GLOBALS['TYPO3_DB']->sql_free_result($res);
+                       // 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
+        * @return      void
+        */
+       public function findRealPageIds(array &$idList) {
+               foreach ($idList as $key => $id) {
+                       $rec = t3lib_BEfunc::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=1
+        * 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 integer $uid Record uid to move (online record)
+        * @param integer $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 integer $wsUid UID of offline version of online record
+        * @param t3lib_TCEmain $tcemainObj TCEmain object
+        * @return void
+        * @see moveRecord()
+        */
+       protected function moveRecord_wsPlaceholders($table, $uid, $destPid, $wsUid, t3lib_TCEmain $tcemainObj) {
+               // 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 = t3lib_BEfunc::getMovePlaceholder($table, abs($destPid), 'uid');
+                       if ($movePlaceHolder !== FALSE) {
+                               $destPid = -$movePlaceHolder['uid'];
+                       }
+               }
+               if ($plh = t3lib_BEfunc::getMovePlaceholder($table, $uid, 'uid')) {
+                       // If already a placeholder exists, move it:
+                       $tcemainObj->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 = array();
+                       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']] = $tcemainObj->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 = $tcemainObj->BE_USER->getPagePermsClause(1);
+                               $access = t3lib_BEfunc::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'] = 'MOVE-TO PLACEHOLDER for #' . $uid;
+                       $newVersion_placeholderFieldArray['t3ver_move_id'] = $uid;
+                       // Setting placeholder state value for temporary record
+                       $newVersion_placeholderFieldArray['t3ver_state'] = 3;
+                       // Setting workspace - only so display of place holders can filter out those from other workspaces.
+                       $newVersion_placeholderFieldArray['t3ver_wsid'] = $tcemainObj->BE_USER->workspace;
+                       $newVersion_placeholderFieldArray[$GLOBALS['TCA'][$table]['ctrl']['label']] = ((('[MOVE-TO PLACEHOLDER for #' . $uid) . ', WS#') . $tcemainObj->BE_USER->workspace) . ']';
+                       // moving localized records requires to keep localization-settings for the placeholder too
+                       if (array_key_exists('languageField', $GLOBALS['TCA'][$table]['ctrl']) && array_key_exists('transOrigPointerField', $GLOBALS['TCA'][$table]['ctrl'])) {
+                               $l10nParentRec = t3lib_BEfunc::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']];
+                               unset($l10nParentRec);
+                       }
+                       // Initially, create at root level.
+                       $newVersion_placeholderFieldArray['pid'] = 0;
+                       $id = 'NEW_MOVE_PLH';
+                       // Saving placeholder as 'original'
+                       $tcemainObj->insertDB($table, $id, $newVersion_placeholderFieldArray, FALSE);
+                       // Move the new placeholder from temporary root-level to location:
+                       $tcemainObj->moveRecord_raw($table, $tcemainObj->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 = array(
+                               't3ver_state' => 4
+                       );
+                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($wsUid), $updateFields);
+               }
+               // Check for the localizations of that element and move them as well
+               $tcemainObj->moveL10nOverlayRecords($table, $uid, $destPid, $originalRecordDestinationPid);
+       }
+
+       /**
+        * Gets all possible child tables that are used on each parent table as field.
+        *
+        * @param string $parentTable Name of the parent table
+        * @param array $possibleInlineChildren Collected possible inline children
+        * @return array
+        */
+       protected function getPossibleInlineChildTablesOfParentTable($parentTable, array $possibleInlineChildren = array()) {
+               t3lib_div::loadTCA($parentTable);
+               foreach ($GLOBALS['TCA'][$parentTable]['columns'] as $parentField => $parentFieldDefinition) {
+                       if (isset($parentFieldDefinition['config']['type'])) {
+                               $parentFieldConfiguration = $parentFieldDefinition['config'];
+                               if ($parentFieldConfiguration['type'] == 'inline' && isset($parentFieldConfiguration['foreign_table'])) {
+                                       if (!in_array($parentFieldConfiguration['foreign_table'], $possibleInlineChildren)) {
+                                               $possibleInlineChildren = $this->getPossibleInlineChildTablesOfParentTable($parentFieldConfiguration['foreign_table'], array_merge($possibleInlineChildren, $parentFieldConfiguration['foreign_table']));
+                                       }
+                               }
+                       }
+               }
+               return $possibleInlineChildren;
+       }
+
+       /**
+        * Gets an instance of the command map helper.
+        *
+        * @param t3lib_TCEmain $tceMain TCEmain object
+        * @param array $commandMap The command map as submitted to t3lib_TCEmain
+        * @return tx_version_tcemain_CommandMap
+        */
+       public function getCommandMap(t3lib_TCEmain $tceMain, array $commandMap) {
+               return t3lib_div::makeInstance('tx_version_tcemain_CommandMap', $this, $tceMain, $commandMap);
+       }
+
+       /**
+        * 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 = array();
+               t3lib_div::loadTCA($table);
+               if ($GLOBALS['TCA'][$table]['columns']) {
+                       foreach ($GLOBALS['TCA'][$table]['columns'] as $field => $configArr) {
+                               if ($configArr['config']['type'] === 'input') {
+                                       $evalCodesArray = t3lib_div::trimExplode(',', $configArr['config']['eval'], 1);
+                                       if (in_array('uniqueInPid', $evalCodesArray) || in_array('unique', $evalCodesArray)) {
+                                               $listArr[] = $field;
+                                       }
+                               }
+                       }
+               }
+               return $listArr;
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Hook/IconUtilityHook.php b/typo3/sysext/version/Classes/Hook/IconUtilityHook.php
new file mode 100644 (file)
index 0000000..27a47a0
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2011 Francois Suter (francois.suter@typo3.org)
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Implements a hook for t3lib_iconworks
+ */
+class tx_version_iconworks {
+
+       /**
+        * Visualizes the deleted status for a versionized record.
+        *
+        * @param string $table Name of the table
+        * @param array $row Record row containing the field values
+        * @param array $status Status to be used for rendering the icon
+        * @return void
+        */
+       public function overrideIconOverlay($table, array $row, array &$status) {
+               if (isset($row['t3ver_state']) && $row['t3ver_state'] == 2) {
+                       $status['deleted'] = TRUE;
+               }
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Hook/PreviewHook.php b/typo3/sysext/version/Classes/Hook/PreviewHook.php
new file mode 100644 (file)
index 0000000..b41661b
--- /dev/null
@@ -0,0 +1,262 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2011 TYPO3 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Hook for checking if the preview mode is activated
+ * preview mode = show a page of a workspace without having to log in
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Version
+ */
+class Tx_Version_Preview implements t3lib_Singleton {
+
+       /**
+        * the GET parameter to be used
+        *
+        * @var string
+        */
+       protected $previewKey = 'ADMCMD_prev';
+
+       /**
+        * instance of the tslib_fe object
+        *
+        * @var tslib_fe
+        */
+       protected $tsfeObj;
+
+       /**
+        * preview configuration
+        *
+        * @var array
+        */
+       protected $previewConfiguration = FALSE;
+
+       /**
+        * hook to check if the preview is activated
+        * right now, this hook is called at the end of "$TSFE->connectToDB"
+        *
+        * @param $params (not needed right now)
+        * @param $pObj the instance of the tslib_fe object
+        * @return void
+        */
+       public function checkForPreview($params, &$pObj) {
+               $this->tsfeObj = $pObj;
+               $this->previewConfiguration = $this->getPreviewConfiguration();
+               if (is_array($this->previewConfiguration)) {
+                       // In case of a keyword-authenticated preview,
+                       // re-initialize the TSFE object:
+                       // because the GET variables are taken from the preview
+                       // configuration
+                       $GLOBALS['TSFE'] = ($this->tsfeObj = t3lib_div::makeInstance('tslib_fe', $GLOBALS['TYPO3_CONF_VARS'], t3lib_div::_GP('id'), t3lib_div::_GP('type'), t3lib_div::_GP('no_cache'), t3lib_div::_GP('cHash'), t3lib_div::_GP('jumpurl'), t3lib_div::_GP('MP'), t3lib_div::_GP('RDCT')));
+                       // Configuration after initialization of TSFE object.
+                       // Basically this unsets the BE cookie if any and forces
+                       // the BE user set according to the preview configuration.
+                       // @previouslyknownas TSFE->ADMCMD_preview_postInit
+                       // Clear cookies:
+                       unset($_COOKIE['be_typo_user']);
+               }
+       }
+
+       /**
+        * hook after the regular BE user has been initialized
+        * if there is no BE user login, but a preview configuration
+        * the BE user of the preview configuration gets initialized
+        *
+        * @param $params holding the BE_USER object
+        * @param $pObj the instance of the tslib_fe object
+        * @return void
+        */
+       public function initializePreviewUser(&$params, &$pObj) {
+               if (((is_null($params['BE_USER']) || $params['BE_USER'] === FALSE) && $this->previewConfiguration !== FALSE) && $this->previewConfiguration['BEUSER_uid'] > 0) {
+                       // New backend user object
+                       $BE_USER = t3lib_div::makeInstance('t3lib_tsfeBeUserAuth');
+                       $BE_USER->userTS_dontGetCached = 1;
+                       $BE_USER->OS = TYPO3_OS;
+                       $BE_USER->setBeUserByUid($this->previewConfiguration['BEUSER_uid']);
+                       $BE_USER->unpack_uc('');
+                       if ($BE_USER->user['uid']) {
+                               $BE_USER->fetchGroupData();
+                               $pObj->beUserLogin = 1;
+                       } else {
+                               $BE_USER = NULL;
+                               $pObj->beUserLogin = 0;
+                               $_SESSION['TYPO3-TT-start'] = FALSE;
+                       }
+                       $params['BE_USER'] = $BE_USER;
+               }
+               // @previouslyknownas $TSFE->workspacePreviewInit()
+               // if there is a valid BE user, and the full workspace should be
+               // previewed, the workspacePreview option shouldbe set
+               $workspaceUid = $this->previewConfiguration['fullWorkspace'];
+               if (($pObj->beUserLogin && is_object($params['BE_USER'])) && t3lib_utility_Math::canBeInterpretedAsInteger($workspaceUid)) {
+                       if ($workspaceUid == 0 || $workspaceUid >= -1 && $params['BE_USER']->checkWorkspace($workspaceUid)) {
+                               // Check Access to workspace. Live (0) is OK to preview for all.
+                               $pObj->workspacePreview = intval($workspaceUid);
+                       } else {
+                               // No preview, will default to "Live" at the moment
+                               $pObj->workspacePreview = -99;
+                       }
+               }
+       }
+
+       /**
+        * Looking for a ADMCMD_prev code, looks it up if found and returns configuration data.
+        * Background: From the backend a request to the frontend to show a page, possibly with workspace preview can be "recorded" and associated with a keyword. When the frontend is requested with this keyword the associated request parameters are restored from the database AND the backend user is loaded - only for that request.
+        * The main point is that a special URL valid for a limited time, eg. http://localhost/typo3site/index.php?ADMCMD_prev=035d9bf938bd23cb657735f68a8cedbf will open up for a preview that doesn't require login. Thus it's useful for sending in an email to someone without backend account.
+        * This can also be used to generate previews of hidden pages, start/endtimes, usergroups and those other settings from the Admin Panel - just not implemented yet.
+        *
+        * @return      array           Preview configuration array from sys_preview record.
+        * @see t3lib_BEfunc::compilePreviewKeyword()
+        * @previouslyknownas TSFE->ADMCMD_preview
+        */
+       public function getPreviewConfiguration() {
+               $inputCode = $this->getPreviewInputCode();
+               // If inputcode is available, look up the settings
+               if ($inputCode) {
+                       // "log out"
+                       if ($inputCode == 'LOGOUT') {
+                               setcookie($this->previewKey, '', 0, t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));
+                               if ($this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate']) {
+                                       $templateFile = PATH_site . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate'];
+                                       if (@is_file($templateFile)) {
+                                               $message = t3lib_div::getUrl(PATH_site . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate']);
+                                       } else {
+                                               $message = ('<strong>ERROR!</strong><br>Template File "' . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate']) . '" configured with $TYPO3_CONF_VARS["FE"]["workspacePreviewLogoutTemplate"] not found. Please contact webmaster about this problem.';
+                                       }
+                               } else {
+                                       $message = 'You logged out from Workspace preview mode. Click this link to <a href="%1$s">go back to the website</a>';
+                               }
+                               $returnUrl = t3lib_div::sanitizeLocalUrl(t3lib_div::_GET('returnUrl'));
+                               die(sprintf($message, htmlspecialchars(preg_replace(('/\\&?' . $this->previewKey) . '=[[:alnum:]]+/', '', $returnUrl))));
+                       }
+                       // Look for keyword configuration record:
+                       $previewData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*', 'sys_preview', (('keyword=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($inputCode, 'sys_preview')) . ' AND endtime>') . $GLOBALS['EXEC_TIME']);
+                       // Get: Backend login status, Frontend login status
+                       // - Make sure to remove fe/be cookies (temporarily);
+                       // BE already done in ADMCMD_preview_postInit()
+                       if (is_array($previewData)) {
+                               if (!count(t3lib_div::_POST())) {
+                                       // Unserialize configuration:
+                                       $previewConfig = unserialize($previewData['config']);
+                                       // For full workspace preview we only ADD a get variable
+                                       // to set the preview of the workspace - so all other Get
+                                       // vars are accepted. Hope this is not a security problem.
+                                       // Still posting is not allowed and even if a backend user
+                                       // get initialized it shouldn't lead to situations where
+                                       // users can use those credentials.
+                                       if ($previewConfig['fullWorkspace']) {
+                                               // Set the workspace preview value:
+                                               t3lib_div::_GETset($previewConfig['fullWorkspace'], 'ADMCMD_previewWS');
+                                               // If ADMCMD_prev is set the $inputCode value cannot come
+                                               // from a cookie and we set that cookie here. Next time it will
+                                               // be found from the cookie if ADMCMD_prev is not set again...
+                                               if (t3lib_div::_GP($this->previewKey)) {
+                                                       // Lifetime is 1 hour, does it matter much?
+                                                       // Requires the user to click the link from their email again if it expires.
+                                                       SetCookie($this->previewKey, t3lib_div::_GP($this->previewKey), 0, t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));
+                                               }
+                                               return $previewConfig;
+                                       } elseif ((((t3lib_div::getIndpEnv('TYPO3_SITE_URL') . 'index.php?') . $this->previewKey) . '=') . $inputCode === t3lib_div::getIndpEnv('TYPO3_REQUEST_URL')) {
+                                               // Set GET variables
+                                               $GET_VARS = '';
+                                               parse_str($previewConfig['getVars'], $GET_VARS);
+                                               t3lib_div::_GETset($GET_VARS);
+                                               // Return preview keyword configuration
+                                               return $previewConfig;
+                                       } else {
+                                               // This check is to prevent people from setting additional
+                                               // GET vars via realurl or other URL path based ways of passing parameters.
+                                               throw new Exception(htmlspecialchars(((((('Request URL did not match "' . t3lib_div::getIndpEnv('TYPO3_SITE_URL')) . 'index.php?') . $this->previewKey) . '=') . $inputCode) . '"', 1294585190));
+                                       }
+                               } else {
+                                       throw new Exception('POST requests are incompatible with keyword preview.', 1294585191);
+                               }
+                       } else {
+                               throw new Exception('ADMCMD command could not be executed! (No keyword configuration found)', 1294585192);
+                       }
+               }
+               return FALSE;
+       }
+
+       /**
+        * returns the input code value from the admin command variable
+        *
+        * @param input code
+        */
+       protected function getPreviewInputCode() {
+               $inputCode = t3lib_div::_GP($this->previewKey);
+               // If no inputcode and a cookie is set, load input code from cookie:
+               if (!$inputCode && $_COOKIE[$this->previewKey]) {
+                       $inputCode = $_COOKIE[$this->previewKey];
+               }
+               return $inputCode;
+       }
+
+       /**
+        * Set preview keyword, eg:
+        * $previewUrl = t3lib_div::getIndpEnv('TYPO3_SITE_URL').'index.php?ADMCMD_prev='.$this->compilePreviewKeyword('id='.$pageId.'&L='.$language.'&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS='.$this->workspace, $GLOBALS['BE_USER']->user['uid'], 120);
+        *
+        * todo for sys_preview:
+        * - Add a comment which can be shown to previewer in frontend in some way (plus maybe ability to write back, take other action?)
+        * - Add possibility for the preview keyword to work in the backend as well: So it becomes a quick way to a certain action of sorts?
+        *
+        * @param       string          Get variables to preview, eg. 'id=1150&L=0&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS=8'
+        * @param       string          32 byte MD5 hash keyword for the URL: "?ADMCMD_prev=[keyword]
+        * @param       integer         Time-To-Live for keyword
+        * @param       integer         Which workspace to preview. Workspace UID, -1 or >0. If set, the getVars is ignored in the frontend, so that string can be empty
+        * @return      string          Returns keyword to use in URL for ADMCMD_prev=
+        * @formallyknownas t3lib_BEfunc::compilePreviewKeyword
+        */
+       public function compilePreviewKeyword($getVarsStr, $backendUserUid, $ttl = 172800, $fullWorkspace = NULL) {
+               $fieldData = array(
+                       'keyword' => md5(uniqid(microtime())),
+                       'tstamp' => $GLOBALS['EXEC_TIME'],
+                       'endtime' => $GLOBALS['EXEC_TIME'] + $ttl,
+                       'config' => serialize(array(
+                               'fullWorkspace' => $fullWorkspace,
+                               'getVars' => $getVarsStr,
+                               'BEUSER_uid' => $backendUserUid
+                       ))
+               );
+               $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_preview', $fieldData);
+               return $fieldData['keyword'];
+       }
+
+       /**
+        * easy function to just return the number of hours
+        * a preview link is valid, based on the TSconfig value "options.workspaces.previewLinkTTLHours"
+        * by default, it's 48hs
+        *
+        * @return integer      the hours as a number
+        */
+       public function getPreviewLinkLifetime() {
+               $ttlHours = intval($GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours'));
+               return $ttlHours ? $ttlHours : 24 * 2;
+       }
+
+}
+
+?>
\ No newline at end of file
index c974a94..0b7780c 100644 (file)
@@ -1,293 +1,8 @@
 <?php
-/***************************************************************
- *  Copyright notice
- *
- *  (c) 2011 TYPO3 Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
- *  All rights reserved
- *
- *  This script is part of the TYPO3 project. The TYPO3 project 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.
- *
- *  The GNU General Public License can be found at
- *  http://www.gnu.org/copyleft/gpl.html.
- *  A copy is found in the textfile GPL.txt and important notices to the license
- *  from the author is found in LICENSE.txt distributed with these scripts.
- *
- *
- *  This script 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.
- *
- *  This copyright notice MUST APPEAR in all copies of the script!
- ***************************************************************/
-
-/**
- * Hook for checking if the preview mode is activated
- *    preview mode = show a page of a workspace without having to log in
- *
- * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
- * @package Version
+/*
+ * @deprecated since 6.0, the classname Tx_Version_Preview and this file is obsolete
+ * and will be removed by 7.0. The class was renamed and is now located at:
+ * typo3/sysext/version/Classes/Hook/PreviewHook.php
  */
-class Tx_Version_Preview implements t3lib_Singleton {
-
-       /**
-        * the GET parameter to be used
-        * @var string
-        */
-       protected $previewKey = 'ADMCMD_prev';
-
-       /**
-        * instance of the tslib_fe object
-        * @var tslib_fe
-        */
-       protected $tsfeObj;
-
-       /**
-        * preview configuration
-        * @var array
-        */
-       protected $previewConfiguration = FALSE;
-
-       /**
-        * hook to check if the preview is activated
-        * right now, this hook is called at the end of "$TSFE->connectToDB"
-        *
-        * @param $params (not needed right now)
-        * @param $pObj the instance of the tslib_fe object
-        * @return void
-        */
-       public function checkForPreview($params, &$pObj) {
-               $this->tsfeObj = $pObj;
-               $this->previewConfiguration = $this->getPreviewConfiguration();
-
-               if (is_array($this->previewConfiguration)) {
-                               // In case of a keyword-authenticated preview,
-                               // re-initialize the TSFE object:
-                               // because the GET variables are taken from the preview
-                               // configuration
-                       $GLOBALS['TSFE'] = $this->tsfeObj = t3lib_div::makeInstance('tslib_fe',
-                               $GLOBALS['TYPO3_CONF_VARS'],
-                               t3lib_div::_GP('id'),
-                               t3lib_div::_GP('type'),
-                               t3lib_div::_GP('no_cache'),
-                               t3lib_div::_GP('cHash'),
-                               t3lib_div::_GP('jumpurl'),
-                               t3lib_div::_GP('MP'),
-                               t3lib_div::_GP('RDCT')
-                       );
-
-                               // Configuration after initialization of TSFE object.
-                               // Basically this unsets the BE cookie if any and forces
-                               // the BE user set according to the preview configuration.
-                               // @previouslyknownas TSFE->ADMCMD_preview_postInit
-                               // Clear cookies:
-                       unset($_COOKIE['be_typo_user']);
-               }
-       }
-
-       /**
-        * hook after the regular BE user has been initialized
-        * if there is no BE user login, but a preview configuration
-        * the BE user of the preview configuration gets initialized
-        *
-        * @param $params holding the BE_USER object
-        * @param $pObj the instance of the tslib_fe object
-        * @return void
-        */
-       public function initializePreviewUser(&$params, &$pObj) {
-               if ((is_null($params['BE_USER']) || ($params['BE_USER'] === FALSE)) && $this->previewConfiguration !== FALSE && $this->previewConfiguration['BEUSER_uid'] > 0) {
-
-                               // New backend user object
-                       $BE_USER = t3lib_div::makeInstance('t3lib_tsfeBeUserAuth');
-                       $BE_USER->userTS_dontGetCached = 1;
-                       $BE_USER->OS = TYPO3_OS;
-                       $BE_USER->setBeUserByUid($this->previewConfiguration['BEUSER_uid']);
-                       $BE_USER->unpack_uc('');
-                       if ($BE_USER->user['uid']) {
-                               $BE_USER->fetchGroupData();
-                               $pObj->beUserLogin = 1;
-                       } else {
-                               $BE_USER = NULL;
-                               $pObj->beUserLogin = 0;
-                               $_SESSION['TYPO3-TT-start'] = FALSE;
-                       }
-                       $params['BE_USER'] = $BE_USER;
-               }
-
-               // @previouslyknownas $TSFE->workspacePreviewInit()
-               // if there is a valid BE user, and the full workspace should be
-               // previewed, the workspacePreview option shouldbe set
-               $workspaceUid = $this->previewConfiguration['fullWorkspace'];
-               if ($pObj->beUserLogin && is_object($params['BE_USER']) && t3lib_utility_Math::canBeInterpretedAsInteger($workspaceUid)) {
-                       if ($workspaceUid == 0 || ($workspaceUid >= -1 && $params['BE_USER']->checkWorkspace($workspaceUid))) {
-                                       // Check Access to workspace. Live (0) is OK to preview for all.
-                               $pObj->workspacePreview = intval($workspaceUid);
-                       } else {
-                                       // No preview, will default to "Live" at the moment
-                               $pObj->workspacePreview = -99;
-                       }
-               }
-       }
-
-
-       /**
-        * Looking for a ADMCMD_prev code, looks it up if found and returns configuration data.
-        * Background: From the backend a request to the frontend to show a page, possibly with workspace preview can be "recorded" and associated with a keyword. When the frontend is requested with this keyword the associated request parameters are restored from the database AND the backend user is loaded - only for that request.
-        * The main point is that a special URL valid for a limited time, eg. http://localhost/typo3site/index.php?ADMCMD_prev=035d9bf938bd23cb657735f68a8cedbf will open up for a preview that doesn't require login. Thus it's useful for sending in an email to someone without backend account.
-        * This can also be used to generate previews of hidden pages, start/endtimes, usergroups and those other settings from the Admin Panel - just not implemented yet.
-        *
-        * @return      array           Preview configuration array from sys_preview record.
-        * @see t3lib_BEfunc::compilePreviewKeyword()
-        * @previouslyknownas TSFE->ADMCMD_preview
-        */
-       public function getPreviewConfiguration() {
-               $inputCode = $this->getPreviewInputCode();
-
-                       // If inputcode is available, look up the settings
-               if ($inputCode) {
-
-                               // "log out"
-                       if ($inputCode == 'LOGOUT') {
-                               setcookie($this->previewKey, '', 0, t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));
-                               if ($this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate'])    {
-                                       $templateFile = PATH_site . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate'];
-                                       if (@is_file($templateFile)) {
-                                               $message = t3lib_div::getUrl(PATH_site.$this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate']);
-                                       } else {
-                                               $message = '<strong>ERROR!</strong><br>Template File "' . $this->tsfeObj->TYPO3_CONF_VARS['FE']['workspacePreviewLogoutTemplate'] . '" configured with $TYPO3_CONF_VARS["FE"]["workspacePreviewLogoutTemplate"] not found. Please contact webmaster about this problem.';
-                                       }
-                               } else {
-                                       $message = 'You logged out from Workspace preview mode. Click this link to <a href="%1$s">go back to the website</a>';
-                               }
-
-                               $returnUrl = t3lib_div::sanitizeLocalUrl(t3lib_div::_GET('returnUrl'));
-                               die(sprintf($message,
-                                       htmlspecialchars(preg_replace('/\&?' . $this->previewKey . '=[[:alnum:]]+/', '', $returnUrl))
-                                       ));
-                       }
-
-                               // Look for keyword configuration record:
-                       $previewData = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow(
-                               '*',
-                               'sys_preview',
-                               'keyword=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($inputCode, 'sys_preview')
-                                       . ' AND endtime>' . $GLOBALS['EXEC_TIME']
-                       );
-
-                               // Get: Backend login status, Frontend login status
-                               // - Make sure to remove fe/be cookies (temporarily);
-                               // BE already done in ADMCMD_preview_postInit()
-                       if (is_array($previewData)) {
-                               if (!count(t3lib_div::_POST())) {
-                                               // Unserialize configuration:
-                                       $previewConfig = unserialize($previewData['config']);
-
-                                               // For full workspace preview we only ADD a get variable
-                                               // to set the preview of the workspace - so all other Get
-                                               // vars are accepted. Hope this is not a security problem.
-                                               // Still posting is not allowed and even if a backend user
-                                               // get initialized it shouldn't lead to situations where
-                                               // users can use those credentials.
-                                       if ($previewConfig['fullWorkspace']) {
-
-                                                       // Set the workspace preview value:
-                                               t3lib_div::_GETset($previewConfig['fullWorkspace'], 'ADMCMD_previewWS');
-
-                                                       // If ADMCMD_prev is set the $inputCode value cannot come
-                                                       // from a cookie and we set that cookie here. Next time it will
-                                                       // be found from the cookie if ADMCMD_prev is not set again...
-                                               if (t3lib_div::_GP($this->previewKey)) {
-                                                               // Lifetime is 1 hour, does it matter much?
-                                                               // Requires the user to click the link from their email again if it expires.
-                                                       SetCookie($this->previewKey, t3lib_div::_GP($this->previewKey), 0, t3lib_div::getIndpEnv('TYPO3_SITE_PATH'));
-                                               }
-                                               return $previewConfig;
-                                       } elseif (t3lib_div::getIndpEnv('TYPO3_SITE_URL') . 'index.php?' . $this->previewKey . '=' . $inputCode === t3lib_div::getIndpEnv('TYPO3_REQUEST_URL')) {
-
-                                                       // Set GET variables
-                                               $GET_VARS = '';
-                                               parse_str($previewConfig['getVars'], $GET_VARS);
-                                               t3lib_div::_GETset($GET_VARS);
-
-                                                       // Return preview keyword configuration
-                                               return $previewConfig;
-                                       } else {
-                                                       // This check is to prevent people from setting additional
-                                                       // GET vars via realurl or other URL path based ways of passing parameters.
-                                               throw new Exception(htmlspecialchars('Request URL did not match "' . t3lib_div::getIndpEnv('TYPO3_SITE_URL') . 'index.php?' . $this->previewKey . '=' . $inputCode . '"', 1294585190));
-                                       }
-                               } else {
-                                       throw new Exception('POST requests are incompatible with keyword preview.', 1294585191);
-                               }
-                       } else {
-                               throw new Exception('ADMCMD command could not be executed! (No keyword configuration found)', 1294585192);
-                       }
-               }
-               return FALSE;
-       }
-
-       /**
-        * returns the input code value from the admin command variable
-        *
-        * @param "input code"
-        */
-       protected function getPreviewInputCode() {
-               $inputCode = t3lib_div::_GP($this->previewKey);
-
-                       // If no inputcode and a cookie is set, load input code from cookie:
-               if (!$inputCode && $_COOKIE[$this->previewKey]) {
-                       $inputCode = $_COOKIE[$this->previewKey];
-               }
-
-               return $inputCode;
-       }
-
-
-       /**
-        * Set preview keyword, eg:
-        *       $previewUrl = t3lib_div::getIndpEnv('TYPO3_SITE_URL').'index.php?ADMCMD_prev='.$this->compilePreviewKeyword('id='.$pageId.'&L='.$language.'&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS='.$this->workspace, $GLOBALS['BE_USER']->user['uid'], 120);
-        *
-        * todo for sys_preview:
-        * - Add a comment which can be shown to previewer in frontend in some way (plus maybe ability to write back, take other action?)
-        * - Add possibility for the preview keyword to work in the backend as well: So it becomes a quick way to a certain action of sorts?
-        *
-        * @param       string          Get variables to preview, eg. 'id=1150&L=0&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS=8'
-        * @param       string          32 byte MD5 hash keyword for the URL: "?ADMCMD_prev=[keyword]"
-        * @param       integer         Time-To-Live for keyword
-        * @param       integer         Which workspace to preview. Workspace UID, -1 or >0. If set, the getVars is ignored in the frontend, so that string can be empty
-        * @return      string          Returns keyword to use in URL for ADMCMD_prev=
-        * @formallyknownas t3lib_BEfunc::compilePreviewKeyword
-        */
-       public function compilePreviewKeyword($getVarsStr, $backendUserUid, $ttl = 172800, $fullWorkspace = NULL) {
-               $fieldData = array(
-                       'keyword'  => md5(uniqid(microtime())),
-                       'tstamp'   => $GLOBALS['EXEC_TIME'],
-                       'endtime'  => $GLOBALS['EXEC_TIME'] + $ttl,
-                       'config'   => serialize(array(
-                               'fullWorkspace' => $fullWorkspace,
-                               'getVars'       => $getVarsStr,
-                               'BEUSER_uid'    => $backendUserUid
-                       ))
-               );
-
-               $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_preview', $fieldData);
-               return $fieldData['keyword'];
-       }
-
-       /**
-        * easy function to just return the number of hours
-        * a preview link is valid, based on the TSconfig value "options.workspaces.previewLinkTTLHours"
-        * by default, it's 48hs
-        * @return integer      the hours as a number
-        */
-       public function getPreviewLinkLifetime() {
-               $ttlHours = intval($GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours'));
-               return ($ttlHours ? $ttlHours : 24*2);
-       }
-
-}
+require_once t3lib_extMgm::extPath('version') . 'Classes/Hook/PreviewHook.php';
 ?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Task/AutoPublishTask.php b/typo3/sysext/version/Classes/Task/AutoPublishTask.php
new file mode 100644 (file)
index 0000000..1401d8f
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010-2011 François Suter <francois@typo3.org>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * This class provides a wrapper around the autopublication
+ * mechanism of workspaces, as a Scheduler task
+ *
+ * @author             François Suter <francois@typo3.org>
+ * @package            TYPO3
+ * @subpackage         tx_version
+ */
+class tx_version_tasks_AutoPublish extends tx_scheduler_Task {
+
+       /**
+        * Method executed from the Scheduler.
+        * Call on the workspace logic to publish workspaces whose publication date
+        * is in the past
+        *
+        * @return      void
+        */
+       public function execute() {
+               // Load the workspace library class and instatiate it
+               require_once t3lib_extMgm::extPath('version') . 'ws/class.wslib.php';
+               $autopubObj = t3lib_div::makeInstance('wslib');
+               // Publish the workspaces that need to be
+               $autopubObj->autoPublishWorkspaces();
+               // There's no feedback from the publishing process,
+               // so there can't be any failure.
+               // TODO: This could certainly be improved.
+               return TRUE;
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/Utility/WorkspacesUtility.php b/typo3/sysext/version/Classes/Utility/WorkspacesUtility.php
new file mode 100644 (file)
index 0000000..19cea72
--- /dev/null
@@ -0,0 +1,147 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2005-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Library with Workspace related functionality
+ *
+ * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
+ * @package TYPO3
+ * @subpackage core
+ */
+class wslib {
+
+       /**
+        * Building tcemain CMD-array for swapping all versions in a workspace.
+        *
+        * @param       integer         Real workspace ID, cannot be ONLINE (zero).
+        * @param       boolean         If set, then the currently online versions are swapped into the workspace in exchange for the offline versions. Otherwise the workspace is emptied.
+        * @param       [type]          $pageId: ...
+        * @return      array           Command array for tcemain
+        * @todo Define visibility
+        */
+       public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0) {
+               $wsid = intval($wsid);
+               $cmd = array();
+               if ($wsid >= -1 && $wsid !== 0) {
+                       // Define stage to select:
+                       $stage = -99;
+                       if ($wsid > 0) {
+                               $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $wsid);
+                               if ($workspaceRec['publish_access'] & 1) {
+                                       $stage = 10;
+                               }
+                       }
+                       // Select all versions to swap:
+                       $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, $pageId ? $pageId : -1);
+                       // Traverse the selection to build CMD array:
+                       foreach ($versions as $table => $records) {
+                               foreach ($records as $rec) {
+                                       // Build the cmd Array:
+                                       $cmd[$table][$rec['t3ver_oid']]['version'] = array(
+                                               'action' => 'swap',
+                                               'swapWith' => $rec['uid'],
+                                               'swapIntoWS' => $doSwap ? 1 : 0
+                                       );
+                               }
+                       }
+               }
+               return $cmd;
+       }
+
+       /**
+        * Select all records from workspace pending for publishing
+        * Used from backend to display workspace overview
+        * User for auto-publishing for selecting versions for publication
+        *
+        * @param       integer         Workspace ID. If -99, will select ALL versions from ANY workspace. If -98 will select all but ONLINE. >=-1 will select from the actual workspace
+        * @param       integer         Lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple), anything else selects all.
+        * @param       integer         Stage filter: -99 means no filtering, otherwise it will be used to select only elements with that stage. For publishing, that would be "10
+        * @param       integer         Page id: Live page for which to find versions in workspace!
+        * @return      array           Array of all records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid and t3ver_oid fields. The REAL pid of the online record is found as "realpid
+        * @todo Define visibility
+        */
+       public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1) {
+               $wsid = intval($wsid);
+               $filter = intval($filter);
+               $output = array();
+               // Traversing all tables supporting versioning:
+               foreach ($GLOBALS['TCA'] as $table => $cfg) {
+                       if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                               // Select all records from this table in the database from the workspace
+                               // This joins the online version with the offline version as tables A and B
+                               $recs = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('A.uid, A.t3ver_oid, B.pid AS realpid', (($table . ' A,') . $table) . ' B', ((((((('A.pid=-1' . ($pageId != -1 ? ($table === 'pages' ? ' AND B.uid=' . intval($pageId) : ' AND B.pid=' . intval($pageId)) : '')) . ($wsid > -98 ? ' AND A.t3ver_wsid=' . $wsid : ($wsid === -98 ? ' AND A.t3ver_wsid!=0' : ''))) . ($filter === 1 ? ' AND A.t3ver_count=0' : ($filter === 2 ? ' AND A.t3ver_count>0' : ''))) . ($stage != -99 ? ' AND A.t3ver_stage=' . intval($stage) : '')) . ' AND B.pid>=0') . ' AND A.t3ver_oid=B.uid') . t3lib_BEfunc::deleteClause($table, 'A')) . t3lib_BEfunc::deleteClause($table, 'B'), '', 'B.uid');
+                               if (count($recs)) {
+                                       $output[$table] = $recs;
+                               }
+                       }
+               }
+               return $output;
+       }
+
+       /****************************
+        *
+        * Scheduler methods
+        *
+        ****************************/
+       /**
+        * This method is called by the Scheduler task that triggers
+        * the autopublication process
+        * It searches for workspaces whose publication date is in the past
+        * and publishes them
+        *
+        * @return      void
+        * @todo Define visibility
+        */
+       public function autoPublishWorkspaces() {
+               // Temporarily set admin rights
+               // FIXME: once workspaces are cleaned up a better solution should be implemented
+               $currentAdminStatus = $GLOBALS['BE_USER']->user['admin'];
+               $GLOBALS['BE_USER']->user['admin'] = 1;
+               // Select all workspaces that needs to be published / unpublished:
+               $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,swap_modes,publish_time,unpublish_time', 'sys_workspace', (((('pid=0
+                               AND
+                               ((publish_time!=0 AND publish_time<=' . intval($GLOBALS['EXEC_TIME'])) . ')
+                               OR (publish_time=0 AND unpublish_time!=0 AND unpublish_time<=') . intval($GLOBALS['EXEC_TIME'])) . '))') . t3lib_BEfunc::deleteClause('sys_workspace'));
+               foreach ($workspaces as $rec) {
+                       // First, clear start/end time so it doesn't get select once again:
+                       $fieldArray = $rec['publish_time'] != 0 ? array('publish_time' => 0) : array('unpublish_time' => 0);
+                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_workspace', 'uid=' . intval($rec['uid']), $fieldArray);
+                       // Get CMD array:
+                       $cmd = $this->getCmdArrayForPublishWS($rec['uid'], $rec['swap_modes'] == 1);
+                       // $rec['swap_modes']==1 means that auto-publishing will swap versions, not just publish and empty the workspace.
+                       // Execute CMD array:
+                       $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+                       $tce->stripslashes_values = 0;
+                       $tce->start(array(), $cmd);
+                       $tce->process_cmdmap();
+               }
+               // Restore admin status
+               $GLOBALS['BE_USER']->user['admin'] = $currentAdminStatus;
+       }
+
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/version/Classes/View/VersionView.php b/typo3/sysext/version/Classes/View/VersionView.php
new file mode 100644 (file)
index 0000000..649d241
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *  (c) 2010-2011 Benjamin Mack (benni@typo3.org)
+ *
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project 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.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script 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.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Contains some parts for staging, versioning and workspaces
+ * to interact with the TYPO3 Core Engine
+ */
+class tx_version_gui {
+
+       /**
+        * Creates the version selector for the page id inputted.
+        * Moved out of the core file typo3/template.php
+        *
+        * @param       integer         Page id to create selector for.
+        * @param       boolean         If set, there will be no button for swapping page.
+        * @return      void
+        */
+       public function getVersionSelector($id, $noAction = FALSE) {
+               if ($id <= 0) {
+                       return;
+               }
+               if ($GLOBALS['BE_USER']->workspace == 0) {
+                       // Get Current page record:
+                       $curPage = t3lib_BEfunc::getRecord('pages', $id);
+                       // If the selected page is not online, find the right ID
+                       $onlineId = $curPage['pid'] == -1 ? $curPage['t3ver_oid'] : $id;
+                       // Select all versions of online version:
+                       $versions = t3lib_BEfunc::selectVersionsOfRecord('pages', $onlineId, 'uid,pid,t3ver_label,t3ver_oid,t3ver_wsid,t3ver_id');
+                       // If more than one was found...:
+                       if (count($versions) > 1) {
+                               $selectorLabel = ('<strong>' . $GLOBALS['LANG']->sL('LLL:EXT:version/locallang.xml:versionSelect.label', TRUE)) . '</strong>';
+                               // Create selector box entries:
+                               $opt = array();
+                               foreach ($versions as $vRow) {
+                                       if ($vRow['uid'] == $onlineId) {
+                                               // Live version
+                                               $label = ('[' . $GLOBALS['LANG']->sL('LLL:EXT:version/locallang.xml:versionSelect.live', TRUE)) . ']';
+                                       } else {
+                                               $label = ((((($vRow['t3ver_label'] . ' (') . $GLOBALS['LANG']->sL('LLL:EXT:version/locallang.xml:versionId', TRUE)) . ' ') . $vRow['t3ver_id']) . ($vRow['t3ver_wsid'] != 0 ? ((' ' . $GLOBALS['LANG']->sL('LLL:EXT:version/locallang.xml:workspaceId', TRUE)) . ' ') . $vRow['t3ver_wsid'] : '')) . ')';
+                                       }
+                                       $opt[] = ((((('<option value="' . htmlspecialchars(t3lib_div::linkThisScript(array('id' => $vRow['uid'])))) . '"') . ($id == $vRow['uid'] ? ' selected="selected"' : '')) . '>') . htmlspecialchars($label)) . '</option>';
+                               }
+                               // Add management link:
+                               $management = ((('<input type="button" value="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:ver.mgm', TRUE)) . '" onclick="window.location.href=\'') . htmlspecialchars(((($GLOBALS['BACK_PATH'] . t3lib_extMgm::extRelPath('version')) . 'cm1/index.php?table=pages&uid=') . $onlineId))) . '\';" />';
+                               // Create onchange handler:
+                               $onChange = 'window.location.href=this.options[this.selectedIndex].value;';
+                               // Controls:
+                               if ($id == $onlineId) {
+                                       $controls .= ((('<img' . t3lib_iconWorks::skinImg($GLOBALS['BACK_PATH'], 'gfx/blinkarrow_left.gif', 'width="5" height="9"')) . ' class="absmiddle" alt="" /> <strong>') . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:ver.online', TRUE)) . '</strong>';
+                               } elseif (!$noAction) {
+                                       $controls .= ((((('<a href="' . $GLOBALS['TBE_TEMPLATE']->issueCommand((((((('&cmd[pages][' . $onlineId) . '][version][swapWith]=') . $id) . '&cmd[pages][') . $onlineId) . '][version][action]=swap'), t3lib_div::linkThisScript(array('id' => $onlineId)))) . '" class="nobr">') . t3lib_iconWorks::getSpriteIcon('actions-version-swap-version', array(
+                                               'title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:ver.swapPage', TRUE),
+                                               'style' => 'margin-left:5px;vertical-align:bottom;'
+                                       ))) . '<strong>') . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:ver.swap', TRUE)) . '</strong></a>';
+                               }
+                               // Write out HTML code:
+                               return ((((((((('
+                                       <!--
+                                               Version selector:
+                                       -->
+                                       <table border="0" cellpadding="0" cellspacing="0" id="typo3-versionSelector">
+                                               <tr>
+                                                       <td>' . $selectorLabel) . '</td>
+                                                       <td>
+                                                               <select onchange="') . htmlspecialchars($onChange)) . '">
+                                                                       ') . implode('', $opt)) . '
+                                                               </select></td>
+                                                       <td>') . $controls) . '</td>
+                                                       <td>') . $management) . '</td>
+                                               </tr>
+                                       </table>
+                               ';
+                       }
+               } elseif ($GLOBALS['BE_USER']->workspace !== 0) {
+                       // Write out HTML code:
+                       switch ($GLOBALS['BE_USER']->workspace) {
+                       case 0:
+                               $wsTitle = $GLOBALS['LANG']->sL('LLL:EXT:version/locallang.xml:live', TRUE);
+                               break;
+                       case -1:
+                               $wsTitle = $GLOBALS['LANG']->sL('LLL:EXT:version/locallang.xml:draft', TRUE);
+                               break;
+                       default:
+                               $wsTitle = $GLOBALS['BE_USER']->workspaceRec['title'];
+                               break;
+                       }
+                       // Get Current page record:
+                       $curPage = t3lib_BEfunc::getRecord('pages', $id);
+                       // If the selected page is not online, find the right ID
+                       $onlineId = $curPage['pid'] == -1 ? $curPage['t3ver_oid'] : $id;
+                       // The version of page:
+                       $verPage = t3lib_BEfunc::getWorkspaceVersionOfRecord($GLOBALS['BE_USER']->workspace, 'pages', $onlineId);
+                       if (!$verPage) {
+                               if (!count(t3lib_BEfunc::countVersionsOfRecordsOnPage($GLOBALS['BE_USER']->workspace, $onlineId))) {
+                                       if ($GLOBALS['BE_USER']->workspaceVersioningTypeAccess(0)) {
+                                               $onClick = $GLOBALS['TBE_TEMPLATE']->issueCommand(((('&cmd[pages][' . $onlineId) . '][version][action]=new&cmd[pages][') . $onlineId) . '][version][treeLevels]=0', t3lib_div::linkThisScript(array(
+                                                       'id' => $onlineId
+                                               )));
+                                               $onClick = ('window.location.href=\'' . $onClick) . '\'; return false;';
+                                               // Write out HTML code:
+                                               return ((((((('
+
+                                                       <!--
+                                                               No version yet, create one?
+                                                       -->
+                                                       <table border="0" cellpadding="0" cellspacing="0" id="typo3-versionSelector">
+                                                               <tr>
+                                                                       <td>' . $selectorLabel) . '</td>
+                                                                       <td>') . $GLOBALS['LANG']->sL('LLL:EXT:version/locallang.xml:workspace', TRUE)) . ': "') . htmlspecialchars($wsTitle)) . '"</td>
+                                                                       <td>
+                                                                               <input type="button" value="New version of page" name="_" onclick="') . htmlspecialchars($onClick)) . '" /></td>
+                                                               </tr>
+                                                       </table>
+                                               ';
+                                       }
+                               }
+                       }
+               }
+       }
+
+}
+
+?>
\ No newline at end of file
index 056cdd5..6bcb2b5 100755 (executable)
@@ -1,105 +1,8 @@
 <?php
-/***************************************************************
-*  Copyright notice
-*
-*  (c) 2004-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
-*  All rights reserved
-*
-*  This script is part of the TYPO3 project. The TYPO3 project 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.
-*
-*  The GNU General Public License can be found at
-*  http://www.gnu.org/copyleft/gpl.html.
-*
-*  This script 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.
-*
-*  This copyright notice MUST APPEAR in all copies of the script!
-***************************************************************/
-
-/**
- * "Versioning" item added to click menu of elements.
- *
- * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
- * @package TYPO3
- * @subpackage core
+/*
+ * @deprecated since 6.0, the classname tx_version_cm1 and this file is obsolete
+ * and will be removed by 7.0. The class was renamed and is now located at:
+ * typo3/sysext/version/Classes/ClickMenu/VersionClickMenu.php
  */
-class tx_version_cm1 {
-
-       /**
-        * Main function, adding the item to input menuItems array
-        *
-        * @param       object          References to parent clickmenu objects.
-        * @param       array           Array of existing menu items accumulated. New element added to this.
-        * @param       string          Table name of the element
-        * @param       integer         Record UID of the element
-        * @return      array           Modified menuItems array
-        */
-       function main(&$backRef, $menuItems, $table, $uid) {
-               $localItems = Array();
-               if (!$backRef->cmLevel && $uid > 0 && $GLOBALS['BE_USER']->check('modules', 'web_txversionM1')) {
-
-                               // Returns directly, because the clicked item was not from the pages table
-                       if (in_array('versioning', $backRef->disabledItems) || !$GLOBALS['TCA'][$table] || !$GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
-                               return $menuItems;
-                       }
-
-                               // Adds the regular item
-                       $LL = $this->includeLL();
-
-                               // "Versioning" element added:
-                       $url = t3lib_extMgm::extRelPath('version').'cm1/index.php?table='.rawurlencode($table).'&uid='.$uid;
-                       $localItems[] = $backRef->linkItem(
-                               $GLOBALS['LANG']->getLLL('title', $LL),
-                               $backRef->excludeIcon('<img src="'.$backRef->backPath.t3lib_extMgm::extRelPath('version').'cm1/cm_icon.gif" width="15" height="12" border="0" align="top" alt="" />'),
-                               $backRef->urlRefForCM($url),
-                               1
-                       );
-
-                               // "Send to review" element added:
-                       /*
-                       $url = t3lib_extMgm::extRelPath('version').'cm1/index.php?id='.($table=='pages'?$uid:$backRef->rec['pid']).'&table='.rawurlencode($table).'&uid='.$uid.'&sendToReview=1';
-                       $localItems[] = $backRef->linkItem(
-                               $GLOBALS['LANG']->getLLL('title_review', $LL),
-                               $backRef->excludeIcon('<img src="'.$backRef->backPath.t3lib_extMgm::extRelPath('version').'cm1/cm_icon.gif" width="15" height="12" border="0" align="