[TASK] Move workspace preview functionality into versioning / workspace
authorBenjamin Mack <benni@typo3.org>
Sat, 9 Jul 2011 13:11:19 +0000 (15:11 +0200)
committerBenjamin Mack <benni@typo3.org>
Sat, 9 Jul 2011 13:11:19 +0000 (15:11 +0200)
As the whole handling of the ADMCMD_preview is related to
version / workspaces, the according code should go in these
extensions.

Change-Id: I84a14aa95e95bacca4bdf7d40416da929356a35e
Resolves: #27931

typo3/sysext/version/Classes/Preview.php [new file with mode: 0644]
typo3/sysext/version/cm1/index.php [changed mode: 0644->0755]
typo3/sysext/version/ext_autoload.php
typo3/sysext/version/ext_localconf.php
typo3/sysext/version/ext_tables.sql [new file with mode: 0644]
typo3/sysext/version/ws/index.php [new file with mode: 0755]

diff --git a/typo3/sysext/version/Classes/Preview.php b/typo3/sysext/version/Classes/Preview.php
new file mode 100644 (file)
index 0000000..84ac226
--- /dev/null
@@ -0,0 +1,249 @@
+<?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 {
+
+       /**
+        * the GET parameter to be used
+        * @var string
+        */
+       protected $previewKey = 'ADMCMD_prev';
+
+       /**
+        * instance of the tslib_fe object
+        * @var tslib_fe
+        */
+       protected $tsfeObj;
+
+       /**
+        * 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;
+               $previewConfiguration = $this->getPreviewConfiguration();
+
+               if (is_array($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']);
+                       $this->tsfeObj->ADMCMD_preview_BEUSER_uid = $previewConfiguration['BEUSER_uid'];
+               }
+       }
+
+
+       /**
+        * 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);
+       }
+
+}
+
+
+if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/Classes/Preview.php'])) {
+       include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/version/Classes/Preview.php']);
+}
+
+?>
old mode 100644 (file)
new mode 100755 (executable)
index 8911de3..b84f369
  *
  * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
  */
+/**
+ * [CLASS/FUNCTION INDEX of SCRIPT]
+ *
+ *
+ *
+ *  102: class tx_version_cm1 extends t3lib_SCbase
+ *
+ *              SECTION: Standard module initialization
+ *  138:     function menuConfig()
+ *  175:     function main()
+ *  236:     function jumpToUrl(URL)
+ *  296:     function printContent()
+ *
+ *              SECTION: Versioning management
+ *  322:     function versioningMgm()
+ *  485:     function pageSubContent($pid,$c=0)
+ *  539:     function lookForOwnVersions($table,$uid)
+ *  556:     function adminLinks($table,$row)
+ *
+ *              SECTION: Workspace management
+ *  628:     function workspaceMgm()
+ *  688:     function displayWorkspaceOverview()
+ *  758:     function displayWorkspaceOverview_list($pArray)
+ *  923:     function displayWorkspaceOverview_setInPageArray(&$pArray,$table,$row)
+ *  936:     function displayWorkspaceOverview_allStageCmd()
+ *
+ *              SECTION: Helper functions (REDUNDANT FROM user/ws/index.php - someone could refactor this...)
+ *  986:     function formatVerId($verId)
+ *  996:     function formatWorkspace($wsid)
+ * 1023:     function formatCount($count)
+ * 1050:     function versionsInOtherWS($table,$uid)
+ * 1080:     function showStageChangeLog($table,$id,$stageCommands)
+ * 1129:     function subElements($uid,$treeLevel,$origId=0)
+ * 1232:     function subElements_getNonPageRecords($tN, $uid, &$recList)
+ * 1262:     function subElements_renderItem(&$tCell,$tN,$uid,$rec,$origId,$iconMode,$HTMLdata)
+ * 1331:     function markupNewOriginals()
+ * 1353:     function createDiffView($table, $diff_1_record, $diff_2_record)
+ * 1470:     function displayWorkspaceOverview_stageCmd($table,&$rec_off)
+ * 1557:     function displayWorkspaceOverview_commandLinks($table,&$rec_on,&$rec_off,$vType)
+ * 1627:     function displayWorkspaceOverview_commandLinksSub($table,$rec,$origId)
+ *
+ *              SECTION: Processing
+ * 1683:     function publishAction()
+ *
+ * TOTAL FUNCTIONS: 27
+ * (This index is automatically created/updated by the extension "extdeveval")
+ *
+ */
 
 
        // DEFAULT initialization of a module [BEGIN]
@@ -73,6 +121,12 @@ class tx_version_cm1 extends t3lib_SCbase {
        var $be_user_Array = array();
        var $stageIndex = array();
        var $recIndex = array();
+       protected $showDraftWorkspace = FALSE; // Determines whether to show the dummy draft workspace
+
+
+
+
+
 
        /*********************************
         *
@@ -87,6 +141,42 @@ class tx_version_cm1 extends t3lib_SCbase {
         */
        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(
+                       '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('liveWorkspace'),
+                               -98 => $GLOBALS['LANG']->getLL('draftWorkspaces'),
+                               -99 => $GLOBALS['LANG']->getLL('filter_all'),
+                       ),
+                       'diff' => ''
+               );
+
+               if($this->showDraftWorkspace === TRUE) {
+                       $this->MOD_MENU['display'][-1] = $GLOBALS['LANG']->getLL('defaultDraft');
+               }
+
+                       // Add workspaces (only if the live workspace is currently active):
+               if (t3lib_extMgm::isLoaded('workspaces') && $GLOBALS['BE_USER']->workspace ===0 ) {
+                       $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'].'] '.$rec['title'];
+                               }
+                       }
+               }
+
                        // CLEANSE SETTINGS
                $this->MOD_SETTINGS = t3lib_BEfunc::getModuleData($this->MOD_MENU, t3lib_div::_GP('SET'), $this->MCONF['name'], 'ses');
        }
@@ -126,8 +216,13 @@ class tx_version_cm1 extends t3lib_SCbase {
 
                        // Getting input data:
                $this->id = intval(t3lib_div::_GP('id'));               // Page id. If set, indicates activation from Web>Versioning module
-               $this->uid = intval(t3lib_div::_GP('uid'));             // Record uid. Goes with table name to indicate specific record
-               $this->table = t3lib_div::_GP('table');                 // Record table. Goes with uid to indicate specific record
+               if (!$this->id) {
+                       $this->uid = intval(t3lib_div::_GP('uid'));             // Record uid. Goes with table name to indicate specific record
+                       $this->table = t3lib_div::_GP('table');                 // Record table. Goes with uid to indicate specific record
+               } else {
+                       $this->uid = $this->id;
+                       $this->table = 'pages';
+               }
                $this->details = t3lib_div::_GP('details');             // Page id. If set, indicates activation from Web>Versioning module
                $this->diffOnly = t3lib_div::_GP('diffOnly');           // Flag. If set, shows only the offline version and with diff-view
 
@@ -146,7 +241,7 @@ class tx_version_cm1 extends t3lib_SCbase {
                $pidValue = $this->table==='pages' ? $this->uid : $record['pid'];
 
                        // Checking access etc.
-               if ($this->recordFound && $GLOBALS['TCA'][$this->table]['ctrl']['versioningWS'] && !$this->id)  {
+               if ($this->recordFound && $GLOBALS['TCA'][$this->table]['ctrl']['versioningWS']) {
                        $this->doc->form='<form action="" method="post">';
                        $this->uid = $record['uid'];    // Might have changed if new live record was found!
 
@@ -191,7 +286,11 @@ class tx_version_cm1 extends t3lib_SCbase {
                                $this->publishAccess = $GLOBALS['BE_USER']->workspacePublishAccess($GLOBALS['BE_USER']->workspace);
 
                                        // Render content:
-                               $this->versioningMgm();
+                               if ($this->id)  {
+                                       $this->workspaceMgm();
+                               } else {
+                                       $this->versioningMgm();
+                               }
                        }
 
                        $this->content.=$this->doc->spacer(10);
@@ -200,6 +299,7 @@ class tx_version_cm1 extends t3lib_SCbase {
                        $docHeaderButtons = $this->getButtons();
                        $markers['CSH'] = $docHeaderButtons['csh'];
                        $markers['FUNC_MENU'] = t3lib_BEfunc::getFuncMenu($this->id, 'SET[function]', $this->MOD_SETTINGS['function'], $this->MOD_MENU['function']);
+                       $markers['WS_MENU'] = $this->workspaceMenu();
                        $markers['CONTENT'] = $this->content;
                } else {
                                // If no access or id value, create empty document
@@ -569,6 +669,1165 @@ class tx_version_cm1 extends t3lib_SCbase {
 
                return $adminLink;
        }
+
+
+
+
+
+
+
+
+
+
+
+       /******************************
+        *
+        * Workspace management
+        *
+        ******************************/
+
+       /**
+        * Management of workspace for page ID
+        * Called when $this->id is set.
+        *
+        * @return      void
+        */
+       function workspaceMgm() {
+
+                       // Perform workspace publishing action if buttons are pressed:
+               $errors = $this->publishAction();
+
+                       // Generate workspace overview:
+               $WSoverview = $this->displayWorkspaceOverview();
+
+                       // Buttons for publish / swap:
+               $actionLinks = '<br />';
+               if ($GLOBALS['BE_USER']->workspace!==0) {
+                       if ($this->publishAccess)       {
+                               $actionLinks.= '<input type="submit" name="_publish" value="' . $GLOBALS['LANG']->getLL('publishPage') . '" onclick="return confirm(\'' . sprintf($GLOBALS['LANG']->getLL('publishPageQuestion'), $GLOBALS['BE_USER']->workspaceRec['publish_access'] & 1 ? $GLOBALS['LANG']->getLL('publishPageQuestionStage') : '') . '\');"/>';
+                               if ($GLOBALS['BE_USER']->workspaceSwapAccess()) {
+                                       $actionLinks.= '<input type="submit" name="_swap" value="' . $GLOBALS['LANG']->getLL('swapPage') . '" onclick="return confirm(\'' . sprintf($GLOBALS['LANG']->getLL('swapPageQuestion'), $GLOBALS['BE_USER']->workspaceRec['publish_access'] & 1 ? $GLOBALS['LANG']->getLL('publishPageQuestionStage') : '') . '\');" />';
+                               }
+                       } else {
+                               $actionLinks.= $this->doc->icons(1) . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:no_publish_permission');
+                       }
+               }
+
+               $actionLinks.= '<input type="submit" name="_" value="' . $GLOBALS['LANG']->getLL('refresh') . '" />';
+               $actionLinks.= '<input type="submit" name="_previewLink" value="' . $GLOBALS['LANG']->getLL('previewLink') . '" />';
+               $actionLinks.= '<input type="checkbox" class="checkbox" name="_previewLink_wholeWorkspace" id="_previewLink_wholeWorkspace" value="1" /><label for="_previewLink_wholeWorkspace">' . $GLOBALS['LANG']->getLL('allowPreviewOfWholeWorkspace') . '</label>';
+               $actionLinks.= $this->displayWorkspaceOverview_allStageCmd();
+
+               if ($actionLinks || count($errors))     {
+                       $this->content .= $this->doc->section('', $actionLinks . (count($errors) ? '<h3>' . $GLOBALS['LANG']->getLL('errors') . '</h3><br />' . implode('<br />', $errors) . '<hr />' : ''), 0, 1);
+               }
+
+               if (t3lib_div::_POST('_previewLink'))   {
+                       $ttlHours = intval($GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours'));
+                       $ttlHours = ($ttlHours ? $ttlHours : 24*2);
+
+                       if (t3lib_div::_POST('_previewLink_wholeWorkspace'))    {
+                               $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($this->id);
+                       } else {
+                               $params = 'id='.$this->id.'&ADMCMD_previewWS='.$GLOBALS['BE_USER']->workspace;
+                               $previewUrl = t3lib_BEfunc::getViewDomain($this->id) . '/index.php?ADMCMD_prev=' . t3lib_BEfunc::compilePreviewKeyword($params, $GLOBALS['BE_USER']->user['uid'], 60*60*$ttlHours);
+                       }
+                       $this->content .= $this->doc->section($GLOBALS['LANG']->getLL('previewUrl'), sprintf($GLOBALS['LANG']->getLL('previewInstruction'), $ttlHours) . '<br /><br /><a target="_blank" href="' . htmlspecialchars($previewUrl) . '">' . $previewUrl . '</a>', 0, 1);
+               }
+
+                       // Output overview content:
+               $this->content.= $this->doc->spacer(15);
+               $this->content.= $this->doc->section($this->details ? $GLOBALS['LANG']->getLL('versionDetails') : $GLOBALS['LANG']->getLL('wsManagement'), $WSoverview,0,1);
+
+       }
+
+       function workspaceMenu() {
+               if($this->id) {
+                       $menu = '';
+                       if ($GLOBALS['BE_USER']->workspace===0) {
+                               $menu.= t3lib_BEfunc::getFuncMenu($this->id,'SET[filter]',$this->MOD_SETTINGS['filter'],$this->MOD_MENU['filter']);
+                               $menu.= t3lib_BEfunc::getFuncMenu($this->id,'SET[display]',$this->MOD_SETTINGS['display'],$this->MOD_MENU['display']);
+                       }
+                       if (!$this->details && $GLOBALS['BE_USER']->workspace && !$this->diffOnly)      {
+                               $menu.= t3lib_BEfunc::getFuncCheck($this->id,'SET[diff]',$this->MOD_SETTINGS['diff'],'','','id="checkDiff"').' <label for="checkDiff">' . $GLOBALS['LANG']->getLL('showDiffView') . '</label>';
+                       }
+
+                       if ($menu)      {
+                               return $menu;
+                       }
+               }
+       }
+
+       /**
+        * Rendering the overview of versions in the current workspace
+        *
+        * @return      string          HTML (table)
+        * @see ws/index.php for sister function!
+        */
+       function displayWorkspaceOverview()     {
+
+                       // Initialize variables:
+               $this->showWorkspaceCol = $GLOBALS['BE_USER']->workspace===0 && $this->MOD_SETTINGS['display']<=-98;
+
+                       // Get usernames and groupnames
+               $be_group_Array = t3lib_BEfunc::getListGroupNames('title,uid');
+               $groupArray = array_keys($be_group_Array);
+               $this->be_user_Array = t3lib_BEfunc::getUserNames();
+               if (!$GLOBALS['BE_USER']->isAdmin())            $this->be_user_Array = t3lib_BEfunc::blindUserNames($this->be_user_Array,$groupArray,1);
+
+                       // Initialize Workspace ID and filter-value:
+               if ($GLOBALS['BE_USER']->workspace===0) {
+                       $wsid = $this->details ? -99 : $this->MOD_SETTINGS['display'];          // Set wsid to the value from the menu (displaying content of other workspaces)
+                       $filter = $this->details ? 0 : $this->MOD_SETTINGS['filter'];
+               } else {
+                       $wsid = $GLOBALS['BE_USER']->workspace;
+                       $filter = 0;
+               }
+
+                       // Initialize workspace object and request all pending versions:
+               $wslibObj = t3lib_div::makeInstance('wslib');
+
+                       // Selecting ALL versions belonging to the workspace:
+               $versions = $wslibObj->selectVersionsInWorkspace($wsid, $filter, -99, $this->uid);      // $this->uid is the page id of LIVE record.
+
+                       // Traverse versions and build page-display array:
+               $pArray = array();
+               foreach($versions as $table => $records)        {
+                       foreach($records as $rec)       {
+                               $pageIdField = $table==='pages' ? 't3ver_oid' : 'realpid';
+                               $this->displayWorkspaceOverview_setInPageArray(
+                                       $pArray,
+                                       $table,
+                                       $rec
+                               );
+                       }
+               }
+
+                       // Make header of overview:
+               $tableRows = array();
+               if (count($pArray))     {
+                       $tableRows[] = '
+                               <tr class="bgColor5 tableheader">
+                                       '.($this->diffOnly?'':'<td nowrap="nowrap" colspan="2">' . $GLOBALS['LANG']->getLL('liveVersion') . '</td>').'
+                                       <td nowrap="nowrap" colspan="2">' . $GLOBALS['LANG']->getLL('wsVersions') . '</td>
+                                       <td nowrap="nowrap"'.($this->diffOnly?' colspan="2"':' colspan="4"').'>' . $GLOBALS['LANG']->getLL('controls') . '</td>
+                               </tr>';
+
+                               // Add lines from overview:
+                       $tableRows = array_merge($tableRows, $this->displayWorkspaceOverview_list($pArray));
+
+                       $table = '<table border="0" cellpadding="0" cellspacing="1" class="lrPadding workspace-overview">'.implode('',$tableRows).'</table>';
+               } else $table = '';
+
+               $returnUrl = t3lib_div::sanitizeLocalUrl(t3lib_div::_GP('returnUrl'));
+               $linkBack = t3lib_div::_GP('returnUrl') ? '<a href="' . htmlspecialchars($returnUrl) . '" class="typo3-goBack">' .
+                               t3lib_iconWorks::getSpriteIcon('actions-view-go-back') . $GLOBALS['LANG']->getLL('goBack', TRUE) .
+                       '</a><br /><br />' : '';
+               $resetDiffOnly = $this->diffOnly ? '<a href="index.php?id=' . intval($this->id) . '" class="typo3-goBack">' . $GLOBALS['LANG']->getLL('showAllInformation') . '</a><br /><br />' : '';
+
+               $versionSelector = $GLOBALS['BE_USER']->workspace ? $this->doc->getVersionSelector($this->id) : '';
+
+               return $versionSelector.$linkBack.$resetDiffOnly.$table.$this->markupNewOriginals();
+       }
+
+       /**
+        * Rendering the content for the publish / review overview:
+        * (Made for internal recursive calling)
+        *
+        * @param       array           Storage of the elements to display (see displayWorkspaceOverview() / displayWorkspaceOverview_setInPageArray())
+        * @return      array           Table rows, see displayWorkspaceOverview()
+        */
+       function displayWorkspaceOverview_list($pArray) {
+
+                       // If there ARE elements on this level, print them:
+               $warnAboutVersions_nonPages = FALSE;
+               $warnAboutVersions_page = FALSE;
+               if (is_array($pArray))  {
+                       foreach($pArray as $table => $oidArray) {
+                               foreach($oidArray as $oid => $recs)     {
+
+                                               // Get CURRENT online record and icon based on "t3ver_oid":
+                                       $rec_on = t3lib_BEfunc::getRecord($table,$oid);
+                                       $icon = t3lib_iconWorks::getSpriteIconForRecord($table, $rec_on, array('title' => t3lib_BEfunc::getRecordIconAltText($rec_on,$table)));
+                                       if ($GLOBALS['BE_USER']->workspace===0) {       // Only edit online records if in ONLINE workspace:
+                                               $icon = $this->doc->wrapClickMenuOnIcon($icon, $table, $rec_on['uid'], 1, '', '+edit,view,info,delete');
+                                       }
+
+                                               // Online version display:
+                                               // Create the main cells which will span over the number of versions there is.
+                                       $verLinkUrl = $GLOBALS['TCA'][$table]['ctrl']['versioningWS'];
+                                       $origElement = $icon.
+                                               ($verLinkUrl ? '<a href="'.htmlspecialchars('index.php?table='.$table.'&uid='.$rec_on['uid']).'">' : '').
+                                               t3lib_BEfunc::getRecordTitle($table,$rec_on,TRUE).
+                                               ($verLinkUrl ? '</a>' : '');
+                                       $mainCell_rowSpan = count($recs)>1 ? ' rowspan="'.count($recs).'"' : '';
+                                       $mainCell = '
+                                                               <td align="center"'.$mainCell_rowSpan.'>'.$this->formatVerId($rec_on['t3ver_id']).'</td>
+                                                               <td nowrap="nowrap"'.$mainCell_rowSpan.'>'.
+                                                                       $origElement.
+                                                                       '###SUB_ELEMENTS###'.   // For substitution with sub-elements, if any.
+                                                               '</td>';
+
+                                               // Offline versions display:
+                                               // Traverse the versions of the element
+                                       foreach($recs as $rec)  {
+
+                                                       // Get the offline version record and icon:
+                                               $rec_off = t3lib_BEfunc::getRecord($table,$rec['uid']);
+
+                                               // Prepare swap-mode values:
+                                               if ($table==='pages' && $rec_off['t3ver_swapmode']!=-1) {
+                                                       if ($rec_off['t3ver_swapmode']>0)       {
+                                                               $vType = 'branch';
+                                                       } else {
+                                                               $vType = 'page';
+                                                       }
+                                               } else {
+                                                       $vType = 'element';
+                                               }
+
+                                               // Get icon
+                                               $icon = t3lib_iconWorks::getSpriteIconForRecord($table, $rec_off, array('title' => t3lib_BEfunc::getRecordIconAltText($rec_off, $table)));
+                                               $tempUid = ($table != 'pages' || $vType==='branch' || $GLOBALS['BE_USER']->workspace===0 ? $rec_off['uid'] : $rec_on['uid']);
+                                               $icon = $this->doc->wrapClickMenuOnIcon($icon, $table, $tempUid, 1, '', '+edit,' . ($table == 'pages' ? 'view,info,' : '') . 'delete');
+
+                                                       // Prepare diff-code:
+                                               if ($this->MOD_SETTINGS['diff'] || $this->diffOnly)     {
+                                                       $diffCode = '';
+                                                       list($diffHTML,$diffPct) = $this->createDiffView($table, $rec_off, $rec_on);
+                                                       if ($rec_on['t3ver_state']==1)  {       // New record:
+                                                               $diffCode.= $this->doc->icons(1) . $GLOBALS['LANG']->getLL('newElement') . '<br />';
+                                                               $diffCode.= $diffHTML;
+                                                       } elseif ($rec_off['t3ver_state']==2)   {
+                                                               $diffCode.= $this->doc->icons(2) . $GLOBALS['LANG']->getLL('deletedElement') . '<br />';
+                                                       } elseif ($rec_on['t3ver_state']==3)    {
+                                                               $diffCode.= $this->doc->icons(1) . $GLOBALS['LANG']->getLL('moveToPlaceholder') . '<br />';
+                                                       } elseif ($rec_off['t3ver_state']==4)   {
+                                                               $diffCode.= $this->doc->icons(1) . $GLOBALS['LANG']->getLL('moveToPointer') . '<br />';
+                                                       } else {
+                                                               $diffCode.= ($diffPct<0 ? $GLOBALS['LANG']->getLL('notAvailable') : ($diffPct ? $diffPct . '% ' . $GLOBALS['LANG']->getLL('change') : ''));
+                                                               $diffCode.= $diffHTML;
+                                                       }
+                                               } else $diffCode = '';
+
+                                               switch($vType) {
+                                                       case 'element':
+                                                               $swapLabel = $GLOBALS['LANG']->getLL('element');
+                                                               $swapClass = 'ver-element';
+                                                               $warnAboutVersions_nonPages = $warnAboutVersions_page;  // Setting this if sub elements are found with a page+content (must be rendered prior to this of course!)
+                                                       break;
+                                                       case 'page':
+                                                               $swapLabel = $GLOBALS['LANG']->getLL('page');
+                                                               $swapClass = 'ver-page';
+                                                               $warnAboutVersions_page = !$this->showWorkspaceCol;             // This value is TRUE only if multiple workspaces are shown and we need the opposite here.
+                                                       break;
+                                                       case 'branch':
+                                                               $swapLabel = $GLOBALS['LANG']->getLL('branch');
+                                                               $swapClass = 'ver-branch';
+                                                       break;
+                                               }
+
+                                                       // Modify main cell based on first version shown:
+                                               $subElements = array();
+                                               if ($table==='pages' && $rec_off['t3ver_swapmode']!=-1 && $mainCell)    {       // For "Page" and "Branch" swap modes where $mainCell is still carrying content (only first version)
+                                                       $subElements['on'] = $this->subElements($rec_on['uid'], $rec_off['t3ver_swapmode']);
+                                                       $subElements['off'] = $this->subElements($rec_off['uid'],$rec_off['t3ver_swapmode'],$rec_on['uid']);
+                                               }
+                                               $mainCell = str_replace('###SUB_ELEMENTS###', $subElements['on'], $mainCell);
+
+                                                       // Create version element:
+                                               $versionsInOtherWS = $this->versionsInOtherWS($table, $rec_on['uid']);
+                                               $versionsInOtherWSWarning = $versionsInOtherWS && $GLOBALS['BE_USER']->workspace !== 0 ? '<br />' . $this->doc->icons(2) . $GLOBALS['LANG']->getLL('otherVersions') . $versionsInOtherWS : '';
+                                               $multipleWarning = (!$mainCell && $GLOBALS['BE_USER']->workspace !== 0 ? '<br />' . $this->doc->icons(3) . '<strong>' . $GLOBALS['LANG']->getLL('multipleVersions') . '</strong>' : '');
+                                               $verWarning = $warnAboutVersions || ($warnAboutVersions_nonPages && $GLOBALS['TCA'][$table]['ctrl']['versioning_followPages']) ? '<br />' . $this->doc->icons(3) . '<strong>' . $GLOBALS['LANG']->getLL('versionInVersion') . '</strong>' : '';
+                                               $verElement = $icon.
+                                                       (!$this->details ? '<a href="'.htmlspecialchars($this->doc->backPath.t3lib_extMgm::extRelPath('version').'cm1/index.php?id='.($table==='pages'?$rec_on['uid']:$rec_on['pid']).'&details='.rawurlencode($table.':'.$rec_off['uid']).'&returnUrl='.rawurlencode($this->REQUEST_URI)).'">' : '').
+                                                       t3lib_BEfunc::getRecordTitle($table,$rec_off,TRUE).
+                                                       (!$this->details ? '</a>' : '').
+                                                       $versionsInOtherWSWarning.
+                                                       $multipleWarning.
+                                                       $verWarning;
+
+                                               $ctrlTable = '
+                                                               <td nowrap="nowrap">'.$this->showStageChangeLog($table,$rec_off['uid'],$this->displayWorkspaceOverview_stageCmd($table,$rec_off)).'</td>
+                                                               <td nowrap="nowrap" class="'.$swapClass.'">'.
+                                                                       $this->displayWorkspaceOverview_commandLinks($table,$rec_on,$rec_off,$vType).
+                                                                       htmlspecialchars($swapLabel).
+                                                                       '&nbsp;&nbsp;</td>
+                                                               '.(!$this->diffOnly?'<td nowrap="nowrap"><strong>' . $GLOBALS['LANG']->getLL('lifecycle')  . ':</strong> '.htmlspecialchars($this->formatCount($rec_off['t3ver_count'])).'</td>'.               // Lifecycle
+                                                                       ($this->showWorkspaceCol ? '
+                                                               <td nowrap="nowrap">&nbsp;&nbsp;<strong>' . $GLOBALS['LANG']->getLL('workspace')  . ':</strong> '.htmlspecialchars($this->formatWorkspace($rec_off['t3ver_wsid'])).'</td>' : ''):'');
+
+                                               if ($diffCode)  {
+                                                       $verElement = $verElement.'
+                                                       <br /><strong>' . $GLOBALS['LANG']->getLL('diffToLiveElement') . '</strong>
+                                                       <table border="0" cellpadding="0" cellspacing="0" class="ver-verElement">
+                                                               <tr>
+                                                                       <td class="c-diffCell">'.$diffCode.'</td>
+                                                               </tr>
+                                                       </table>';
+                                               }
+
+
+                                                       // Create version cell:
+                                               $verCell = '
+                                                               <td align="center">'.$this->formatVerId($rec_off['t3ver_id']).'</td>
+                                                               <td nowrap="nowrap">'.
+                                                                       $verElement.
+                                                                       $subElements['off'].
+                                                                       '</td>
+                                                               ';
+
+                                                       // Compile table row:
+                                               $tableRows[] = '
+                                                       <tr class="bgColor4">
+                                                               '.
+                                                               ($this->diffOnly?'':$mainCell).
+                                                               $verCell.
+                                                               $ctrlTable.
+                                                               '
+                                                       </tr>';
+
+                                                       // Reset the main cell:
+                                               $mainCell = '';
+
+                                       }
+                               }
+                       }
+               }
+
+               return $tableRows;
+       }
+
+       /**
+        * Building up of the $pArray
+        * (Internal)
+        *
+        * @param       array           Array that is built up with the page tree structure
+        * @param       string          Table name
+        * @param       array           Table row
+        * @return      void            $pArray is passed by reference and modified internally
+        */
+       function displayWorkspaceOverview_setInPageArray(&$pArray,$table,$row)  {
+               if (!$this->details || $this->details==$table.':'.$row['uid'])  {
+                       $pArray[$table][$row['t3ver_oid']][] = $row;
+               }
+       }
+
+       /**
+        * Links to stage change of a version
+        *
+        * @param       string          Table name
+        * @param       array           Offline record (version)
+        * @return      string          HTML content, mainly link tags and images.
+        */
+       function displayWorkspaceOverview_allStageCmd() {
+
+               $table = t3lib_div::_GP('table');
+               if ($table && $table!='pages')  {
+                       $uid = t3lib_div::_GP('uid');
+                       if ($rec_off = t3lib_BEfunc::getRecordWSOL($table,$uid)) {
+                               $uid = $rec_off['_ORIG_uid'];
+                       }
+               } else $table = '';
+
+               if ($table)     {
+                       if ($uid && $this->recIndex[$table][$uid])      {
+                               $sId = $this->recIndex[$table][$uid];
+                               switch($sId)    {
+                                       case 1:
+                                               $label = $GLOBALS['LANG']->getLL('commentForReviewer');
+                                       break;
+                                       case 10:
+                                               $label = $GLOBALS['LANG']->getLL('commentForPublisher');
+                                       break;
+                               }
+                       } else $sId = 0;
+               } else {
+                       if (count($this->stageIndex[1]))        {       // Review:
+                               $sId = 1;
+                               $color = '#666666';
+                               $label = $GLOBALS['LANG']->getLL('sendItemsToReview') . $GLOBALS['LANG']->getLL('commentForReviewer');
+                               $titleAttrib = $GLOBALS['LANG']->getLL('sendAllToReview');
+                       } elseif(count($this->stageIndex[10]))  {       // Publish:
+                               $sId = 10;
+                               $color = '#6666cc';
+                               $label = $GLOBALS['LANG']->getLL('approveToPublish') . $GLOBALS['LANG']->getLL('commentForPublisher');
+                               $titleAttrib = $GLOBALS['LANG']->getLL('approveAllToPublish');
+                       } else {
+                               $sId = 0;
+                       }
+               }
+
+               if ($sId>0)     {
+                       $issueCmd = '';
+                       $itemCount = 0;
+
+                       if ($table && $uid && $this->recIndex[$table][$uid])    {
+                               $issueCmd.='&cmd['.$table.']['.$uid.'][version][action]=setStage';
+                               $issueCmd.='&cmd['.$table.']['.$uid.'][version][stageId]='.$this->recIndex[$table][$uid];
+                       } else {
+                               foreach($this->stageIndex[$sId] as $table => $uidArray) {
+                                       $issueCmd.='&cmd['.$table.']['.implode(',',$uidArray).'][version][action]=setStage';
+                                       $issueCmd.='&cmd['.$table.']['.implode(',',$uidArray).'][version][stageId]='.$sId;
+                                       $itemCount+=count($uidArray);
+                               }
+                       }
+
+                       $onClick = 'var commentTxt=window.prompt("'.sprintf($label,$itemCount).'","");
+                                                       if (commentTxt!=null) {window.location.href="'.$this->doc->issueCommand($issueCmd,$this->REQUEST_URI).'&generalComment="+escape(commentTxt);}';
+
+                       if (t3lib_div::_GP('sendToReview'))     {
+                               $onClick.= ' else {window.location.href = "'.$this->REQUEST_URI.'"}';
+                               $actionLinks.=
+                                       $this->doc->wrapScriptTags($onClick);
+                       } else {
+                               $onClick.= ' return false;';
+                               $actionLinks.=
+                                       '<input type="submit" name="_" value="'.htmlspecialchars($titleAttrib).'" onclick="'.htmlspecialchars($onClick).'" />';
+                       }
+               } elseif (t3lib_div::_GP('sendToReview'))       {
+                       $onClick = 'window.location.href = "'.$this->REQUEST_URI.'";';
+                       $actionLinks.=
+                               $this->doc->wrapScriptTags($onClick);
+               } else $actionLinks = '';
+
+               return $actionLinks;
+       }
+
+
+
+
+
+
+       /**************************************
+        *
+        * Helper functions (REDUNDANT FROM user/ws/index.php - someone could refactor this...)
+        *
+        *************************************/
+
+       /**
+        * Formatting the version number for HTML output
+        *
+        * @param       integer         Version number
+        * @return      string          Version number for output
+        */
+       function formatVerId($verId)    {
+               return '1.'.$verId;
+       }
+
+       /**
+        * Formatting workspace ID into a visual label
+        *
+        * @param       integer         Workspace ID
+        * @return      string          Workspace title
+        */
+       function formatWorkspace($wsid) {
+
+                       // Render, if not cached:
+               if (!isset($this->formatWorkspace_cache[$wsid]))        {
+                       switch($wsid)   {
+                               case -1:
+                                       $this->formatWorkspace_cache[$wsid] = $GLOBALS['LANG']->getLL('offline');
+                               break;
+                               case 0:
+                                       $this->formatWorkspace_cache[$wsid] = '';       // Does not output anything for ONLINE because it might confuse people to think that the elemnet IS online which is not the case - only that it exists as an offline version in the online workspace...
+                               break;
+                               default:
+                                       $titleRec = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('title', 'sys_workspace', 'uid=' . intval($wsid) . t3lib_BEfunc::deleteClause('sys_workspace'));
+                                       $this->formatWorkspace_cache[$wsid] = '['.$wsid.'] '.$titleRec['title'];
+                               break;
+                       }
+               }
+
+               return $this->formatWorkspace_cache[$wsid];
+       }
+
+       /**
+        * Format publishing count for version (lifecycle state)
+        *
+        * @param       integer         t3ver_count value (number of times it has been online)
+        * @return      string          String translation of count.
+        */
+       function formatCount($count)    {
+
+                       // Render, if not cached:
+               if (!isset($this->formatCount_cache[$count]))   {
+                       switch($count)  {
+                               case 0:
+                                       $this->formatCount_cache[$count] = $GLOBALS['LANG']->getLL('draft');
+                               break;
+                               case 1:
+                                       $this->formatCount_cache[$count] = $GLOBALS['LANG']->getLL('archive');
+                               break;
+                               default:
+                                       $this->formatCount_cache[$count] = sprintf($GLOBALS['LANG']->getLL('publishedXTimes'), $count);
+                               break;
+                       }
+               }
+
+               return $this->formatCount_cache[$count];
+       }
+
+       /**
+        * Looking for versions of a record in other workspaces than the current
+        *
+        * @param       string          Table name
+        * @param       integer         Record uid
+        * @return      string          List of other workspace IDs
+        */
+       function versionsInOtherWS($table,$uid) {
+
+                       // Check for duplicates:
+                       // Select all versions of record NOT in this workspace:
+               $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       't3ver_wsid',
+                       $table,
+                       'pid=-1
+                               AND t3ver_oid='.intval($uid).'
+                               AND t3ver_wsid!='.intval($GLOBALS['BE_USER']->workspace).'
+                               AND (t3ver_wsid=-1 OR t3ver_wsid>0)'.
+                               t3lib_BEfunc::deleteClause($table),
+                       '',
+                       't3ver_wsid',
+                       '',
+                       't3ver_wsid'
+               );
+               if (count($rows))       {
+                       return implode(',',array_keys($rows));
+               }
+       }
+
+       /**
+        * Looks up stage changes for version and displays a formatted view on mouseover.
+        *
+        * @param       string          Table name
+        * @param       integer         Record ID
+        * @param       string          HTML string to wrap the mouseover around (should be stage change links)
+        * @return      string          HTML code.
+        */
+       function showStageChangeLog($table,$id,$stageCommands)  {
+               $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       'log_data,tstamp,userid',
+                       'sys_log',
+                       'action=6 and details_nr=30
+                               AND tablename='.$GLOBALS['TYPO3_DB']->fullQuoteStr($table,'sys_log').'
+                               AND recuid='.intval($id)
+               );
+
+               $entry = array();
+               foreach($rows as $dat)  {
+                       $data = unserialize($dat['log_data']);
+                       $username = $this->be_user_Array[$dat['userid']] ? $this->be_user_Array[$dat['userid']]['username'] : '['.$dat['userid'].']';
+
+                       switch($data['stage'])  {
+                               case 1:
+                                       $text = $GLOBALS['LANG']->getLL('stage.sentToReview');
+                               break;
+                               case 10:
+                                       $text = $GLOBALS['LANG']->getLL('stage.approvedForPublish');
+                               break;
+                               case -1:
+                                       $text = $GLOBALS['LANG']->getLL('stage.rejectedElement');
+                               break;
+                               case 0:
+                                       $text = $GLOBALS['LANG']->getLL('stage.resetToEdit');
+                               break;
+                               default:
+                                       $text = $GLOBALS['LANG']->getLL('stage.undefined');
+                               break;
+                       }
+                       $text = t3lib_BEfunc::dateTime($dat['tstamp']).': "'.$username.'" '.$text;
+                       $text.= ($data['comment'] ? '<br />' . $GLOBALS['LANG']->getLL('userComment') . ': <em>' . htmlspecialchars($data['comment']) . '</em>' : '');
+
+                       $entry[] = $text;
+               }
+
+               return count($entry) ? '<span onmouseover="document.getElementById(\'log_' . $table . $id . '\').style.visibility = \'visible\';" onmouseout="document.getElementById(\'log_' . $table . $id . '\').style.visibility = \'hidden\';">' . $stageCommands . ' (' . count($entry) . ')</span>' .
+                               '<div class="t3-version-infolayer logLayer" id="log_' . $table . $id . '">' . implode('<hr/>', array_reverse($entry)) . '</div>' : $stageCommands;
+       }
+
+       /**
+        * Creates display of sub elements of a page when the swap mode is either "Page" or "Branch" (0 / ALL)
+        *
+        * @param       integer         Page uid (for either online or offline version, but it MUST have swapmode/treeLevel set to >0 (not -1 indicating element versioning)
+        * @param       integer         The treeLevel value, >0 indicates "branch" while 0 means page+content. (-1 would have meant element versioning, but that should never happen for a call to this function!)
+        * @param       integer         For offline versions; This is t3ver_oid, the original ID of the online page.
+        * @return      string          HTML content.
+        */
+       function subElements($uid,$treeLevel,$origId=0) {
+                       // In online workspace we have a reduced view because otherwise it will bloat the listing:
+               if (!$this->details && ($GLOBALS['BE_USER']->workspace === 0 || !$this->MOD_SETTINGS['expandSubElements'])) {
+                       return '<br />
+                                       <img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/ol/joinbottom.gif','width="18" height="16"').' align="top" alt="" title="" />'.
+                                       ($origId ?
+                                               '<a href="'.htmlspecialchars($this->doc->backPath.t3lib_extMgm::extRelPath('version').'cm1/index.php?id='.$uid.'&details='.rawurlencode('pages:'.$uid).'&returnUrl='.rawurlencode($this->REQUEST_URI)).'">'.
+                                               '<span class="typo3-dimmed"><em>' . $GLOBALS['LANG']->getLL('subElementsClick')  . '</em><span></a>' :
+                                               '<span class="typo3-dimmed"><em>' . $GLOBALS['LANG']->getLL('subElements') . '</em><span>');
+               } else {        // For an offline workspace, show sub elements:
+
+                       $tCell = array();
+
+                               // Find records that follow pages when swapping versions:
+                       $recList = array();
+                       foreach ($GLOBALS['TCA'] as $tN => $tCfg) {
+                               if ($tN !== 'pages' && ($treeLevel > 0 || $GLOBALS['TCA'][$tN]['ctrl']['versioning_followPages'])) {
+                                       $this->subElements_getNonPageRecords($tN, $uid, $recList);
+                               }
+                       }
+
+                               // Render records collected above:
+                       $elCount = count($recList)-1;
+                       foreach($recList as $c => $comb)        {
+                               list($tN,$rec) = $comb;
+
+                               $this->subElements_renderItem(
+                                       $tCell,
+                                       $tN,
+                                       $uid,
+                                       $rec,
+                                       $origId,
+                                       $c==$elCount && $treeLevel==0 ? 1 : 0,          // If TRUE, will show bottom-join icon.
+                                       ''
+                               );
+                       }
+
+                               // For branch, dive into the subtree:
+                       if ($treeLevel>0) {
+
+                                       // Drawing tree:
+                               $tree = t3lib_div::makeInstance('t3lib_pageTree');
+                               $tree->init('AND '.$GLOBALS['BE_USER']->getPagePermsClause(1));
+                               $tree->makeHTML = 2;            // 2=Also rendering depth-data into the result array
+                               $tree->getTree($uid, 99, '');
+
+                                       // Traverse page tree:
+                               foreach($tree->tree as $data)   {
+
+                                               // Render page in table cell:
+                                       $this->subElements_renderItem(
+                                               $tCell,
+                                               'pages',
+                                               $uid,
+                                               t3lib_BEfunc::getRecord('pages',$data['row']['uid']),   // Needs all fields, at least more than what is given in $data['row']...
+                                               $origId,
+                                               2,              // 2=the join icon and icon for the record is not rendered for pages (where all is in $data['HTML']
+                                               $data['HTML']
+                                       );
+
+                                               // Find all records from page and collect in $recList:
+                                       $recList = array();
+                                       foreach ($GLOBALS['TCA'] as $tN => $tCfg) {
+                                               if ($tN!=='pages')      {
+                                                       $this->subElements_getNonPageRecords($tN, $data['row']['uid'], $recList);
+                                               }
+                                       }
+
+                                               // Render records collected above:
+                                       $elCount = count($recList)-1;
+                                       foreach($recList as $c => $comb)        {
+                                               list($tN,$rec) = $comb;
+
+                                               $this->subElements_renderItem(
+                                                       $tCell,
+                                                       $tN,
+                                                       $uid,
+                                                       $rec,
+                                                       $origId,
+                                                       $c==$elCount?1:0,       // If TRUE, will show bottom-join icon.
+                                                       $data['HTML_depthData']
+                                               );
+                                       }
+                               }
+                       }
+
+                       return '
+                                       <!-- Sub-element tree for versions -->
+                                       <table border="0" cellpadding="0" cellspacing="1" class="ver-subtree">
+                                               '.implode('',$tCell).'
+                                       </table>';
+               }
+       }
+
+       /**
+        * Select records from a table and add them to recList
+        *
+        * @param       string          Table name (from TCA)
+        * @param       integer         PID to select records from
+        * @param       array           Array where records are accumulated, passed by reference
+        * @return      void
+        */
+       function subElements_getNonPageRecords($tN, $uid, &$recList)    {
+               $records = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       '*',
+                       $tN,
+                       'pid='.intval($uid).
+                               ($GLOBALS['TCA'][$tN]['ctrl']['versioningWS'] ? ' AND t3ver_state=0' : '') .
+                               t3lib_BEfunc::deleteClause($tN),
+                       '',
+                       $GLOBALS['TCA'][$tN]['ctrl']['sortby'] ? $GLOBALS['TCA'][$tN]['ctrl']['sortby'] : $GLOBALS['TYPO3_DB']->stripOrderBy($GLOBALS['TCA'][$tN]['ctrl']['default_sortby'])
+               );
+
+               foreach($records as $rec)       {
+                       $recList[] = array($tN,$rec);
+               }
+       }
+
+       /**
+        * Render a single item in a subelement list into a table row:
+        *
+        * @param       array           Table rows, passed by reference
+        * @param       string          Table name
+        * @param       integer         Page uid for which the subelements are selected/shown
+        * @param       array           Row of element in list
+        * @param       integer         The uid of the online version of $uid. If zero it means we are drawing a row for the online version itself while a value means we are drawing display for an offline version.
+        * @param       integer         Mode of icon display: 0=not the last, 1= is the last in list (make joinbottom icon then), 2=do not shown icons are all (for pages from the page tree already rendered)
+        * @param       string          Prefix HTML data (icons for tree rendering)
+        * @return      void            (Content accumulated in $tCell!)
+        */
+       function subElements_renderItem(&$tCell,$tN,$uid,$rec,$origId,$iconMode,$HTMLdata)      {
+
+                       // Initialize:
+               $origUidFields = $GLOBALS['TCA'][$tN]['ctrl']['origUid'];
+               $diffCode = '';
+
+               if ($origUidFields)     {       // If there is a field for this table with original uids we will use that to connect records:
+                       if (!$origId)   {       // In case we are displaying the online originals:
+                               $this->targets['orig_'.$uid.'_'.$tN.'_'.$rec['uid']] = $rec;    // Build up target array (important that
+                               $tdParams =  ' id="orig_'.$uid.'_'.$tN.'_'.$rec['uid'].'" class="typo3-ver"';           // Setting ID of the table row
+                       } else {        // Version branch:
+                               if ($this->targets['orig_'.$origId.'_'.$tN.'_'.$rec[$origUidFields]])   {       // If there IS a corresponding original record...:
+
+                                               // Prepare Table row parameters:
+                                       $tdParams =  ' onmouseover="hlSubelements(\''.$origId.'_'.$tN.'_'.$rec[$origUidFields].'\', \''.$uid.'_'.$tN.'_'.$rec[$origUidFields].'\', 1, '.($this->MOD_SETTINGS['diff']==2?1:0).');"'.
+                                                               ' onmouseout="hlSubelements(\''.$origId.'_'.$tN.'_'.$rec[$origUidFields].'\', \''.$uid.'_'.$tN.'_'.$rec[$origUidFields].'\', 0, '.($this->MOD_SETTINGS['diff']==2?1:0).');"'.
+                                                               ' id="ver_'.$uid.'_'.$tN.'_'.$rec[$origUidFields].'" class="typo3-ver"';
+
+                                               // Create diff view:
+                                       if ($this->MOD_SETTINGS['diff'])        {
+                                               list($diffHTML,$diffPct) = $this->createDiffView($tN, $rec, $this->targets['orig_'.$origId.'_'.$tN.'_'.$rec[$origUidFields]]);
+
+                                               if ($this->MOD_SETTINGS['diff']==2)     {
+                                                       $diffCode =
+                                                               ($diffPct ? '<span class="nobr">'.$diffPct.'% change</span>' : '-').
+                                                               '<div style="visibility: hidden; position: absolute;" id="diff_'.$uid.'_'.$tN.'_'.$rec[$origUidFields].'" class="diffLayer">'.
+                                                               $diffHTML.
+                                                               '</div>';
+                                               } else {
+                                                       $diffCode =
+                                                               ($diffPct<0 ? $GLOBALS['LANG']->getLL('notAvailable') : ($diffPct ? $diffPct . '% ' . $GLOBALS['LANG']->getLL('change') : '')).
+                                                               $diffHTML;
+                                               }
+                                       }
+
+                                               // Unsetting the target fields allows us to mark all originals without a version in the subtree (see ->markupNewOriginals())
+                                       unset($this->targets['orig_'.$origId.'_'.$tN.'_'.$rec[$origUidFields]]);
+                               } else {        // No original record, so must be new:
+                                       $tdParams =  ' class="typo3-ver-new"';
+                               }
+                       }
+               } else {        // If no original uid column is supported for this table we are forced NOT to display any diff or highlighting.
+                       $tdParams = ' class="typo3-ver-noComp"';
+               }
+
+                       // Compile the cell:
+               $tCell[] = '
+                                               <tr'.$tdParams.'>
+                                                       <td class="iconTitle">'.
+                                                               $HTMLdata.
+                                                               ($iconMode < 2 ?
+                                                                       '<img'.t3lib_iconWorks::skinImg($this->doc->backPath,'gfx/ol/join'.($iconMode ? 'bottom' : '').'.gif','width="18" height="16"').' alt="" />'.
+                                                                       t3lib_iconWorks::getSpriteIconForRecord($tN, $rec) : '').
+                                                               t3lib_BEfunc::getRecordTitle($tN, $rec, TRUE).
+                                                       '</td>
+                                                       <td class="cmdCell">'.
+                                                               $this->displayWorkspaceOverview_commandLinksSub($tN,$rec,$origId).
+                                                       '</td>'.($origId ? '<td class="diffCell">'.
+                                                               $diffCode.
+                                                       '</td>':'').'
+                                               </tr>';
+       }
+
+       /**
+        * JavaScript code to mark up new records that are online (in sub element lists)
+        *
+        * @return      string          HTML javascript section
+        */
+       function markupNewOriginals()   {
+
+               if (count($this->targets))      {
+                       $scriptCode = '';
+                       foreach($this->targets as $key => $rec) {
+                               $scriptCode.='
+                                       document.getElementById(\''.$key.'\').attributes.getNamedItem("class").nodeValue = \'typo3-ver-new\';
+                               ';
+                       }
+
+                       return $this->doc->wrapScriptTags($scriptCode);
+               }
+       }
+
+       /**
+        * Create visual difference view of two records. Using t3lib_diff library
+        *
+        * @param       string          Table name
+        * @param       array           New version record (green)
+        * @param       array           Old version record (red)
+        * @return      array           Array with two keys (0/1) with HTML content / percentage integer (if -1, then it means N/A) indicating amount of change
+        */
+       function createDiffView($table, $diff_1_record, $diff_2_record) {
+
+                       // Initialize:
+               $pctChange = 'N/A';
+
+                       // Check that records are arrays:
+               if (is_array($diff_1_record) && is_array($diff_2_record))       {
+
+                               // Load full table description and initialize diff-object:
+                       t3lib_div::loadTCA($table);
+                       $t3lib_diff_Obj = t3lib_div::makeInstance('t3lib_diff');
+
+                               // Add header row:
+                       $tRows = array();
+                       $tRows[] = '
+                               <tr class="bgColor5 tableheader">
+                                       <td>' . $GLOBALS['LANG']->getLL('fieldname')  . ':</td>
+                                       <td width="98%" nowrap="nowrap">' . $GLOBALS['LANG']->getLL('coloredDiffView') . ':</td>
+                               </tr>
+                       ';
+
+                               // Initialize variables to pick up string lengths in:
+                       $allStrLen = 0;
+                       $diffStrLen = 0;
+
+                               // Traversing the first record and process all fields which are editable:
+                       foreach($diff_1_record as $fN => $fV)   {
+                               if ($GLOBALS['TCA'][$table]['columns'][$fN] && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] !== 'passthrough'
+                                       && !t3lib_div::inList('t3ver_label', $fN)) {
+
+                                               // Check if it is files:
+                                       $isFiles = FALSE;
+                                       if (strcmp(trim($diff_1_record[$fN]), trim($diff_2_record[$fN]))
+                                                       && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['type'] === 'group'
+                                                       && $GLOBALS['TCA'][$table]['columns'][$fN]['config']['internal_type'] === 'file') {
+
+                                                       // Initialize:
+                                               $uploadFolder = $GLOBALS['TCA'][$table]['columns'][$fN]['config']['uploadfolder'];
+                                               $files1 = array_flip(t3lib_div::trimExplode(',', $diff_1_record[$fN],1));
+                                               $files2 = array_flip(t3lib_div::trimExplode(',', $diff_2_record[$fN],1));
+
+                                                       // Traverse filenames and read their md5 sum:
+                                               foreach($files1 as $filename => $tmp)   {
+                                                       $files1[$filename] = @is_file(PATH_site.$uploadFolder.'/'.$filename) ? md5(t3lib_div::getUrl(PATH_site.$uploadFolder.'/'.$filename)) : $filename;
+                                               }
+                                               foreach($files2 as $filename => $tmp)   {
+                                                       $files2[$filename] = @is_file(PATH_site.$uploadFolder.'/'.$filename) ? md5(t3lib_div::getUrl(PATH_site.$uploadFolder.'/'.$filename)) : $filename;
+                                               }
+
+                                                       // Implode MD5 sums and set flag:
+                                               $diff_1_record[$fN] = implode(' ',$files1);
+                                               $diff_2_record[$fN] = implode(' ',$files2);
+                                               $isFiles = TRUE;
+                                       }
+
+                                               // If there is a change of value:
+                                       if (strcmp(trim($diff_1_record[$fN]),trim($diff_2_record[$fN])))        {
+
+
+                                                       // Get the best visual presentation of the value and present that:
+                                               $val1 = t3lib_BEfunc::getProcessedValue($table,$fN,$diff_2_record[$fN],0,1);
+                                               $val2 = t3lib_BEfunc::getProcessedValue($table,$fN,$diff_1_record[$fN],0,1);
+
+                                                       // Make diff result and record string lenghts:
+                                               $diffres = $t3lib_diff_Obj->makeDiffDisplay($val1,$val2,$isFiles?'div':'span');
+                                               $diffStrLen+= $t3lib_diff_Obj->differenceLgd;
+                                               $allStrLen+= strlen($val1.$val2);
+
+                                                       // If the compared values were files, substituted MD5 hashes:
+                                               if ($isFiles)   {
+                                                       $allFiles = array_merge($files1,$files2);
+                                                       foreach($allFiles as $filename => $token)       {
+                                                               if (strlen($token)==32 && strstr($diffres,$token))      {
+                                                                       $filename =
+                                                                               t3lib_BEfunc::thumbCode(array($fN=>$filename),$table,$fN,$this->doc->backPath).
+                                                                               $filename;
+                                                                       $diffres = str_replace($token,$filename,$diffres);
+                                                               }
+                                                       }
+                                               }
+
+                                                       // Add table row with result:
+                                               $tRows[] = '
+                                                       <tr class="bgColor4">
+                                                               <td>'.htmlspecialchars($GLOBALS['LANG']->sL(t3lib_BEfunc::getItemLabel($table,$fN))).'</td>
+                                                               <td width="98%">'.$diffres.'</td>
+                                                       </tr>
+                                               ';
+                                       } else {
+                                                       // Add string lengths even if value matched - in this was the change percentage is not high if only a single field is changed:
+                                               $allStrLen+=strlen($diff_1_record[$fN].$diff_2_record[$fN]);
+                                       }
+                               }
+                       }
+
+                               // Calculate final change percentage:
+                       $pctChange = $allStrLen ? ceil($diffStrLen*100/$allStrLen) : -1;
+
+                               // Create visual representation of result:
+                       if (count($tRows)>1)    {
+                               $content.= '<table border="0" cellpadding="1" cellspacing="1" class="diffTable">'.implode('',$tRows).'</table>';
+                       } else {
+                               $content.= '<span class="nobr">'.$this->doc->icons(1) . $GLOBALS['LANG']->getLL('completeMatch') . '</span>';
+                       }
+               } else $content.= $this->doc->icons(3) . $GLOBALS['LANG']->getLL('errorRecordsNotFound');
+
+                       // Return value:
+               return array($content,$pctChange);
+       }
+
+       /**
+        * Links to stage change of a version
+        *
+        * @param       string          Table name
+        * @param       array           Offline record (version)
+        * @return      string          HTML content, mainly link tags and images.
+        */
+       function displayWorkspaceOverview_stageCmd($table,&$rec_off)    {
+#debug($rec_off['t3ver_stage']);
+               switch((int)$rec_off['t3ver_stage'])    {
+                       case 0:
+                               $sId = 1;
+                               $sLabel = $GLOBALS['LANG']->getLL('editing');
+                               $color = '#666666';
+                               $label = $GLOBALS['LANG']->getLL('commentForReviewer');
+                               $titleAttrib = $GLOBALS['LANG']->getLL('sendToReview');
+                       break;
+                       case 1:
+                               $sId = 10;
+                               $sLabel = $GLOBALS['LANG']->getLL('review');
+                               $color = '#6666cc';
+                               $label = $GLOBALS['LANG']->getLL('commentForPublisher');
+                               $titleAttrib = $GLOBALS['LANG']->getLL('approveForPublishing');
+                       break;
+                       case 10:
+                               $sLabel = $GLOBALS['LANG']->getLL('publish');
+                               $color = '#66cc66';
+                       break;
+                       case -1:
+                               $sLabel = $this->doc->icons(2) . $GLOBALS['LANG']->getLL('rejected');
+                               $sId = 0;
+                               $color = '#ff0000';
+                               $label = $GLOBALS['LANG']->getLL('comment');
+                               $titleAttrib = $GLOBALS['LANG']->getLL('resetStage');
+                       break;
+                       default:
+                               $sLabel = $GLOBALS['LANG']->getLL('undefined');
+                               $sId = 0;
+                               $color = '';
+                       break;
+               }
+#debug($sId);
+
+               $raiseOk = !$GLOBALS['BE_USER']->workspaceCannotEditOfflineVersion($table,$rec_off);
+
+               if ($raiseOk && $rec_off['t3ver_stage']!=-1)    {
+                       $onClick = 'var commentTxt=window.prompt("' . $GLOBALS['LANG']->getLL('rejectExplain') . '","");
+                                                       if (commentTxt!=null) {window.location.href="'.$this->doc->issueCommand(
+                                                       '&cmd['.$table.']['.$rec_off['uid'].'][version][action]=setStage'.
+                                                       '&cmd['.$table.']['.$rec_off['uid'].'][version][stageId]=-1'
+                                                       ).'&cmd['.$table.']['.$rec_off['uid'].'][version][comment]="+escape(commentTxt);}'.
+                                                       ' return false;';
+                               // Reject:
+                       $actionLinks.=
+                               '<a href="#" onclick="'.htmlspecialchars($onClick).'" title="' . $GLOBALS['LANG']->getLL('reject', TRUE) . '">'.
+                                       t3lib_iconWorks::getSpriteIcon('actions-move-down') .
+                               '</a>';
+               } else {
+                               // Reject:
+                       $actionLinks.=
+                               '<img src="'.$this->doc->backPath.'gfx/clear.gif" width="14" height="14" alt="" align="top" title="" />';
+               }
+
+               $actionLinks.= '<span style="background-color: '.$color.'; color: white;">'.$sLabel.'</span>';
+
+                       // Raise
+               if ($raiseOk)   {
+                       $onClick = 'var commentTxt=window.prompt("'.$label.'","");
+                                                       if (commentTxt!=null) {window.location.href="'.$this->doc->issueCommand(
+                                                       '&cmd['.$table.']['.$rec_off['uid'].'][version][action]=setStage'.
+                                                       '&cmd['.$table.']['.$rec_off['uid'].'][version][stageId]='.$sId
+                                                       ).'&cmd['.$table.']['.$rec_off['uid'].'][version][comment]="+escape(commentTxt);}'.
+                                                       ' return false;';
+                       if ($rec_off['t3ver_stage']!=10)        {
+                               $actionLinks.=
+                                       '<a href="#" onclick="'.htmlspecialchars($onClick).'" title="' . htmlspecialchars($titleAttrib) . '">' .
+                                               t3lib_iconWorks::getSpriteIcon('actions-move-up') .
+                                       '</a>';
+
+                               $this->stageIndex[$sId][$table][] = $rec_off['uid'];
+                               $this->recIndex[$table][$rec_off['uid']] = $sId;
+                       }
+               }
+               return $actionLinks;
+       }
+
+       /**
+        * Links to publishing etc of a version
+        *
+        * @param       string          Table name
+        * @param       array           Online record
+        * @param       array           Offline record (version)
+        * @param       string          Swap type, "branch", "page" or "element"
+        * @return      string          HTML content, mainly link tags and images.
+        */
+       function displayWorkspaceOverview_commandLinks($table,&$rec_on,&$rec_off,$vType)        {
+               if ($this->publishAccess && (!($GLOBALS['BE_USER']->workspaceRec['publish_access']&1) || (int)$rec_off['t3ver_stage']===-10))   {
+                       $actionLinks =
+                               '<a href="'.htmlspecialchars($this->doc->issueCommand(
+                                               '&cmd['.$table.']['.$rec_on['uid'].'][version][action]=swap'.
+                                               '&cmd['.$table.']['.$rec_on['uid'].'][version][swapWith]='.$rec_off['uid']
+                                               )).'" title="' . $GLOBALS['LANG']->getLL('publish', TRUE) . '">'.
+                                       t3lib_iconWorks::getSpriteIcon('actions-version-swap-version') .
+                               '</a>';
+                       if ($GLOBALS['BE_USER']->workspaceSwapAccess()) {
+                               $actionLinks.=
+                                       '<a href="'.htmlspecialchars($this->doc->issueCommand(
+                                                       '&cmd['.$table.']['.$rec_on['uid'].'][version][action]=swap'.
+                                                       '&cmd['.$table.']['.$rec_on['uid'].'][version][swapWith]='.$rec_off['uid'].
+                                                       '&cmd['.$table.']['.$rec_on['uid'].'][version][swapIntoWS]=1'
+                                                       )).'" title="' . $GLOBALS['LANG']->getLL('swap', TRUE) . '">'.
+                                               t3lib_iconWorks::getSpriteIcon('actions-version-swap-workspace') .
+                                       '</a>';
+                       }
+               }
+
+               if (!$GLOBALS['BE_USER']->workspaceCannotEditOfflineVersion($table,$rec_off))   {
+                               // Release
+                       $actionLinks.=
+                               '<a href="'.htmlspecialchars($this->doc->issueCommand('&cmd['.$table.']['.$rec_off['uid'].'][version][action]=clearWSID')).'" onclick="return confirm(\'' . $GLOBALS['LANG']->getLL('removeFromWorkspace', TRUE) . '?\');" title="' . $GLOBALS['LANG']->getLL('removeFromWorkspace', TRUE) . '">'.
+                                       t3lib_iconWorks::getSpriteIcon('actions-version-document-remove') .
+                               '</a>';
+
+                               // Edit
+                       if ($table==='pages' && $vType!=='element')     {
+                               $tempUid = ($vType==='branch' || $GLOBALS['BE_USER']->workspace===0 ? $rec_off['uid'] : $rec_on['uid']);
+                               $actionLinks.=
+                                       '<a href="#" onclick="top.loadEditId('.$tempUid.');top.goToModule(\''.$this->pageModule.'\'); return false;" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:img_title_edit_page', TRUE) . '">'.
+                                               t3lib_iconWorks::getSpriteIcon('actions-version-page-open') .
+                                       '</a>';
+                       } else {
+                               $params = '&edit['.$table.']['.$rec_off['uid'].']=edit';
+                               $actionLinks.=
+                                       '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::editOnClick($params,$this->doc->backPath)).'" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:img_title_edit_element', TRUE). '">'.
+                                               t3lib_iconWorks::getSpriteIcon('actions-document-open') .
+                                       '</a>';
+                       }
+               }
+
+                       // History/Log
+               $actionLinks.=
+                       '<a href="'.htmlspecialchars($this->doc->backPath.'show_rechis.php?element='.rawurlencode($table.':'.$rec_off['uid']).'&returnUrl='.rawurlencode($this->REQUEST_URI)).'" title="' . $GLOBALS['LANG']->getLL('showLog', TRUE) . '">'.
+                               t3lib_iconWorks::getSpriteIcon('actions-document-history-open') .
+                       '</a>';
+
+                       // View
+               if ($table==='pages')   {
+                       $tempUid = ($vType==='branch' || $GLOBALS['BE_USER']->workspace===0 ? $rec_off['uid'] : $rec_on['uid']);
+                       $actionLinks.=
+                               '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::viewOnClick($tempUid,$this->doc->backPath,t3lib_BEfunc::BEgetRootLine($tempUid))).'">'.
+                                       t3lib_iconWorks::getSpriteIcon('actions-document-view') .
+                               '</a>';
+               }
+
+               return $actionLinks;
+       }
+
+       /**
+        * Links to publishing etc of a version
+        *
+        * @param       string          Table name
+        * @param       array           Record
+        * @param       integer         The uid of the online version of $uid. If zero it means we are drawing a row for the online version itself while a value means we are drawing display for an offline version.
+        * @return      string          HTML content, mainly link tags and images.
+        */
+       function displayWorkspaceOverview_commandLinksSub($table,$rec,$origId)  {
+               $uid = $rec['uid'];
+               if ($origId || $GLOBALS['BE_USER']->workspace===0)      {
+                       if (!$GLOBALS['BE_USER']->workspaceCannotEditRecord($table,$rec))       {
+                                       // Edit
+                               if ($table==='pages')   {
+                                       $actionLinks.=
+                                               '<a href="#" onclick="top.loadEditId('.$uid.');top.goToModule(\''.$this->pageModule.'\'); return false;" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:img_title_edit_page', TRUE) . '">'.
+                                                       t3lib_iconWorks::getSpriteIcon('apps-version-page-open') .
+                                               '</a>';
+                               } else {
+                                       $params = '&edit['.$table.']['.$uid.']=edit';
+                                       $actionLinks.=
+                                               '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::editOnClick($params,$this->doc->backPath)).'" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:img_title_edit_element', TRUE) . '">'.
+                                                       t3lib_iconWorks::getSpriteIcon('actions-document-open') .
+                                               '</a>';
+                               }
+                       }
+
+                               // History/Log
+                       $actionLinks.=
+                               '<a href="'.htmlspecialchars($this->doc->backPath.'show_rechis.php?element='.rawurlencode($table.':'.$uid).'&returnUrl='.rawurlencode($this->REQUEST_URI)).'" title="' . $GLOBALS['LANG']->getLL('showLog', TRUE) . '">'.
+                                       t3lib_iconWorks::getSpriteIcon('actions-document-history-open') .
+                               '</a>';
+               }
+
+                       // View
+               if ($table==='pages')   {
+                       $actionLinks.=
+                               '<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::viewOnClick($uid,$this->doc->backPath,t3lib_BEfunc::BEgetRootLine($uid))).'">'.
+                                       t3lib_iconWorks::getSpriteIcon('actions-document-view') .
+                               '</a>';
+               }
+
+               return $actionLinks;
+       }
+
+
+
+
+
+
+
+
+
+       /**********************************
+        *
+        * Processing
+        *
+        **********************************/
+
+       /**
+        * Will publish workspace if buttons are pressed
+        *
+        * @return      void
+        */
+       function publishAction()        {
+
+                       // If "Publish" or "Swap" buttons are pressed:
+               if (t3lib_div::_POST('_publish') || t3lib_div::_POST('_swap'))  {
+
+                       if ($this->table==='pages')     {       // Making sure ->uid is a page ID!
+                                       // Initialize workspace object and request all pending versions:
+                               $wslibObj = t3lib_div::makeInstance('wslib');
+                               $cmd = $wslibObj->getCmdArrayForPublishWS($GLOBALS['BE_USER']->workspace, t3lib_div::_POST('_swap'),$this->uid);
+
+                                       // Execute the commands:
+                               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+                               $tce->stripslashes_values = 0;
+                               $tce->start(array(), $cmd);
+                               $tce->process_cmdmap();
+
+                               t3lib_BEfunc::setUpdateSignal('updatePageTree');
+
+                               return $tce->errorLog;
+                       }
+               }
+       }
 }
 
 
index c305b3e..8b9a53f 100644 (file)
@@ -9,5 +9,6 @@ return array(
        'tx_version_gui' => $extensionPath . 'class.tx_version_gui.php',
        'tx_version_tcemain' => $extensionPath . 'class.tx_version_tcemain.php',
        'tx_version_tasks_autopublish' => $extensionPath . 'tasks/class.tx_version_tasks_autopublish.php',
+       'tx_version_preview' => $extensionPath . 'Classes/Preview.php',
 );
-?>
\ No newline at end of file
+?>
index 8ed9fda..8526f27 100644 (file)
@@ -10,6 +10,8 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['proc
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['moveRecordClass']['version'] = t3lib_extMgm::extPath('version', 'class.tx_version_tcemain.php:&tx_version_tcemain');
        // Register hook for overriding the icon status overlay
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_iconworks.php']['overrideIconOverlay']['version'] = t3lib_extMgm::extPath('version', 'class.tx_version_iconworks.php:&tx_version_iconworks');
+       // Register hook to check for the preview mode in the FE
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['connectToDB']['version_preview'] = 'EXT:version/Classes/Preview.php:Tx_Version_Preview->checkForPreview';
 
 if (TYPO3_MODE == 'BE') {
 
diff --git a/typo3/sysext/version/ext_tables.sql b/typo3/sysext/version/ext_tables.sql
new file mode 100644 (file)
index 0000000..e1c3a47
--- /dev/null
@@ -0,0 +1,11 @@
+
+#
+# Table structure for table 'sys_preview'
+#
+CREATE TABLE sys_preview (
+  keyword varchar(32) DEFAULT '' NOT NULL,
+  tstamp int(11) DEFAULT '0' NOT NULL,
+  endtime int(11) DEFAULT '0' NOT NULL,
+  config text,
+  PRIMARY KEY (keyword)
+);
diff --git a/typo3/sysext/version/ws/index.php b/typo3/sysext/version/ws/index.php
new file mode 100755 (executable)
index 0000000..f9dab04
--- /dev/null
@@ -0,0 +1,1074 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 1999-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!
+***************************************************************/
+/**
+ * Module: Workspace manager
+ *
+ * $Id: index.php 9427 2010-11-17 08:38:31Z benni $
+ *
+ * @author     Kasper Skårhøj <kasperYYYY@typo3.com>
+ * @author     Dmitry Dulepov <typo3@accio.lv>
+ */
+/**
+ * [CLASS/FUNCTION INDEX of SCRIPT]
+ *
+ *
+ *
+ *  101: class SC_mod_user_ws_index extends t3lib_SCbase
+ *
+ *              SECTION: Standard module initialization
+ *  128:     function menuConfig()
+ *  175:     function init()
+ *  233:     function main()
+ *  280:     function printContent()
+ *
+ *              SECTION: Module content: Publish
+ *  310:     function moduleContent_publish()
+ *  411:     function displayVersionDetails($details)
+ *  420:     function displayWorkspaceOverview()
+ *
+ *              SECTION: Module content: Workspace list
+ *  461:     function moduleContent_workspaceList()
+ *  476:     function workspaceList_displayUserWorkspaceList()
+ *  553:     function workspaceList_getUserWorkspaceList()
+ *  592:     function workspaceList_formatWorkspaceData(&$wksp)
+ *  634:     function workspaceList_getWebMountPoints(&$wksp)
+ *  683:     function workspaceList_getFileMountPoints(&$wksp)
+ *  736:     function workspaceList_displayUserWorkspaceListHeader()
+ *  756:     function workspaceList_getUserList(&$wksp)
+ *  783:     function workspaceList_getUserListForSysWorkspace(&$wksp)
+ *  810:     function workspaceList_getUserListWithAccess(&$list, $access)
+ *  883:     function workspaceList_displayIcons($currentWorkspace, &$wksp)
+ *  931:     function workspaceList_hasEditAccess(&$wksp)
+ *  943:     function workspaceList_createFakeWorkspaceRecord($uid)
+ *
+ * TOTAL FUNCTIONS: 20
+ * (This index is automatically created/updated by the extension "extdeveval")
+ *
+ */
+
+       // Initialize module:
+unset($MCONF);
+require ('conf.php');
+require ($GLOBALS['BACK_PATH'].'init.php');
+require ($GLOBALS['BACK_PATH'].'template.php');
+$GLOBALS['BE_USER']->modAccess($MCONF,1);
+
+       // Include libraries of various kinds used inside:
+$GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_mod_user_ws.xml');
+$GLOBALS['LANG']->includeLLFile('EXT:lang/locallang_misc.xml');
+require_once('class.wslib.php');
+require_once('class.wslib_gui.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
+       var $MCONF = array();                           // Module configuration
+       var $MOD_MENU = array();                        // Module menu items
+       var $MOD_SETTINGS = array();            // Module session settings
+
+       /**
+        * Document Template Object
+        *
+        * @var noDoc
+        */
+       var $doc;
+       var $content;                                           // Accumulated content
+
+
+               // Internal:
+       var $publishAccess = FALSE;
+       var $be_user_Array = array();
+       var $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
+        */
+       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:
+        */
+       function execute()      {
+               $post = t3lib_div::_POST();
+
+#              debug($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_div::testInt($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
+        */
+       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
+        */
+       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['CSH'] = $docHeaderButtons['csh'];
+
+               }
+               $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
+        */
+       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
+        */
+       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
+        */
+       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)
+        */
+       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
+        */
+       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
+        */
+       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
+        */
+       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;       // `$result` contains `$rec` plus access type through '_ACCESS' key
+                       }
+               }
+               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
+        */
+       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
+        */
+       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(
+                       '*',    // All fields needed for t3lib_iconWorks::getSpriteIconForRecord()
+                       '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
+        */
+       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(
+                       '*',    // All fields needed for t3lib_iconWorks::getSpriteIconForRecord()
+                       '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
+        */
+       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
+        */
+       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
+        */
+       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
+        */
+       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
+        */
+       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
+        */
+       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)
+        */
+       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;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['typo3/mod/user/ws/index.php'])) {
+       include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['typo3/mod/user/ws/index.php']);
+}
+
+
+
+// Make instance:
+$SOBE = t3lib_div::makeInstance('SC_mod_user_ws_index');
+$SOBE->execute();
+$SOBE->init();
+$SOBE->main();
+$SOBE->printContent();
+
+?>