Added feature #16247: Integrate new workspace module based on ExtJS
authorOliver Hader <oliver.hader@typo3.org>
Thu, 11 Nov 2010 22:12:03 +0000 (22:12 +0000)
committerOliver Hader <oliver.hader@typo3.org>
Thu, 11 Nov 2010 22:12:03 +0000 (22:12 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@9340 709f56b5-9817-0410-a4d7-c38de5d9e867

76 files changed:
ChangeLog
typo3/sysext/extbase/Classes/MVC/Controller/ActionController.php
typo3/sysext/workspaces/Classes/BackendUserInterface/WorkspaceSelectorToolbarItem.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Controller/AbstractController.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Controller/PreviewController.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Controller/ReviewController.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/AbstractHandler.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/MassActionHandler.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/Server.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/ToolbarMenu.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/AutoPublish.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/AutoPublishTask.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/Befunc.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/GridData.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/Stages.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/Tcemain.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/Workspaces.php [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang.xml [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xml [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xml [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Language/locallang_mod.xml [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Layouts/module.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Layouts/nodoc.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Layouts/popup.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Partials/legend.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Partials/navigation.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/bg.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/component.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/Filter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/ListFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/NumericFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/StringFilter.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/equals.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/find.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/greater_than.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/less_than.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_asc.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/images/sort_filtered_desc.gif [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/ListMenu.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/menu/RangeMenu.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/helpers.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/preview.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/toolbar.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/workspacegrid.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/workspacemenu.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/workspaces.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/StyleSheet/module.css [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Service/WorkspaceTest.php [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Service/fixtures/dbDefaultPages.xml [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Service/fixtures/dbDefaultWorkspaces.xml [new file with mode: 0644]
typo3/sysext/workspaces/Tests/Service/fixtures/dbMovedContent.xml [new file with mode: 0644]
typo3/sysext/workspaces/ext_autoload.php [new file with mode: 0644]
typo3/sysext/workspaces/ext_emconf.php [new file with mode: 0644]
typo3/sysext/workspaces/ext_icon.gif [new file with mode: 0644]
typo3/sysext/workspaces/ext_localconf.php [new file with mode: 0644]
typo3/sysext/workspaces/ext_tables.php [new file with mode: 0644]
typo3/sysext/workspaces/ext_tables.sql [new file with mode: 0644]
typo3/sysext/workspaces/last_synched_target [new file with mode: 0644]
typo3/sysext/workspaces/tca.php [new file with mode: 0644]

index 5ea2cc8..3d8689a 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2010-11-11  Oliver Hader  <oliver@typo3.org>
+
+       * Added feature #16247: Integrate new workspace module based on ExtJS (thanks to Workspaces Team)
+
 2010-11-11  Stanislas Rolland  <typo3@sjbr.ca>
 
        * Fixed bug #16314: Incorrect use of is_dir in class.tx_rtehtmlarea_clearrtecache.php (Thanks to Juergen Deisenroth)
index e253248..380b1e9 100644 (file)
@@ -34,7 +34,6 @@
  * @api
  */
 class Tx_Extbase_MVC_Controller_ActionController extends Tx_Extbase_MVC_Controller_AbstractController {
-
        /**
         * @var Tx_Extbase_Reflection_Service
         */
diff --git a/typo3/sysext/workspaces/Classes/BackendUserInterface/WorkspaceSelectorToolbarItem.php b/typo3/sysext/workspaces/Classes/BackendUserInterface/WorkspaceSelectorToolbarItem.php
new file mode 100644 (file)
index 0000000..3d7b251
--- /dev/null
@@ -0,0 +1,216 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2007-2010 Ingo Renner <ingo@typo3.org>
+*  (c) 2010 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!
+***************************************************************/
+
+if(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
+       require_once(PATH_typo3 . 'interfaces/interface.backend_toolbaritem.php');
+}
+
+/**
+ * class to render the workspace selector
+ *
+ * @author     Ingo Renner <ingo@typo3.org>
+ * @package Workspaces
+ * @subpackage BackendUserInterface
+ */
+class WorkspaceSelectorToolbarItem implements backend_toolbarItem {
+
+       protected $changeWorkspace;
+       protected $changeWorkspacePreview;
+
+       /**
+        * reference back to the backend object
+        *
+        * @var TYPO3backend
+        */
+       protected $backendReference;
+
+       protected $checkAccess = NULL;
+
+       /**
+        * constructor
+        *
+        * @param       TYPO3backend    TYPO3 backend object reference
+        */
+       public function __construct(TYPO3backend &$backendReference = null) {
+               $this->backendReference       = $backendReference;
+               $this->changeWorkspace        = t3lib_div::_GP('changeWorkspace');
+               $this->changeWorkspacePreview = t3lib_div::_GP('changeWorkspacePreview');
+
+               $pageRenderer = t3lib_div::makeInstance('t3lib_pageRenderer');
+               $this->backendReference->addJavaScript("TYPO3.Workspaces = { workspaceTitle : '" . tx_Workspaces_Service_Workspaces::getWorkspaceTitle($GLOBALS['BE_USER']->workspace) . "'};\n");
+       }
+
+       /**
+        * checks whether the user has access to this toolbar item
+        *
+        * @see         typo3/alt_shortcut.php
+        * @return  boolean  true if user has access, false if not
+        */
+       public function checkAccess() {
+               if (t3lib_extMgm::isLoaded('workspaces')) {
+                       if ($this->checkAccess == NULL) {
+                                       $availableWorkspaces = $this->getAvailableWorkspaces();
+                                       if (count($availableWorkspaces) > 1) {
+                                               $this->checkAccess = TRUE;
+                                       } else {
+                                               $this->checkAccess = FALSE;
+                                       }
+                       }
+                       return $this->checkAccess;
+               }
+               return FALSE;
+       }
+
+       /**
+        * retrieves the available workspaces from the database and checks whether
+        * they're available to the current BE user
+        *
+        * @return      array   array of worspaces available to the current user
+        */
+       protected function getAvailableWorkspaces() {
+               $availableWorkspaces = array();
+
+                       // add default workspaces
+               if($GLOBALS['BE_USER']->checkWorkspace(array('uid' => 0))) {
+                       $availableWorkspaces[0] = '['.$GLOBALS['LANG']->getLL('bookmark_onlineWS').']';
+               }
+               if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => -1))) {
+                       $availableWorkspaces[-1] = '['.$GLOBALS['LANG']->getLL('bookmark_offlineWS').']';
+               }
+
+                       // add custom workspaces (selecting all, filtering by BE_USER check):
+               $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       'uid, title, adminusers, members, reviewers',
+                       'sys_workspace',
+                       'pid = 0'.t3lib_BEfunc::deleteClause('sys_workspace'),
+                       '',
+                       'title'
+               );
+               if(count($customWorkspaces)) {
+                       foreach($customWorkspaces as $workspace) {
+                               if($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
+                                       $availableWorkspaces[$workspace['uid']] = $workspace['uid'] . ': ' . htmlspecialchars($workspace['title']);
+                               }
+                       }
+               }
+
+               return $availableWorkspaces;
+       }
+
+       /**
+        * Creates the selector for workspaces
+        *
+        * @return      string          workspace selector as HTML select
+        */
+       public function render() {
+               $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:toolbarItems.workspace', true);
+               $this->addJavascriptToBackend();
+               $availableWorkspaces = $this->getAvailableWorkspaces();
+               $workspaceMenu       = array();
+
+               $stateCheckedIcon = t3lib_iconWorks::getSpriteIcon('status-status-checked');
+
+               $stateUncheckedIcon = t3lib_iconWorks::getSpriteIcon('empty-empty', array(
+                       'title' => $GLOBALS['LANG']->getLL('bookmark_inactive')
+               ));
+
+               $workspaceMenu[] = '<a href="#" class="toolbar-item">' .
+                       t3lib_iconWorks::getSpriteIcon('apps-toolbar-menu-workspace', array('title' => $title)) .
+                               '</a>';
+               $workspaceMenu[] = '<ul class="toolbar-item-menu" style="display: none;">';
+
+               if (count($availableWorkspaces)) {
+                       foreach($availableWorkspaces as $workspaceId => $label) {
+                               $selected = '';
+                               $icon = $stateUncheckedIcon;
+                               if((int) $GLOBALS['BE_USER']->workspace === $workspaceId) {
+                                       $selected = ' class="selected"';
+                                       $icon = $stateCheckedIcon;
+                               }
+
+                               $workspaceMenu[] = '<li' . $selected . '>' . $icon .
+                                       ' <a href="backend.php?changeWorkspace=' .
+                                       intval($workspaceId) . '" id="ws-' . intval($workspaceId) .
+                                       '" class="ws">' . $label . '</a></li>';
+                       }
+               } else {
+                       $workspaceMenu[] = '<li>' . $stateUncheckedIcon . ' ' .
+                               $GLOBALS['LANG']->getLL('bookmark_noWSfound', true) .
+                               '</li>';
+               }
+
+                       // frontend preview toggle
+               $frontendPreviewActiveIcon = $stateUncheckedIcon;
+               if ($GLOBALS['BE_USER']->user['workspace_preview']) {
+                       $frontendPreviewActiveIcon = $stateCheckedIcon;
+               }
+
+               $workspaceMenu[] = '<li class="divider">' . $frontendPreviewActiveIcon .
+                       '<a href="backend.php?changeWorkspacePreview=' .
+                       ($GLOBALS['BE_USER']->user['workspace_preview'] ? '0' : '1') .
+                       '" id="frontendPreviewToggle">' . $GLOBALS['LANG']->getLL('bookmark_FEPreview', true) . '</a></li>';
+
+                       // go to workspace module link
+               $workspaceMenu[] = '<li>' . $stateUncheckedIcon . ' ' .
+                       '<a href="javascript:top.goToModule(\'web_WorkspacesWorkspaces\');" target="content" id="goToWsModule">' .
+                       ' '. $GLOBALS['LANG']->getLL('bookmark_workspace', true) . '</a></li>';
+
+               $workspaceMenu[] = '</ul>';
+
+               return implode(LF, $workspaceMenu);
+       }
+
+       /**
+        * adds the necessary JavaScript to the backend
+        *
+        * @return      void
+        */
+       protected function addJavascriptToBackend() {
+               $this->backendReference->addJavascriptFile(t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/JavaScript/workspacemenu.js');
+       }
+
+       /**
+        * returns additional attributes for the list item in the toolbar
+        *
+        * @return      string          list item HTML attibutes
+        */
+       public function getAdditionalAttributes() {
+               return ' id="workspace-selector-menu"';
+       }
+}
+
+
+if(!(TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX)) {
+       $GLOBALS['TYPO3backend']->addToolbarItem('workSpaceSelector', 'WorkspaceSelectorToolbarItem');
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/BackendUserInterface/WorkspaceSelectorToolbarItem.php'])   {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/BackendUserInterface/WorkspaceSelectorToolbarItem.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Controller/AbstractController.php b/typo3/sysext/workspaces/Classes/Controller/AbstractController.php
new file mode 100644 (file)
index 0000000..0fcc211
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+require_once($GLOBALS['BACK_PATH'] . 'template.php');
+
+/**
+ * Abstract action controller.
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Controller
+ */
+class Tx_Workspaces_Controller_AbstractController extends Tx_Extbase_MVC_Controller_ActionController {
+       /**
+        * @var string Key of the extension this controller belongs to
+        */
+       protected $extensionName = 'Workspaces';
+
+       /**
+        * @var t3lib_PageRenderer
+        */
+       protected $pageRenderer;
+
+       /**
+        * @var integer
+        */
+       protected $pageId;
+
+       /**
+        * Initializes the controller before invoking an action method.
+        *
+        * @return void
+        */
+       protected function initializeAction() {
+               // @todo Evaluate how the intval() call can be used with Extbase validators/filters
+               $this->pageId = intval(t3lib_div::_GP('id'));
+
+               $this->pageRenderer->addInlineSetting('Workspaces', 'id', $this->pageId);
+               $this->pageRenderer->addInlineSetting('Workspaces', 'depth', ($this->pageId === 0 ? 999 : 1));
+
+               $this->pageRenderer->disableCompressJavascript();
+               $this->pageRenderer->disableConcatenateFiles();
+
+               $this->pageRenderer->addCssFile(t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/StyleSheet/module.css');
+
+               $this->pageRenderer->addInlineLanguageLabelArray(array(
+                       'title'                 => $GLOBALS['LANG']->getLL('title'),
+                       'path'                  => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.path'),
+                       'table'                 => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.table'),
+                       'depth'                 => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_perm.xml:Depth'),
+                       'depth_0'               => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_0'),
+                       'depth_1'               => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_1'),
+                       'depth_2'               => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_2'),
+                       'depth_3'               => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_3'),
+                       'depth_4'               => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_4'),
+                       'depth_infi'    => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:labels.depth_infi'),
+               ));
+
+               $this->pageRenderer->addInlineLanguageLabelFile('EXT:workspaces/Resources/Private/Language/locallang.xml');
+       }
+
+       /**
+        * Processes a general request. The result can be returned by altering the given response.
+        *
+        * @param Tx_Extbase_MVC_Request $request The request object
+        * @param Tx_Extbase_MVC_Response $response The response, modified by this handler
+        * @throws Tx_Extbase_MVC_Exception_UnsupportedRequestType if the controller doesn't support the current request type
+        * @return void
+        */
+       public function processRequest(Tx_Extbase_MVC_Request $request, Tx_Extbase_MVC_Response $response) {
+               $this->template = t3lib_div::makeInstance('template');
+               $this->pageRenderer = $this->template->getPageRenderer();
+
+               $GLOBALS['SOBE'] = new stdClass();
+               $GLOBALS['SOBE']->doc = $this->template;
+
+               parent::processRequest($request, $response);
+
+               $pageHeader = $this->template->startpage(
+                       $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:module.title')
+               );
+               $pageEnd = $this->template->endPage();
+
+               $response->setContent($pageHeader . $response->getContent() . $pageEnd);
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Controller/AbstractController.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Controller/AbstractController.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Controller/PreviewController.php b/typo3/sysext/workspaces/Classes/Controller/PreviewController.php
new file mode 100644 (file)
index 0000000..18a2cf0
--- /dev/null
@@ -0,0 +1,241 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+/**
+ * Implements the preview controller of the workspace module.
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Controller
+ */
+class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controller_AbstractController {
+       /**
+        * Creates this object.
+        *
+        * @param boolean $skipObjectInit
+        * @return void
+        */
+       public function __construct($initializeObjects = TRUE) {
+                       // removed this because the injection breaks for some reason when viewOnClick hook inits this object
+               if ($initializeObjects) {
+                       $this->initializeObjects();
+               }
+       }
+
+       /**
+        * Initializes the controller before invoking an action method.
+        *
+        * @return void
+        */
+       protected function initializeAction() {
+               parent::initializeAction();
+
+               $this->pageRenderer->loadExtJS();
+               $this->pageRenderer->enableExtJSQuickTips();
+               $this->pageRenderer->enableExtJsDebug();
+
+                       // Load  JavaScript:
+               $this->pageRenderer->addExtDirectCode();
+               $this->pageRenderer->addJsFile(
+                       $this->backPath . 'ajax.php?ajaxID=ExtDirect::getAPI&namespace=TYPO3.Workspaces',
+                       NULL,
+                       FALSE
+               );
+
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/flashmessages.js');
+               $this->pageRenderer->addJsFile($this->backPath . 'js/extjs/iframepanel.js');
+
+               $resourcePath = t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/JavaScript/';
+               $this->pageRenderer->addJsFile($resourcePath . 'preview.js');
+
+                       // todo this part should be done with inlineLocallanglabels
+               $this->pageRenderer->addJsInlineCode('workspace-inline-code', $this->generateJavascript());
+       }
+
+       /**
+        * Basically makes sure that the workspace preview is rendered.
+        * The preview itself consists of three frames, so there are
+        * only the frames-urls we've to generate here
+        *
+        * @return void
+        */
+       public function indexAction() {
+               // @todo Evaluate how the intval() call can be used with Extbase validators/filters
+               $language = intval(t3lib_div::_GP('L'));
+
+               $controller = t3lib_div::makeInstance('Tx_Workspaces_Controller_ReviewController', TRUE);
+               $uriBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Routing_UriBuilder');
+
+               $wsSettingsPath = t3lib_div::getIndpEnv('TYPO3_SITE_URL') . 'typo3/';
+               $wsSettingsUri = $uriBuilder->uriFor('singleIndex', array(), $controller, 'workspaces', 'web_workspacesworkspaces');
+               $wsSettingsParams = '&tx_workspaces_web_workspacesworkspaces[controller]=Review';
+               $wsSettingsUrl = $wsSettingsPath . $wsSettingsUri . $wsSettingsParams;
+
+               $wsHelpUri = $uriBuilder->uriFor('help', array(), $this, 'workspaces', 'web_workspacesworkspaces');
+               $wsHelpParams = '&tx_workspaces_web_workspacesworkspaces[controller]=Preview';
+               $wsHelpUrl = $wsSettingsPath . $wsHelpUri . $wsHelpParams;
+
+               $wsBaseUrl = t3lib_div::getIndpEnv('TYPO3_SITE_URL') . 'index.php?id=' . $this->pageId . '&L=' . $language;
+
+               // @todo - handle new pages here
+               // branchpoints are not handled anymore because this feature is not supposed anymore
+               $this->view->assign('liveUrl', $wsBaseUrl . '&ADMCMD_noBeUser=1');
+               $this->view->assign('wsUrl', $wsBaseUrl . '&ADMCMD_view=1&ADMCMD_editIcons=1&ADMCMD_previewWS=' . $GLOBALS['BE_USER']->workspace);
+               $this->view->assign('wsSettingsUrl', $wsSettingsUrl);
+               $this->view->assign('wsHelpUrl', $wsHelpUrl);
+       }
+
+       /**
+        * @return void
+        */
+       public function helpAction() {
+               // @todo Implement this action
+       }
+
+       /**
+        * Generates the JavaScript code for the backend,
+        * and since we're loading a backend module outside of the actual backend
+        * this copies parts of the backend.php
+        *
+        * @return      void
+        */
+       protected function generateJavascript() {
+               $pathTYPO3 = t3lib_div::dirname(t3lib_div::getIndpEnv('SCRIPT_NAME')) . '/';
+
+                       // If another page module was specified, replace the default Page module with the new one
+               $newPageModule = trim($GLOBALS['BE_USER']->getTSConfigVal('options.overridePageModule'));
+               $pageModule = t3lib_BEfunc::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
+               if (!$GLOBALS['BE_USER']->check('modules', $pageModule)) {
+                       $pageModule = '';
+               }
+
+               $menuFrameName = 'menu';
+               if ($GLOBALS['BE_USER']->uc['noMenuMode'] === 'icons') {
+                       $menuFrameName = 'topmenuFrame';
+               }
+
+                       // determine security level from conf vars and default to super challenged
+               if ($GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel']) {
+                       $loginSecurityLevel = $GLOBALS['TYPO3_CONF_VARS']['BE']['loginSecurityLevel'];
+               } else {
+                       $loginSecurityLevel = 'superchallenged';
+               }
+
+               $t3Configuration = array(
+                       'siteUrl' => t3lib_div::getIndpEnv('TYPO3_SITE_URL'),
+                       'PATH_typo3' => $pathTYPO3,
+                       'PATH_typo3_enc' => rawurlencode($pathTYPO3),
+                       'username' => htmlspecialchars($GLOBALS['BE_USER']->user['username']),
+                       'uniqueID' => t3lib_div::shortMD5(uniqid('')),
+                       'securityLevel' => $this->loginSecurityLevel,
+                       'TYPO3_mainDir' => TYPO3_mainDir,
+                       'pageModule' => $pageModule,
+                       'condensedMode' => $GLOBALS['BE_USER']->uc['condensedMode'] ? 1 : 0 ,
+                       'inWorkspace' => $GLOBALS['BE_USER']->workspace !== 0 ? 1 : 0,
+                       'workspaceFrontendPreviewEnabled' => $GLOBALS['BE_USER']->user['workspace_preview'] ? 1 : 0,
+                       'veriCode' => $GLOBALS['BE_USER']->veriCode(),
+                       'denyFileTypes' => PHP_EXTENSIONS_DEFAULT,
+                       'moduleMenuWidth' => $this->menuWidth - 1,
+                       'topBarHeight' => (isset($GLOBALS['TBE_STYLES']['dims']['topFrameH']) ? intval($GLOBALS['TBE_STYLES']['dims']['topFrameH']) : 30),
+                       'showRefreshLoginPopup' => isset($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) ? intval($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) : FALSE,
+                       'listModulePath' => t3lib_extMgm::isLoaded('list') ? t3lib_extMgm::extRelPath('list') . 'mod1/' : '',
+                       'debugInWindow' => $GLOBALS['BE_USER']->uc['debugInWindow'] ? 1 : 0,
+                       'ContextHelpWindows' => array(
+                               'width' => 600,
+                               'height' => 400
+                       )
+               );
+
+               $t3LLLcore = array(
+                       'waitTitle' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_logging_in') ,
+                       'refresh_login_failed' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_failed'),
+                       'refresh_login_failed_message' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_failed_message'),
+                       'refresh_login_title' => sprintf($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_title'), htmlspecialchars($GLOBALS['BE_USER']->user['username'])),
+                       'login_expired' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.login_expired'),
+                       'refresh_login_username' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_username'),
+                       'refresh_login_password' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_password'),
+                       'refresh_login_emptyPassword' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_emptyPassword'),
+                       'refresh_login_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_button'),
+                       'refresh_logout_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_logout_button'),
+                       'please_wait' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.please_wait'),
+                       'loadingIndicator' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:loadingIndicator'),
+                       'be_locked' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.be_locked'),
+                       'refresh_login_countdown_singular' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_countdown_singular'),
+                       'refresh_login_countdown' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_countdown'),
+                       'login_about_to_expire' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.login_about_to_expire'),
+                       'login_about_to_expire_title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.login_about_to_expire_title'),
+                       'refresh_login_refresh_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_login_refresh_button'),
+                       'refresh_direct_logout_button' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:mess.refresh_direct_logout_button'),
+                       'tabs_closeAll' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.closeAll'),
+                       'tabs_closeOther' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.closeOther'),
+                       'tabs_close' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.close'),
+                       'tabs_openInBrowserWindow' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:tabs.openInBrowserWindow'),
+                       'donateWindow_title' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.title'),
+                       'donateWindow_message' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.message'),
+                       'donateWindow_button_donate' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.button_donate'),
+                       'donateWindow_button_disable' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.button_disable'),
+                       'donateWindow_button_postpone' => $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xml:donateWindow.button_postpone'),
+               );
+
+                       // Convert labels/settings back to UTF-8 since json_encode() only works with UTF-8:
+               if ($GLOBALS['LANG']->charSet !== 'utf-8') {
+                       $t3Configuration['username'] = $GLOBALS['LANG']->csConvObj->conv($t3Configuration['username'], $GLOBALS['LANG']->charSet, 'utf-8');
+                       $GLOBALS['LANG']->csConvObj->convArray($t3LLLcore, $GLOBALS['LANG']->charSet, 'utf-8');
+                       $GLOBALS['LANG']->csConvObj->convArray($t3LLLfileUpload, $GLOBALS['LANG']->charSet, 'utf-8');
+               }
+
+               $js .= '
+               TYPO3.configuration = ' . json_encode($t3Configuration) . ';
+               TYPO3.LLL = {
+                       core : ' . json_encode($t3LLLcore) . '
+               };
+
+               /**
+                * TypoSetup object.
+                */
+               function typoSetup()    {       //
+                       this.PATH_typo3 = TYPO3.configuration.PATH_typo3;
+                       this.PATH_typo3_enc = TYPO3.configuration.PATH_typo3_enc;
+                       this.username = TYPO3.configuration.username;
+                       this.uniqueID = TYPO3.configuration.uniqueID;
+                       this.navFrameWidth = 0;
+                       this.securityLevel = TYPO3.configuration.securityLevel;
+                       this.veriCode = TYPO3.configuration.veriCode;
+                       this.denyFileTypes = TYPO3.configuration.denyFileTypes;
+               }
+               var TS = new typoSetup();
+                       //backwards compatibility
+               ';
+               return $js;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Controller/PreviewController.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Controller/PreviewController.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Controller/ReviewController.php b/typo3/sysext/workspaces/Classes/Controller/ReviewController.php
new file mode 100644 (file)
index 0000000..4775cd0
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 Steffen Ritter (steffen@typo3.org)
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *  A copy is found in the textfile GPL.txt and important notices to the license
+ *  from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+class Tx_Workspaces_Controller_ReviewController extends Tx_Workspaces_Controller_AbstractController {
+
+       /**
+        * Renders the review module user dependent with all workspaces.
+        * The module will show all records of one workspace.
+        *
+        * @return void
+        */
+       public function indexAction() {
+               $wsService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+               $this->view->assign('showGrid', !($GLOBALS['BE_USER']->workspace === 0 && !$GLOBALS['BE_USER']->isAdmin()));
+               $this->view->assign('showAllWorkspaceTab', $GLOBALS['BE_USER']->isAdmin());
+               $this->view->assign('pageUid', t3lib_div::_GP('id'));
+               $this->view->assign('showLegend', !($GLOBALS['BE_USER']->workspace === 0 && !$GLOBALS['BE_USER']->isAdmin()));
+
+               $wsList = $wsService->getAvailableWorkspaces();
+               $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+
+               if (!$GLOBALS['BE_USER']->isAdmin()) {
+                       $wsCur = array($activeWorkspace => true);
+                       $wsList = array_intersect_key($wsList, $wsCur);
+               } else {
+                       $wsList = $wsService->getAvailableWorkspaces();
+                       if (strlen(t3lib_div::_GP('workspace'))) {
+                               $switchWs = (int) t3lib_div::_GP('workspace');
+                               if (in_array($switchWs, array_keys($wsList))) {
+                                       $activeWorkspace = $switchWs;
+                               } elseif ($switchWs == tx_Workspaces_Service_Workspaces::SELECT_ALL_WORKSPACES) {
+                                       $activeWorkspace = tx_Workspaces_Service_Workspaces::SELECT_ALL_WORKSPACES;
+                               }
+                       }
+               }
+               $this->view->assign('workspaceList', $wsList);
+               $this->view->assign('activeWorkspaceUid', $activeWorkspace);
+               $GLOBALS['BE_USER']->setAndSaveSessionData('tx_workspace_activeWorkspace', $activeWorkspace);
+       }
+
+       /**
+        * Renders the review module for admins.
+        * The module will show all records of all workspaces.
+        *
+        * @return void
+        */
+       public function fullIndexAction() {
+               if (!$GLOBALS['BE_USER']->isAdmin()) {
+                       $this->redirect('index');
+               } else {
+                       $wsService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+                       $this->view->assign('pageUid', t3lib_div::_GP('id'));
+                       $this->view->assign('showGrid', true);
+                       $this->view->assign('showLegend', true);
+                       $this->view->assign('showAllWorkspaceTab', $GLOBALS['BE_USER']->isAdmin());
+                       $this->view->assign('workspaceList', $wsService->getAvailableWorkspaces());
+                       $this->view->assign('activeWorkspaceUid', tx_Workspaces_Service_Workspaces::SELECT_ALL_WORKSPACES);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('tx_workspace_activeWorkspace', tx_Workspaces_Service_Workspaces::SELECT_ALL_WORKSPACES);
+               }
+       }
+
+       /**
+        * Renders the review module for a single page. This is used within the
+        * workspace-preview frame.
+        *
+        * @return void
+        */
+       public function singleIndexAction() {
+
+               $wsService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+               $wsList = $wsService->getAvailableWorkspaces();
+               $activeWorkspace = $GLOBALS['BE_USER']->workspace;
+
+               $wsCur = array($activeWorkspace => true);
+               $wsList = array_intersect_key($wsList, $wsCur);
+
+               $this->view->assign('pageUid', t3lib_div::_GP('id'));
+               $this->view->assign('showGrid', true);
+               $this->view->assign('showAllWorkspaceTab', false);
+               $this->view->assign('workspaceList', $wsList);
+       }
+
+
+       /**
+        * Initializes the controller before invoking an action method.
+        *
+        * @return void
+        */
+       protected function initializeAction() {
+               parent::initializeAction();
+
+               $this->pageRenderer->loadExtJS();
+               $this->pageRenderer->enableExtJSQuickTips();
+               $this->pageRenderer->enableExtJsDebug();
+
+                       // Load  JavaScript:
+               $this->pageRenderer->addExtDirectCode();
+               $this->pageRenderer->addJsFile($this->backPath . 'ajax.php?ajaxID=ExtDirect::getAPI&namespace=TYPO3.Workspaces', NULL, FALSE);
+
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/flashmessages.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/Ext.grid.RowExpander.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/Ext.app.SearchField.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/Ext.ux.FitToParent.js');
+
+               $resourcePath = t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/JavaScript/';
+
+               $this->pageRenderer->addCssFile($resourcePath . 'gridfilters/css/GridFilters.css');
+               $this->pageRenderer->addCssFile($resourcePath . 'gridfilters/css/RangeMenu.css');
+
+               $jsFiles = array(
+                       'gridfilters/menu/RangeMenu.js',
+                       'gridfilters/menu/ListMenu.js',
+                       'gridfilters/GridFilters.js',
+                       'gridfilters/filter/Filter.js',
+                       'gridfilters/filter/StringFilter.js',
+                       'gridfilters/filter/DateFilter.js',
+                       'gridfilters/filter/ListFilter.js',
+                       'gridfilters/filter/NumericFilter.js',
+                       'gridfilters/filter/BooleanFilter.js',
+                       'gridfilters/filter/BooleanFilter.js',
+
+                       'configuration.js',
+                       'helpers.js',
+                       'actions.js',
+                       'component.js',
+                       'toolbar.js',
+                       'grid.js',
+                       'workspaces.js',
+               );
+
+               foreach ($jsFiles as $jsFile) {
+                       $this->pageRenderer->addJsFile($resourcePath . $jsFile);
+               }
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Controller/ReviewController.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Controller/ReviewController.php']);
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/AbstractHandler.php b/typo3/sysext/workspaces/Classes/ExtDirect/AbstractHandler.php
new file mode 100644 (file)
index 0000000..e55f724
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 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!
+***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage ExtDirect
+ */
+abstract class tx_Workspaces_ExtDirect_AbstractHandler {
+       /**
+        * Gets the current workspace ID.
+        *
+        * @return integer The current workspace ID
+        */
+       protected function getCurrentWorkspace() {
+               if ($GLOBALS['BE_USER']->isAdmin()) {
+                       $workspaceId = $GLOBALS['BE_USER']->getSessionData('tx_workspace_activeWorkspace');
+               } else {
+                       $workspaceId = $GLOBALS['BE_USER']->workspace;
+               }
+
+               return $workspaceId;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/AbstractHandler.php'])   {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/AbstractHandler.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php b/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php
new file mode 100644 (file)
index 0000000..3d935a3
--- /dev/null
@@ -0,0 +1,504 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 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!
+***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage ExtDirect
+ */
+class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_AbstractHandler {
+       /**
+        * @var Tx_Workspaces_Service_Stages
+        */
+       protected $stageService;
+
+       /**
+        * Creates this object.
+        */
+       public function __construct() {
+               $this->stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+       }
+
+       /**
+        * Generates a workspace preview link.
+        *
+        * @param integer $uid The ID of the record to be linked
+        * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
+        */
+       public function generateWorkspacePreviewLink($uid) {
+               $ttlHours = intval($GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours'));
+               $ttlHours = ($ttlHours ? $ttlHours : 24*2) * 3600;
+               $linkParams = array(
+                       'ADMCMD_prev'   => t3lib_BEfunc::compilePreviewKeyword('', $GLOBALS['BE_USER']->user['uid'], $ttlHours, $this->getCurrentWorkspace()),
+                       'id'                    => $uid
+               );
+               return t3lib_BEfunc::getViewDomain($uid) . '/index.php?' . t3lib_div::implodeArrayForUrl('', $linkParams);
+       }
+
+       /**
+        * Swaps a sisngle record.
+        *
+        * @param string $table
+        * @param integer $t3ver_oid
+        * @param integer $orig_uid
+        * @return void
+        *
+        * @todo What about reporting errors back to the ExtJS interface? /olly/
+        */
+       public function swapSingleRecord($table, $t3ver_oid, $orig_uid) {
+               $cmd[$table][$t3ver_oid]['version'] = array(
+                       'action' => 'swap',
+                       'swapWith' => $orig_uid,
+                       'swapIntoWS' => 1
+               );
+
+               $tce = t3lib_div::makeInstance ('t3lib_TCEmain');
+               $tce->start(array(), $cmd);
+               $tce->process_cmdmap();
+       }
+
+       /**
+        * Deletes a single record.
+        *
+        * @param string $table
+        * @param integer $uid
+        * @return void
+        *
+        * @todo What about reporting errors back to the ExtJS interface? /olly/
+        */
+       public function deleteSingleRecord($table, $uid) {
+               $cmd[$table][$uid]['version'] = array(
+                       'action' => 'clearWSID'
+               );
+
+               $tce = t3lib_div::makeInstance ('t3lib_TCEmain');
+               $tce->start(array(), $cmd);
+               $tce->process_cmdmap();
+       }
+
+       /**
+        * Generates a view link for a page.
+        *
+        * @param string $pid
+        * @return void
+        */
+       public function viewSingleRecord($pid) {
+               return t3lib_BEfunc::viewOnClick($pid);
+       }
+
+
+       /**
+        * Saves the selected columns to be shown to the preferences of the current backend user.
+        *
+        * @param object $model
+        * @return void
+        */
+       public function saveColumnModel($model) {
+               $data = array();
+               foreach ($model as $column) {
+                       $data[$column->column] = array(
+                               'position'  => $column->position,
+                               'hidden'   => $column->hidden
+                       );
+               }
+               $GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['columns'] = $data;
+               $GLOBALS['BE_USER']->writeUC();
+       }
+
+       public function loadColumnModel() {
+               if(is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['columns'])) {
+                       return $GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['columns'];
+               } else {
+                       return array();
+               }
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to the next stage.
+        *
+        * @param string $table
+        * @param integer $uid
+        * @param integer $t3ver_oid
+        * @return array
+        */
+       public function sendToNextStageWindow($table, $uid, $t3ver_oid) {
+               $elementRecord = t3lib_BEfunc::getRecord($table, $uid);
+
+               if(is_array($elementRecord)) {
+                       $stageId = $elementRecord['t3ver_stage'];
+                       $nextStage = $this->getStageService()->getNextStage($stageId);
+
+                       $result = $this->getSentToStageWindow($nextStage['uid']);
+                       $result['affects'] = array(
+                               'table' => $table,
+                               'nextStage' => $nextStage['uid'],
+                               't3ver_oid' => $t3ver_oid,
+                               'uid' => $uid,
+                       );
+               } else {
+                       $result = array(
+                               'error' => array(
+                                       'code' => 1287264776,
+                                       'message' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.sendToNextStage.noRecordFound'),
+                               ),
+                               'success' => FALSE,
+                       );
+               }
+
+               return $result;
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to the previous stage.
+        *
+        * @param string $table
+        * @param integer $uid
+        * @return array
+        */
+       public function sendToPrevStageWindow($table, $uid) {
+               $elementRecord = t3lib_BEfunc::getRecord($table, $uid);
+
+               if(is_array($elementRecord)) {
+                       $stageId = intval($elementRecord['t3ver_stage']);
+                       if ($stageId !== Tx_Workspaces_Service_Stages::STAGE_EDIT_ID) {
+                               $prevStage = $this->getStageService()->getPrevStage($stageId);
+
+                               $result = $this->getSentToStageWindow($prevStage['uid']);
+                               $result['affects'] = array(
+                                       'table' => $table,
+                                       'uid' => $uid,
+                                       'nextStage' => $prevStage['uid'],
+                               );
+                       } else {
+                                       // element is already in edit stage, there is no prev stage - return an error message
+                               $result = array(
+                                       'error' => array(
+                                               'code' => 1287264746,
+                                               'message' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.sendToPrevStage.noPreviousStage'),
+                                       ),
+                                       'success' => FALSE,
+                               );
+                       }
+               } else {
+                       $result = array(
+                               'error' => array(
+                                       'code' => 1287264765,
+                                       'message' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.sendToNextStage.noRecordFound'),
+                               ),
+                               'success' => FALSE,
+                       );
+               }
+
+               return $result;
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to a specific stage.
+        *
+        * @param integer $nextStageId
+        * @return array
+        */
+       public function sendToSpecificStageWindow($nextStageId) {
+               $result = $this->getSentToStageWindow($nextStageId);
+               $result['affects'] = array(
+                       'nextStage' => $nextStageId,
+               );
+
+               return $result;
+       }
+
+       /**
+        * Gets a merged variant of recipient defined by uid and custom ones.
+        *
+        * @param array list of recipients
+        * @param string given user string of additional recipients
+        * @return array
+        */
+       public function getRecipientList(array $uidOfRecipients, $additionalRecipients) {
+               $finalRecipients = array();
+
+               $recipients = array();
+               foreach ($uidOfRecipients as $userUid) {
+                       $beUserRecord = t3lib_befunc::getRecord('be_users',intval($userUid));
+                       if(is_array($beUserRecord) && $beUserRecord['email'] != '') {
+                               $recipients[] = $beUserRecord['email'];
+                       }
+               }
+
+               if ($additionalRecipients != '') {
+                       $additionalRecipients = t3lib_div::trimExplode("\n", $additionalRecipients, TRUE);
+               } else {
+                       $additionalRecipients = array();
+               }
+
+               $finalRecipients = array_unique(
+                       array_merge($recipients, $additionalRecipients)
+               );
+
+               return $finalRecipients;
+       }
+
+       /**
+        * Gets an object with this structure:
+        *
+        *      affects: object
+        *              table
+        *              t3ver_oid
+        *              nextStage
+        *              uid
+        *      receipients: array with uids
+        *      additional: string
+        *      comments: string
+        *
+        * @param stdObject $parameters
+        * @return array
+        */
+       public function sendToNextStageExecute(stdClass $parameters) {
+               $cmdArray = array();
+               $recipients = array();
+
+               $setStageId = $parameters->affects->nextStage;
+               $comments = $parameters->comments;
+               $table = $parameters->affects->table;
+               $uid = $parameters->affects->uid;
+               $t3ver_oid = $parameters->affects->t3ver_oid;
+               $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional);
+
+               if ($setStageId == Tx_Workspaces_Service_Stages::STAGE_PUBLISH_EXECUTE_ID) {
+                       $cmdArray[$table][$t3ver_oid]['version']['action'] = 'swap';
+                       $cmdArray[$table][$t3ver_oid]['version']['swapWith'] = $uid;
+                       $cmdArray[$table][$t3ver_oid]['version']['comment'] = $comments;
+                       $cmdArray[$table][$t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
+               } else {
+                       $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+                       $cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
+                       $cmdArray[$table][$uid]['version']['comment'] = $comments;
+                       $cmdArray[$table][$uid]['version']['notificationAlternativeRecipients'] = $recipients;
+               }
+
+               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+               $tce->start(array(), $cmdArray);
+               $tce->process_cmdmap();
+
+               $result = array(
+                       'success' => TRUE,
+               );
+
+               return $result;
+       }
+
+       /**
+        * Gets an object with this structure:
+        *
+        *      affects: object
+        *              table
+        *              t3ver_oid
+        *              nextStage
+        *      receipients: array with uids
+        *      additional: string
+        *      comments: string
+        *
+        * @param stdObject $parameters
+        * @return array
+        */
+       public function sendToPrevStageExecute(stdClass $parameters) {
+               $cmdArray = array();
+               $recipients = array();
+
+               $setStageId = $parameters->affects->nextStage;
+               $comments = $parameters->comments;
+               $table = $parameters->affects->table;
+               $uid = $parameters->affects->uid;
+               $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional);
+
+               $cmdArray[$table][$uid]['version']['action'] = 'setStage';
+               $cmdArray[$table][$uid]['version']['stageId'] = $setStageId;
+               $cmdArray[$table][$uid]['version']['comment'] = $comments;
+               $cmdArray[$table][$uid]['version']['notificationAlternativeRecipients'] = $recipients;
+
+               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+               $tce->start(array(), $cmdArray);
+               $tce->process_cmdmap();
+
+               $result = array(
+                       'success' => TRUE,
+               );
+
+               return $result;
+       }
+
+       /**
+        * Gets an object with this structure:
+        *
+        *      affects: object
+        *              elements: array
+        *                      0: object
+        *                              table
+        *                              t3ver_oid
+        *                              uid
+        *                      1: object
+        *                              table
+        *                              t3ver_oid
+        *                              uid
+        *              nextStage
+        *      receipients: array with uids
+        *      additional: string
+        *      comments: string
+        *
+        * @param stdObject $parameters
+        * @return array
+        */
+       public function sendToSpecificStageExecute(stdClass $parameters) {
+               $cmdArray = array();
+               $recipients = array();
+
+               $setStageId = $parameters->affects->nextStage;
+               $comments = $parameters->comments;
+               $elements = $parameters->affects->elements;
+               $recipients = $this->getRecipientList($parameters->receipients, $parameters->additional);
+
+               foreach($elements as $key=>$element) {
+                       if ($setStageId == Tx_Workspaces_Service_Stages::STAGE_PUBLISH_EXECUTE_ID) {
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['action'] = 'swap';
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['swapWith'] = $element->uid;
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['comment'] = $comments;
+                               $cmdArray[$element->table][$element->t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
+                       } else {
+                               $cmdArray[$element->table][$element->uid]['version']['action'] = 'setStage';
+                               $cmdArray[$element->table][$element->uid]['version']['stageId'] = $setStageId;
+                               $cmdArray[$element->table][$element->uid]['version']['comment'] = $comments;
+                               $cmdArray[$element->table][$element->uid]['version']['notificationAlternativeRecipients'] = $recipients;
+                       }
+               }
+
+               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+               $tce->start(array(), $cmdArray);
+               $tce->process_cmdmap();
+
+               $result = array(
+                       'success' => TRUE,
+               );
+
+               return $result;
+       }
+
+       /**
+        * Gets the dialog window to be displayed before a record can be sent to a stage.
+        *
+        * @param  $nextStageId
+        * @return array
+        */
+       protected function getSentToStageWindow($nextStageId) {
+               $stageTitle = $this->getStageService()->getStageTitle($nextStageId);
+               $result = array(
+                       'title' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:actionSendToStage'),
+                       'items' => array(
+                               array(
+                                       'xtype' => 'panel',
+                                       'bodyStyle' => 'margin-bottom: 7px; border: none;',
+                                       'html' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:window.sendToNextStageWindow.itemsWillBeSentTo') . $stageTitle,
+                               ),
+                               array(
+                                       'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:window.sendToNextStageWindow.sendMailTo'),
+                                       'xtype' => 'checkboxgroup',
+                                       'itemCls' => 'x-check-group-alt',
+                                       'columns' => 1,
+                                       'items' => array(
+                                               $this->getReceipientsOfStage($nextStageId)
+                                       )
+                               ),
+                               array(
+                                       'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:window.sendToNextStageWindow.additionalRecipients'),
+                                       'name' => 'additional',
+                                       'xtype' => 'textarea',
+                                       'width' => 250,
+                               ),
+                               array(
+                                       'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:window.sendToNextStageWindow.comments'),
+                                       'name' => 'comments',
+                                       'xtype' => 'textarea',
+                                       'width' => 250,
+                                       'value' => $this->getDefaultCommentOfStage($nextStageId),
+                               ),
+                       )
+               );
+
+               return $result;
+       }
+
+       /**
+        * Gets all assigned recipients of a particular stage.
+        * 
+        * @param integer $stage
+        * @return array
+        */
+       protected function getReceipientsOfStage($stage) {
+               $result = array();
+
+               $recipients = $this->getStageService()->getResponsibleBeUser($stage);
+
+               foreach ($recipients as $id => $name) {
+                       $result[] = array(
+                               'boxLabel' => $name,
+                               'name' => 'receipients-' . $id,
+                               'checked' => TRUE,
+                       );
+               }
+
+               return $result;
+       }
+
+       /**
+        * Gets the default comment of a particular stage.
+        *
+        * @param integer $stage
+        * @return string
+        */
+       protected function getDefaultCommentOfStage($stage) {
+               $result = $this->getStageService()->getPropertyOfCurrentWorkspaceStage($stage, 'default_mailcomment');
+
+               return $result;
+       }
+
+       /**
+        * Gets an instance of the Stage service.
+        *
+        * @return Tx_Workspaces_Service_Stages
+        */
+       protected function getStageService() {
+               if (!isset($this->stageService)) {
+                       $this->stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+               }
+               return $this->stageService;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/ActionHandler.php'])     {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/ActionHandler.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/MassActionHandler.php b/typo3/sysext/workspaces/Classes/ExtDirect/MassActionHandler.php
new file mode 100644 (file)
index 0000000..17668ec
--- /dev/null
@@ -0,0 +1,236 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 1999-2010 Kasper Skårhøj (kasperYYYY@typo3.com)
+ *  (c) 2010 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!
+ ***************************************************************/
+
+/**
+ * Class encapsulates all actions which are triggered for all elements within the current workspace.
+ *
+ * @author Kasper Skårhøj (kasperYYYY@typo3.com)
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage ExtDirect
+ */
+class tx_Workspaces_ExtDirect_MassActionHandler extends tx_Workspaces_ExtDirect_AbstractHandler {
+       const MAX_RECORDS_TO_PROCESS = 30;
+
+       /**
+        * Get list of available mass workspace actions.
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getMassStageActions($parameter) {
+               $actions = array();
+               $currentWorkspace = $this->getCurrentWorkspace();
+
+                       // in case we're working within "All Workspaces" we can't provide Mass Actions
+               if ($currentWorkspace != tx_Workspaces_Service_Workspaces::SELECT_ALL_WORKSPACES) {
+                       $publishAccess = $GLOBALS['BE_USER']->workspacePublishAccess();
+                       if ($publishAccess && !($GLOBALS['BE_USER']->workspaceRec['publish_access'] & 1)) {
+                               $actions[] = array('action' => 'publish', 'title' => 'Publish' //$GLOBALS['LANG']->getLL('label_doaction_publish'));
+                               );
+                               if ($GLOBALS['BE_USER']->workspaceSwapAccess()) {
+                                       $actions[] = array('action' => 'swap', 'title' => 'Swap' //$GLOBALS['LANG']->getLL('label_doaction_swap')
+                                       );
+                               }
+                       }
+
+                       if ($currentWorkspace !== tx_Workspaces_Service_Workspaces::LIVE_WORKSPACE_ID) {
+                               $actions[] = array('action' => 'release', 'title' => 'Release' // $GLOBALS['LANG']->getLL('label_doaction_release'));
+                               );
+                       }
+               }
+
+               $result = array(
+                       'total' => count($actions),
+                       'data' => $actions
+               );
+               return $result;
+       }
+
+       /**
+        * Publishes the current workspace.
+        *
+        * @param stdclass $parameters
+        * @return array
+        */
+       public function publishWorkspace(stdclass $parameters) {
+               $result = array(
+                       'init' => false,
+                       'total' => 0,
+                       'processed' => 0,
+                       'error' => false
+               );
+
+               try {
+                       if ($parameters->init) {
+                               $cnt = $this->initPublishData($this->getCurrentWorkspace(), $parameters->swap);
+                               $result['total'] = $cnt;
+                       } else {
+                               $result['processed'] = $this->processData($this->getCurrentWorkspace());
+                               $result['total'] = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_total');
+                       }
+               } catch (Exception $e) {
+                       $result['error'] = $e->getMessage();
+               }
+               return $result;
+       }
+
+       /**
+        * Flushes the current workspace.
+        *
+        * @param stdclass $parameters
+        * @return array
+        */
+       public function flushWorkspace(stdclass $parameters) {
+               $result = array(
+                       'init' => false,
+                       'total' => 0,
+                       'processed' => 0,
+                       'error' => false
+               );
+
+               try {
+                       if ($parameters->init) {
+                               $cnt = $this->initFlushData($this->getCurrentWorkspace());
+                               $result['total'] = $cnt;
+                       } else {
+                               $result['processed'] = $this->processData($this->getCurrentWorkspace());
+                               $result['total'] = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_total');
+                       }
+               } catch (Exception $e) {
+                       $result['error'] = $e->getMessage();
+               }
+               return $result;
+       }
+
+       /**
+        * Initializes the command map to be used for publishing.
+        *
+        * @param integer $workspace
+        * @param boolean $swap
+        * @return integer
+        */
+       protected function initPublishData($workspace, $swap) {
+               $workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+                       // workspace might be -98 a.k.a "All Workspaces but that's save here
+               $publishData = $workspaceService->getCmdArrayForPublishWS($workspace, $swap);
+               $recordCount = 0;
+               foreach ($publishData as $table => $recs) {
+                       $recordCount += count($recs);
+               }
+               if ($recordCount > 0) {
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', $publishData);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_total', $recordCount);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_processed', 0);
+               }
+               return $recordCount;
+       }
+
+       /**
+        * Initializes the command map to be used for flushing.
+        *
+        * @param integer $workspace
+        * @return integer
+        */
+       protected function initFlushData($workspace) {
+               $workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+                       // workspace might be -98 a.k.a "All Workspaces but that's save here
+               $flushData = $workspaceService->getCmdArrayForFlushWS($workspace);
+               $recordCount = 0;
+               foreach ($flushData as $table => $recs) {
+                       $recordCount += count($recs);
+               }
+               if ($recordCount > 0) {
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', $flushData);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_total', $recordCount);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_processed', 0);
+               }
+               return $recordCount;
+       }
+
+       /**
+        * Processes the data.
+        *
+        * @param integer $workspace
+        * @return integer
+        */
+       protected function processData($workspace) {
+               $processData = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction');
+               $recordsProcessed = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_processed');
+               $limitedCmd = array();
+               $numRecs = 0;
+
+               foreach ($processData as $table => $recs) {
+                       foreach ($recs as $key => $value) {
+                               $numRecs++;
+                               $limitedCmd[$table][$key] = $value;
+                               if ($numRecs == self::MAX_RECORDS_TO_PROCESS) {
+                                       break;
+                               }
+                       }
+                       if ($numRecs == self::MAX_RECORDS_TO_PROCESS) {
+                               break;
+                       }
+               }
+
+               if ($numRecs == 0) {
+                               // All done
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', null);
+                       $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_total', 0);
+               } else {
+                               // Execute the commands:
+                       $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+                       $tce->stripslashes_values = 0;
+                       $tce->start(array(), $limitedCmd);
+                       $tce->process_cmdmap();
+
+                       $errors = $tce->errorLog;
+                       if (count($errors) > 0) {
+                               throw new Exception(implode(', ', $errors));
+                       } else {
+                                       // Unset processed records
+                               foreach ($limitedCmd as $table => $recs) {
+                                       foreach ($recs as $key => $value) {
+                                               $recordsProcessed++;
+                                               unset($processData[$table][$key]);
+                                       }
+                               }
+                               $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction', $processData);
+                               $GLOBALS['BE_USER']->setAndSaveSessionData('workspaceMassAction_processed', $recordsProcessed);
+                       }
+               }
+
+               return $recordsProcessed;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/MassActionHandler.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/MassActionHandler.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/Server.php b/typo3/sysext/workspaces/Classes/ExtDirect/Server.php
new file mode 100644 (file)
index 0000000..ca2826d
--- /dev/null
@@ -0,0 +1,206 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 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!
+***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage ExtDirect
+ */
+class tx_Workspaces_ExtDirect_Server extends tx_Workspaces_ExtDirect_AbstractHandler {
+       /**
+        * Get List of workspace changes
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getWorkspaceInfos($parameter) {
+                       // To avoid too much work we use -1 to indicate that every page is relevant
+               $pageId = $parameter->id > 0 ? $parameter->id : -1;
+
+               $wslibObj = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+               $versions = $wslibObj->selectVersionsInWorkspace($this->getCurrentWorkspace(), 0, -99, $pageId, $parameter->depth);
+
+               $workspacesService = t3lib_div::makeInstance('tx_Workspaces_Service_GridData');
+               $data = $workspacesService->generateGridListFromVersions($versions, $parameter);
+               return $data;
+       }
+
+       /**
+        * Get List of available workspace actions
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getStageActions($parameter) {
+               $currentWorkspace = $this->getCurrentWorkspace();
+               $stages = array();
+               if ($currentWorkspace != tx_Workspaces_Service_Workspaces::SELECT_ALL_WORKSPACES) {
+                       $stagesService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+                       $stages = $stagesService->getStagesForWSUser();
+               }
+
+               $data = array(
+                       'total' => count($stages),
+                       'data' => $stages
+               );
+               return $data;
+       }
+
+       /**
+        * Fetch futher information to current selected worspace record.
+        *
+        * @param object $parameter
+        * @return array $data
+        */
+       public function getRowDetails($parameter) {
+               global $TCA,$BE_USER;
+               $diffReturnArray = array();
+               $liveReturnArray = array();
+               /**
+                * @todo  make sure this would work in local extension installation too
+                */
+               $backPath = isset($GLOBALS['BACK_PATH']) ? $GLOBALS['BACK_PATH'] : '../../../' . TYPO3_mainDir;
+
+               $t3lib_diff = t3lib_div::makeInstance('t3lib_diff');
+               $stagesService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+
+               $liveRecord = t3lib_BEfunc::getRecord($parameter->table, $parameter->t3ver_oid);
+               $versionRecord = t3lib_BEfunc::getRecord($parameter->table, $parameter->uid);
+               $icon_Live = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($parameter->table, $liveRecord);
+               $icon_Workspace = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($parameter->table, $versionRecord);
+               $stagePosition = $stagesService->getPositionOfCurrentStage($parameter->stage);
+
+               $fieldsOfRecords = array_keys($liveRecord);
+
+               // get field list from TCA configuration, if available
+               if ($TCA[$parameter->table]) {
+                       if ($TCA[$parameter->table]['interface']['showRecordFieldList']) {
+                               $fieldsOfRecords = $TCA[$parameter->table]['interface']['showRecordFieldList'];
+                               $fieldsOfRecords = t3lib_div::trimExplode(',',$fieldsOfRecords,1);
+                       }
+               }
+
+               foreach ($fieldsOfRecords as $fieldName) {
+                               // check for exclude fields
+                       if ($GLOBALS['BE_USER']->isAdmin() || ($TCA[$parameter->table]['columns'][$fieldName]['exclude'] == 0) || t3lib_div::inList($BE_USER->groupData['non_exclude_fields'],$parameter->table.':'.$fieldName)) {
+                                       // call diff class only if there is a difference
+                               if (strcmp($liveRecord[$fieldName],$versionRecord[$fieldName]) !== 0) {
+                                               // Select the human readable values before diff
+                                       $liveRecord[$fieldName] = t3lib_BEfunc::getProcessedValue($parameter->table,$fieldName,$liveRecord[$fieldName],0,1);
+                                       $versionRecord[$fieldName] = t3lib_BEfunc::getProcessedValue($parameter->table,$fieldName,$versionRecord[$fieldName],0,1);
+
+                                       $fieldTitle = $GLOBALS['LANG']->sL(t3lib_BEfunc::getItemLabel($parameter->table, $fieldName));
+
+                                       if ($TCA[$parameter->table]['columns'][$fieldName]['config']['type'] == 'group' && $TCA[$parameter->table]['columns'][$fieldName]['config']['internal_type'] == 'file') {
+                                               $versionThumb = t3lib_BEfunc::thumbCode($versionRecord, $parameter->table, $fieldName, $backPath);
+                                               $liveThumb = t3lib_BEfunc::thumbCode($liveRecord, $parameter->table, $fieldName, $backPath);
+
+                                               $diffReturnArray[] = array(
+                                                       'label' => $fieldTitle,
+                                                       'content' => $versionThumb
+                                               );
+                                               $liveReturnArray[] = array(
+                                                       'label' => $fieldTitle,
+                                                       'content' => $liveThumb
+                                               );
+                                       } else {
+                                               $diffReturnArray[] = array(
+                                                       'label' => $fieldTitle,
+                                                       'content' => $t3lib_diff->makeDiffDisplay($liveRecord[$fieldName], $versionRecord[$fieldName]) // call diff class to get diff
+                                               );
+                                               $liveReturnArray[] = array(
+                                                       'label' => $fieldTitle,
+                                                       'content' => $liveRecord[$fieldName]
+                                               );
+                                       }
+                               }
+                       }
+               }
+
+               $commentsForRecord = $this->getCommentsForRecord($parameter->uid, $parameter->table);
+
+               return array(
+                       'total' => 1,
+                       'data' => array(
+                               array(
+                                       'diff' => $diffReturnArray,
+                                       'live_record' => $liveReturnArray,
+                                       'path_Live' => $parameter->path_Live,
+                                       'label_Stage' => $parameter->label_Stage,
+                                       'stage_position' => $stagePosition['position'],
+                                       'stage_count' => $stagePosition['count'],
+                                       'comments' => $commentsForRecord,
+                                       'icon_Live' => $icon_Live,
+                                       'icon_Workspace' => $icon_Workspace
+                               )
+                       )
+               );
+       }
+
+       /**
+        * Gets an array with all sys_log entries and their comments for the given record uid and table
+        *
+        * @param integer $uid uid of changed element to search for in log
+        * @return string $table table name
+        */
+       public function getCommentsForRecord($uid, $table) {
+               $stagesService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+               $sysLogReturnArray = array();
+
+               $sysLogRows = $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($uid),
+                               '',
+                               'tstamp DESC'
+               );
+
+               foreach($sysLogRows as $sysLogRow)      {
+                       $sysLogEntry = array();
+                       $data = unserialize($sysLogRow['log_data']);
+                       $beUserRecord = t3lib_BEfunc::getRecord('be_users', $sysLogRow['userid']);
+
+                       $sysLogEntry['stage_title'] = $stagesService->getStageTitle($data['stage']);
+                       $sysLogEntry['user_uid'] = $sysLogRow['userid'];
+                       $sysLogEntry['user_username'] = is_array($beUserRecord) ? $beUserRecord['username'] : '';
+                       $sysLogEntry['tstamp'] = t3lib_BEfunc::datetime($sysLogRow['tstamp']);
+                       $sysLogEntry['user_comment'] = $data['comment'];
+
+                       $sysLogReturnArray[] = $sysLogEntry;
+               }
+
+               return $sysLogReturnArray;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/Server.php'])    {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/Server.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/ToolbarMenu.php b/typo3/sysext/workspaces/Classes/ExtDirect/ToolbarMenu.php
new file mode 100644 (file)
index 0000000..1d6a6ae
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2010 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!
+***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage ExtDirect
+ */
+class tx_Workspaces_ExtDirect_ToolbarMenu {
+
+       /**
+        * @param $parameter
+        * @return array
+        */
+       public function toggleWorkspacePreviewMode($parameter) {
+               $newState = $GLOBALS['BE_USER']->user['workspace_preview'] ? '0' : '1';
+               $GLOBALS['BE_USER']->setWorkspacePreview($newState);
+
+               return array('newWorkspacePreviewState' => $newState);
+       }
+
+       /**
+        * @param $parameter
+        * @return
+        */
+       public function setWorkspace($parameter) {
+               $workspaceId = intval($parameter->workSpaceId);
+
+               $GLOBALS['BE_USER']->setWorkspace($workspaceId);
+               return array(
+                       'title' => tx_Workspaces_Service_Workspaces::getWorkspaceTitle($workspaceId),
+                       'id' => $workspaceId
+               );
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/ToolbarMenu.php'])       {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/ExtDirect/ToolbarMenu.php']);
+}
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/AutoPublish.php b/typo3/sysext/workspaces/Classes/Service/AutoPublish.php
new file mode 100644 (file)
index 0000000..88bd7f1
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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.
+ *
+ *  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!
+ ***************************************************************/
+
+/**
+ * Automatic publishing of workspaces.
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Service
+ */
+class tx_Workspaces_Service_AutoPublish {
+       /**
+        * This method is called by the Scheduler task that triggers
+        * the autopublication process
+        * It searches for workspaces whose publication date is in the past
+        * and publishes them
+        *
+        * @return      void
+        */
+       public function autoPublishWorkspaces() {
+               global $TYPO3_CONF_VARS;
+
+                       // Temporarily set admin rights
+                       // FIXME: once workspaces are cleaned up a better solution should be implemented
+               $currentAdminStatus = $GLOBALS['BE_USER']->user['admin'];
+               $GLOBALS['BE_USER']->user['admin'] = 1;
+
+                       // Select all workspaces that needs to be published / unpublished:
+               $workspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       'uid,swap_modes,publish_time,unpublish_time',
+                       'sys_workspace',
+                       'pid=0
+                               AND
+                               ((publish_time!=0 AND publish_time<=' . intval($GLOBALS['EXEC_TIME']) . ')
+                               OR (publish_time=0 AND unpublish_time!=0 AND unpublish_time<=' . intval($GLOBALS['EXEC_TIME']) . '))'.
+                               t3lib_BEfunc::deleteClause('sys_workspace')
+                       );
+
+               $workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspace');
+
+               foreach ($workspaces as $rec) {
+
+                               // First, clear start/end time so it doesn't get select once again:
+                       $fieldArray = $rec['publish_time'] != 0 ? array('publish_time' => 0) : array('unpublish_time' => 0);
+                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery('sys_workspace', 'uid=' . intval($rec['uid']), $fieldArray);
+
+                               // Get CMD array:
+                       $cmd = $workspaceService->getCmdArrayForPublishWS($rec['uid'], $rec['swap_modes'] == 1); // $rec['swap_modes']==1 means that auto-publishing will swap versions, not just publish and empty the workspace.
+
+                               // Execute CMD array:
+                       $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+                       $tce->stripslashes_values = 0;
+                       $tce->start(array(), $cmd);
+                       $tce->process_cmdmap();
+               }
+
+                       // Restore admin status
+               $GLOBALS['BE_USER']->user['admin'] = $currentAdminStatus;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/AutoPublish.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/AutoPublish.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/AutoPublishTask.php b/typo3/sysext/workspaces/Classes/Service/AutoPublishTask.php
new file mode 100644 (file)
index 0000000..9e1e892
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * This class provides a wrapper around the autopublication
+ * mechanism of workspaces, as a Scheduler task
+ *
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Service
+ */
+class tx_Workspaces_Service_AutoPublishTask extends tx_scheduler_Task {
+
+       /**
+        * Method executed from the Scheduler.
+        * Call on the workspace logic to publish workspaces whose publication date
+        * is in the past
+        *
+        * @return      void
+        */
+       public function execute() {
+               $autopubObj = t3lib_div::makeInstance('tx_Workspaces_Service_AutoPublish');
+                       // Publish the workspaces that need to be
+               $autopubObj->autoPublishWorkspaces();
+                       // There's no feedback from the publishing process,
+                       // so there can't be any failure.
+                       // TODO: This could certainly be improved.
+               return TRUE;
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/AutoPublishTask.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/AutoPublishTask.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/Befunc.php b/typo3/sysext/workspaces/Classes/Service/Befunc.php
new file mode 100644 (file)
index 0000000..0ffa2c2
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Service
+ */
+class tx_Workspaces_Service_Befunc {
+       /**
+        * Hooks into the t3lib_beFunc::viewOnClick and redirects to the workspace preview
+        * only if we're in a workspace and if the frontend-preview is disabled.
+        *
+        * @param  $pageUid
+        * @param  $backPath
+        * @param  $rootLine
+        * @param  $anchorSection
+        * @param  $viewScript
+        * @param  $additionalGetVars
+        * @param  $switchFocus
+        * @return void
+        */
+       public function preProcess($pageUid, $backPath, $rootLine, $anchorSection, &$viewScript, $additionalGetVars, $switchFocus) {
+               if ($GLOBALS['BE_USER']->workspace !== 0 && !$GLOBALS['BE_USER']->user['workspace_preview']) {
+                       $ctrl = t3lib_div::makeInstance('Tx_Workspaces_Controller_PreviewController', true);
+                       $uriBuilder = t3lib_div::makeInstance('Tx_Extbase_MVC_Web_Routing_UriBuilder');
+                       /**
+                        *  @todo BACK_PATH is not available be still needed when used during AJAX request
+                        *  @todo make sure this would work in local extension installation too
+                        */
+                       $backPath = isset($GLOBALS['BACK_PATH']) ? $GLOBALS['BACK_PATH'] :  '../../../' . TYPO3_mainDir;
+                               // @todo why do we need these additional params? the URIBuilder should add the controller, but he doesn't :(
+                       $additionalParams = '&tx_workspaces_web_workspacesworkspaces%5Bcontroller%5D=Preview&M=web_WorkspacesWorkspaces&id=';
+
+                       $viewScript = '/' . $backPath . $uriBuilder->uriFor('index', array(), $ctrl, 'workspaces', 'web_workspacesworkspaces') . $additionalParams;
+               }
+       }
+
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Befunc.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Befunc.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/GridData.php b/typo3/sysext/workspaces/Classes/Service/GridData.php
new file mode 100644 (file)
index 0000000..52e677b
--- /dev/null
@@ -0,0 +1,367 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Service
+ */
+class tx_Workspaces_Service_GridData {
+       private $dataArray = array();
+       private $sort = '';
+       private $sortDir = '';
+
+       /**
+        * Generates grid list array from given versions.
+        *
+        * @param  $versions    Array of all records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid, t3ver_oid and t3ver_swapmode fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid"
+        * @param  $parameter
+        * @return array
+        */
+       public function generateGridListFromVersions($versions, $parameter) {
+
+                       // Read the given parameters from grid. If the parameter is not set use default values.
+               $filterTxt = isset($parameter->filterTxt) ? $parameter->filterTxt : '';
+               $start = isset($parameter->start) ? intval($parameter->start) : 0;
+               $limit = isset($parameter->limit) ? intval($parameter->limit) : 10;
+               $this->sort = isset($parameter->sort) ? $parameter->sort : 't3ver_oid';
+               $this->sortDir = isset($parameter->dir) ? $parameter->dir : 'ASC';
+
+               $data = array();
+               $data['data'] = array();
+
+               $this->generateDataArray($versions, $filterTxt);
+
+               $data['total'] = count($this->dataArray);
+               $data['data'] = $this->getDataArray($start, $limit);
+
+               return $data;
+       }
+
+       /**
+        * @param  $versions
+        * @param  $filterTxt
+        * @return void
+        */
+       protected function generateDataArray($versions, $filterTxt) {
+               /**
+                * @var Tx_Workspaces_Service_Stages
+                */
+               $stagesObj = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+
+               foreach ($versions as $table => $records) {
+                       $versionArray = array('table' => $table);
+
+                       foreach ($records as $record) {
+
+                               $origRecord = t3lib_BEFunc::getRecord($table, $record['t3ver_oid']);
+                               $versionRecord = t3lib_BEFunc::getRecord($table, $record['uid']);
+
+                               if (isset($GLOBALS['TCA'][$table]['columns']['hidden'])) {
+                                       $recordState = $this->workspaceState($versionRecord['t3ver_state'], $origRecord['hidden'], $versionRecord['hidden']);
+                               } else {
+                                       $recordState = $this->workspaceState($versionRecord['t3ver_state']);
+                               }
+                               $isDeletedPage = ($table == 'pages' && $recordState == 'deleted');
+
+                               $pctChange = $this->calculateChangePercentage($table, $origRecord, $versionRecord);
+                               $versionArray['uid'] = $record['uid'];
+                               $versionArray['workspace'] = $versionRecord['t3ver_id'];
+                               $versionArray['label_Workspace'] = $versionRecord[$GLOBALS['TCA'][$table]['ctrl']['label']];
+                               $versionArray['label_Live'] = $origRecord[$GLOBALS['TCA'][$table]['ctrl']['label']];
+                               $versionArray['label_Stage'] = $stagesObj->getStageTitle($versionRecord['t3ver_stage']);
+                               $versionArray['change'] = $pctChange;
+                               $versionArray['path_Live'] = t3lib_BEfunc::getRecordPath($record['livepid'], '', 999);
+                               $versionArray['path_Workspace'] = t3lib_BEfunc::getRecordPath($record['wspid'], '', 999);
+                               $versionArray['workspace_Title'] = tx_Workspaces_Service_Workspaces::getWorkspaceTitle($versionRecord['t3ver_wsid']);
+
+                               $versionArray['workspace_Tstamp'] = $versionRecord['tstamp'];
+                               $versionArray['workspace_Formated_Tstamp'] = t3lib_BEfunc::datetime($versionRecord['tstamp']);
+                               $versionArray['t3ver_oid'] = $record['t3ver_oid'];
+                               $versionArray['livepid'] = $record['livepid'];
+                               $versionArray['stage'] = $versionRecord['t3ver_stage'];
+                               $versionArray['icon_Live'] = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($table, $origRecord);
+                               $versionArray['icon_Workspace'] = t3lib_iconWorks::mapRecordTypeToSpriteIconClass($table, $versionRecord);
+
+                               $versionArray['allowedAction_nextStage'] = $stagesObj->isNextStageAllowedForUser($versionRecord['t3ver_stage']);
+                               $versionArray['allowedAction_prevStage'] = $stagesObj->isPrevStageAllowedForUser($versionRecord['t3ver_stage']);
+                                       // @todo hide the actions if the user is not allowed to edit the current stage
+                               $versionArray['allowedAction_swap'] = $GLOBALS['BE_USER']->workspaceSwapAccess();
+                               $versionArray['allowedAction_delete'] = TRUE;
+                                       // preview and editing of a deleted page won't work ;)
+                               $versionArray['allowedAction_view'] = !$isDeletedPage;
+                               $versionArray['allowedAction_edit'] = !$isDeletedPage;
+                               $versionArray['allowedAction_editVersionedPage'] = !$isDeletedPage;
+
+                               $versionArray['state_Workspace'] = $recordState;
+
+                               if ($filterTxt == '' || $this->isFilterTextInVisibleColumns($filterTxt, $versionArray)) {
+                                       $this->dataArray[] = $versionArray;
+                               }
+                       }
+               }
+               $this->sortDataArray();
+       }
+
+       /**
+        * @param  $start
+        * @param  $limit
+        * @return array
+        */
+       protected function getDataArray($start, $limit) {
+               $dataArrayPart = array();
+               $end = $start + $limit < count($this->dataArray) ? $start + $limit : count($this->dataArray);
+
+               for ($i = $start; $i < $end; $i++) {
+                       $dataArrayPart[] = $this->dataArray[$i];
+               }
+
+               return $dataArrayPart;
+       }
+
+       /**
+        * @return void
+        */
+       protected function sortDataArray() {
+               switch ($this->sort) {
+                       case 'uid';
+                       case 'change';
+                       case 'workspace_Tstamp';
+                       case 't3ver_oid';
+                       case 'liveid';
+                       case 'livepid':
+                               usort($this->dataArray, array($this, 'intSort'));
+                               break;
+                       case 'label_Workspace';
+                       case 'label_Live';
+                       case 'label_Stage';
+                       case 'workspace_Title';
+                       case 'path_Live':
+                                       // case 'path_Workspace': This is the first sorting attribute
+                               usort($this->dataArray, array($this, 'stringSort'));
+                               break;
+               }
+       }
+
+       /**
+        * @param  $a
+        * @param  $b
+        * @return int
+        */
+       protected function intSort($a, $b) {
+                       // Als erstes nach dem Pfad sortieren
+               $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
+
+               if ($path_cmp < 0) {
+                       return $path_cmp;
+               } elseif ($path_cmp == 0) {
+                       if ($a[$this->sort] == $b[$this->sort]) {
+                               return 0;
+                       }
+                       if ($this->sortDir == 'ASC') {
+                               return ($a[$this->sort] < $b[$this->sort]) ? -1 : 1;
+                       } elseif ($this->sortDir == 'DESC') {
+                               return ($a[$this->sort] > $b[$this->sort]) ? -1 : 1;
+                       }
+               } elseif ($path_cmp > 0) {
+                       return $path_cmp;
+               }
+               return 0; //ToDo: Throw Exception
+       }
+
+       /**
+        * @param  $a
+        * @param  $b
+        * @return int
+        */
+       protected function stringSort($a, $b) {
+               $path_cmp = strcasecmp($a['path_Workspace'], $b['path_Workspace']);
+
+               if ($path_cmp < 0) {
+                       return $path_cmp;
+               } elseif ($path_cmp == 0) {
+                       if ($a[$this->sort] == $b[$this->sort]) {
+                               return 0;
+                       }
+                       if ($this->sortDir == 'ASC') {
+                               return (strcasecmp($a[$this->sort], $b[$this->sort]));
+                       } elseif ($this->sortDir == 'DESC') {
+                               return (strcasecmp($a[$this->sort], $b[$this->sort]) * (-1));
+                       }
+               } elseif ($path_cmp > 0) {
+                       return $path_cmp;
+               }
+               return 0; //ToDo: Throw Exception
+       }
+
+       /**
+        * @param  $filterText
+        * @param  $versionArray
+        * @return bool
+        */
+       protected function isFilterTextInVisibleColumns($filterText, $versionArray) {
+               if (is_array($GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['columns'])) {
+                       foreach ($GLOBALS['BE_USER']->uc['moduleData']['Workspaces']['columns'] as $column => $value) {
+                               if (isset($value['hidden']) && isset($column) && isset($versionArray[$column])) {
+                                       if ($value['hidden'] == 0) {
+                                               switch ($column) {
+                                                       case 'workspace_Tstamp':
+                                                               if (stripos($versionArray['workspace_Formated_Tstamp'], $filterText) !== FALSE) {
+                                                                       return TRUE;
+                                                               }
+                                                               break;
+                                                       case 'change':
+                                                               if (stripos(strval($versionArray[$column]), str_replace('%', '', $filterText)) !== FALSE) {
+                                                                       return TRUE;
+                                                               }
+                                                               break;
+                                                       default:
+                                                               if (stripos(strval($versionArray[$column]), $filterText) !== FALSE) {
+                                                                       return TRUE;
+                                                               }
+                                               }
+                                       }
+                               }
+                       }
+               }
+               return FALSE;
+       }
+
+       /**
+        * @param  $table
+        * @param  $diffRecordOne
+        * @param  $diffRecordTwo
+        * @return float|int
+        */
+       public function calculateChangePercentage($table, $diffRecordOne, $diffRecordTwo) {
+               global $TCA;
+
+                       // Initialize:
+               $changePercentage = 0;
+               $changePercentageArray = array();
+
+                       // Check that records are arrays:
+               if (is_array($diffRecordOne) && is_array($diffRecordTwo)) {
+
+                               // Load full table description
+                       t3lib_div::loadTCA($table);
+
+
+                       $similarityPercentage = 0;
+
+                               // Traversing the first record and process all fields which are editable:
+                       foreach ($diffRecordOne as $fieldName => $fieldValue) {
+                               if ($TCA[$table]['columns'][$fieldName] && $TCA[$table]['columns'][$fieldName]['config']['type'] != 'passthrough' && !t3lib_div::inList('t3ver_label', $fieldName)) {
+
+                                       if (strcmp(trim($diffRecordOne[$fieldName]), trim($diffRecordTwo[$fieldName]))
+                                                       && $TCA[$table]['columns'][$fieldName]['config']['type'] == 'group'
+                                                       && $TCA[$table]['columns'][$fieldName]['config']['internal_type'] == 'file'
+                                       ) {
+
+                                                       // Initialize:
+                                               $uploadFolder = $TCA[$table]['columns'][$fieldName]['config']['uploadfolder'];
+                                               $files1 = array_flip(t3lib_div::trimExplode(',', $diffRecordOne[$fieldName], 1));
+                                               $files2 = array_flip(t3lib_div::trimExplode(',', $diffRecordTwo[$fieldName], 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:
+                                               $diffRecordOne[$fieldName] = implode(' ', $files1);
+                                               $diffRecordTwo[$fieldName] = implode(' ', $files2);
+                                       }
+
+                                               // If there is a change of value:
+                                       if (strcmp(trim($diffRecordOne[$fieldName]), trim($diffRecordTwo[$fieldName]))) {
+                                                       // Get the best visual presentation of the value to calculate differences:
+                                               $val1 = t3lib_BEfunc::getProcessedValue($table, $fieldName, $diffRecordOne[$fieldName], 0, 1);
+                                               $val2 = t3lib_BEfunc::getProcessedValue($table, $fieldName, $diffRecordTwo[$fieldName], 0, 1);
+
+                                               similar_text($val1, $val2, $similarityPercentage);
+                                               $changePercentageArray[] = $similarityPercentage > 0 ? abs($similarityPercentage - 100) : 0;
+                                       }
+                               }
+                       }
+
+                               // Calculate final change percentage:
+                       if (is_array($changePercentageArray)) {
+                               $sumPctChange = 0;
+                               foreach ($changePercentageArray as $singlePctChange) {
+                                       $sumPctChange += $singlePctChange;
+                               }
+                               count($changePercentageArray) > 0 ? $changePercentage = round($sumPctChange / count($changePercentageArray)) : $changePercentage = 0;
+                       }
+
+               }
+               return $changePercentage;
+       }
+
+       /**
+        *
+        * @param       integer stateId of offline record
+        * @param       integer hidden flag of online record
+        * @param       integer hidden flag of offline record
+        * @return      string
+        */
+        protected function workspaceState($stateId, $hiddenOnline = FALSE, $hiddenOffline = FALSE) {
+               $state = FALSE;
+
+               switch ($stateId) {
+                       case -1:
+                               $state = 'new';
+                               break;
+                       case 1:
+                       case 2:
+                               $state = 'deleted';
+                               break;
+                       case 4:
+                               $state = 'moved';
+                               break;
+                       default:
+                               $state = 'modified';
+               }
+
+               if ($hiddenOnline == 0 && $hiddenOffline == 1) {
+                       $state = 'hidden';
+               } elseif ($hiddenOnline == 1 && $hiddenOffline == 0) {
+                       $state = 'unhidden';
+               }
+
+               return $state;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/GridData.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/GridData.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/Stages.php b/typo3/sysext/workspaces/Classes/Service/Stages.php
new file mode 100644 (file)
index 0000000..7ed0e57
--- /dev/null
@@ -0,0 +1,631 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Service
+ */
+
+class Tx_Workspaces_Service_Stages {
+       const TABLE_STAGE = 'sys_workspace_stage';
+               // if a record is in the "ready to publish" stage STAGE_PUBLISH_ID the nextStage is STAGE_PUBLISH_EXECUTE_ID, this id wont be saved at any time in db
+       const STAGE_PUBLISH_EXECUTE_ID = -20;
+               // ready to publish stage
+       const STAGE_PUBLISH_ID = -10;
+       const STAGE_EDIT_ID = 0;
+
+       /** Current workspace id */
+       private $workspaceId = NULL;
+
+       /** path to locallang file */
+       private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xml';
+
+               // local caches to avoid that workspace stages, groups etc need to be read from the DB every time
+       protected $workspaceStageCache = array();
+       protected $workspaceStageAllowedCache = array();
+       protected $fetchGroupsCache = array();
+       /**
+        * Getter for current workspace id
+        *
+        * @return int current workspace id
+        */
+       public function getWorkspaceId() {
+               if ($this->workspaceId == NULL) {
+                       $this->setWorkspaceId($GLOBALS['BE_USER']->workspace);
+               }
+               return $this->workspaceId;
+       }
+
+       /**
+        * Setter for current workspace id
+        *
+        * @param int current workspace id
+        */
+       private function setWorkspaceId($wsid) {
+               $this->workspaceId = $wsid;
+       }
+
+       /**
+        * constructor for workspace library
+        *
+        * @param int current workspace id
+        */
+       public function __construct() {
+               $this->setWorkspaceId($GLOBALS['BE_USER']->workspace);
+       }
+
+       /**
+        * Building an array with all stage ids and titles related to the given workspace
+        *
+        * @return array id and title of the stages
+        */
+       public function getStagesForWS() {
+
+               $stages = array();
+
+               if (isset($this->workspaceStageCache[$this->getWorkspaceId()])) {
+                       $stages = $this->workspaceStageCache[$this->getWorkspaceId()];
+               } else {
+                       $stages[] = array(
+                               'uid' => self::STAGE_EDIT_ID,
+                               'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing') . '"'
+                       );
+
+                       $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $this->getWorkspaceId());
+                       if ($workspaceRec['custom_stages'] > 0) {
+                                       // Get all stage records for this workspace
+                               $workspaceStageRecs = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                                       '*',
+                                       self::TABLE_STAGE,
+                                               'parentid=' . $this->getWorkspaceId() . ' AND parenttable="sys_workspace" AND deleted = 0',
+                                       '',
+                                       'sorting',
+                                       '',
+                                       'uid'
+                               );
+                               foreach($workspaceStageRecs as $stage) {
+                                       $stages[] = $stage;
+                               }
+                       }
+
+                       $stages[] = array(
+                               'uid' => self::STAGE_PUBLISH_ID,
+                               'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xml:stage_ready_to_publish') . '"'
+                       );
+
+                       $stages[] = array(
+                               'uid' => self::STAGE_PUBLISH_EXECUTE_ID,
+                               'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':publish_execute_action_option')
+                       );
+                       $this->workspaceStageCache[$this->getWorkspaceId()] = $stages;
+               }
+
+               return $stages;
+       }
+
+       /**
+        * Returns an array of stages, the user is allowed to send to
+        * @return array id and title of stages
+        */
+       public function getStagesForWSUser() {
+
+                       // initiate return array of stages with edit stage
+               $stagesForWSUserData = array();
+
+                       // get all stages for the current workspace
+               $workspaceStageRecs = $this->getStagesForWS();
+               if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
+                               // go through custom stages records
+                       foreach ($workspaceStageRecs as $workspaceStageRec) {
+                                       // check if the user has permissions to the custom stage
+                               if ($GLOBALS['BE_USER']->workspaceCheckStageForCurrent($this->encodeStageUid($workspaceStageRec['uid']))) {
+                                               // yes, so add to return array
+                                       $stagesForWSUserData[] = array(
+                                               'uid' => $this->encodeStageUid($workspaceStageRec['uid']),
+                                               'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $workspaceStageRec['title'] . '"');
+                               } else if ($workspaceStageRec['uid'] == self::STAGE_PUBLISH_EXECUTE_ID) {
+                                               if ($GLOBALS['BE_USER']->workspacePublishAccess($this->getWorkspaceId())) {
+                                                       $stagesForWSUserData[] = $workspaceStageRec;
+                                               }
+                               }
+                       }
+               }
+               return $stagesForWSUserData;
+       }
+
+       /**
+        * Check if given workspace has custom staging activated
+        *
+        * @return bool true or false
+        */
+       public function checkCustomStagingForWS() {
+               $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $this->getWorkspaceId());
+               return $workspaceRec['custom_stages'] > 0;
+       }
+
+       /**
+        * converts from versioning stage id to stage record UID
+        *
+        * @return int UID of the database record
+        */
+       public function resolveStageUid($ver_stage) {
+               return $ver_stage;
+               //return $ver_stage - $this->raiseStageIdAmount;
+       }
+
+       /**
+        * converts from stage record UID to versioning stage id
+        *
+        * @param int UID of the stage database record
+        * @return int versioning stage id
+        */
+       public function encodeStageUid($stage_uid) {
+               return $stage_uid;
+               //return $stage_uid + $this->raiseStageIdAmount;
+       }
+
+       public function getStageTitle($ver_stage) {
+               global $LANG;
+               $stageTitle = '';
+
+               switch ($ver_stage) {
+                       case self::STAGE_PUBLISH_EXECUTE_ID:
+                               $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_publish');
+                               break;
+                       case self::STAGE_PUBLISH_ID:
+                               $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xml:stage_ready_to_publish');
+                               break;
+                       case self::STAGE_EDIT_ID:
+                               $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing');
+                               break;
+                       default:
+                               $stageTitle = $this->getPropertyOfCurrentWorkspaceStage($ver_stage, 'title');
+
+                               if ($stageTitle == null) {
+                                       $stageTitle = $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.getStageTitle.stageNotFound');
+                               }
+                               break;
+               }
+
+               return $stageTitle;
+       }
+
+       /**
+        * @param  $stageid
+        * @return array
+        */
+       public function getStageRecord($stageid) {
+               return t3lib_BEfunc::getRecord('sys_workspace_stage', $this->resolveStageUid($stageid));
+       }
+
+       /**
+        * Get next stage in process for given stage id
+        *
+        * @param int                   stageid
+        * @return int                  id
+        */
+       public function getNextStage($stageid) {
+
+               if (!t3lib_div::testInt($stageid)) {
+                       throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               }
+
+               $nextStage = FALSE;
+
+               $workspaceStageRecs = $this->getStagesForWS();
+               if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
+                       reset($workspaceStageRecs);
+                       while (!is_null($workspaceStageRecKey = key($workspaceStageRecs))) {
+                               $workspaceStageRec = current($workspaceStageRecs);
+                               if ($workspaceStageRec['uid'] == $this->resolveStageUid($stageid)) {
+                                       $nextStage = next($workspaceStageRecs);
+                                       break;
+                               }
+                               next($workspaceStageRecs);
+                       }
+               } else {
+                       // @todo consider to throw an exception here
+               }
+
+               if ($nextStage === FALSE) {
+                       $nextStage[] = array(
+                               'uid' => self::STAGE_EDIT_ID,
+                               'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':actionSendToStage') . ' "' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_user_ws.xml:stage_editing') . '"'
+                       );
+               }
+
+               return $nextStage;
+       }
+
+       /**
+        * Recursive function to get all next stages for a record depending on user permissions
+        *
+        * @param       array   next stages
+        * @param       int             stage id
+        * @param       int             current stage id of the record
+        * @return      array   next stages
+        */
+       public function getNextStages(array &$nextStageArray, $stageId) {
+                       // Current stage is "Ready to publish" - there is no next stage
+               if ($stageId == self::STAGE_PUBLISH_ID) {
+                       return $nextStageArray;
+               } else {
+                       $nextStageRecord = $this->getNextStage($stageId);
+                       if (empty($nextStageRecord) || !is_array($nextStageRecord)) {
+                                       // There is no next stage
+                               return $nextStageArray;
+                       } else {
+                                       // Check if the user has the permission to for the current stage
+                                       // If this next stage record is the first next stage after the current the user
+                                       // has always the needed permission
+                               if ($this->isStageAllowedForUser($stageId)) {
+                                       $nextStageArray[] = $nextStageRecord;
+                                       return $this->getNextStages($nextStageArray, $nextStageRecord['uid']);
+                               } else {
+                                               // He hasn't - return given next stage array
+                                       return $nextStageArray;
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Get next stage in process for given stage id
+        *
+        * @param int                   workspace id
+        * @param int                   stageid
+        * @return int                  id
+        */
+       public function getPrevStage($stageid) {
+
+               if (!t3lib_div::testInt($stageid)) {
+                       throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               }
+
+               $prevStage = FALSE;
+               $workspaceStageRecs = $this->getStagesForWS();
+               if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
+                       end($workspaceStageRecs);
+                       while (!is_null($workspaceStageRecKey = key($workspaceStageRecs))) {
+                               $workspaceStageRec = current($workspaceStageRecs);
+                               if ($workspaceStageRec['uid'] == $this->resolveStageUid($stageid)) {
+                                       $prevStage = prev($workspaceStageRecs);
+                                       break;
+                               }
+                               prev($workspaceStageRecs);
+                       }
+               } else {
+                       // @todo consider to throw an exception here
+               }
+               return $prevStage;
+       }
+
+       /**
+        * Recursive function to get all prev stages for a record depending on user permissions
+        *
+        * @param       array   prev stages
+        * @param       int             workspace id
+        * @param       int             stage id
+        * @param       int             current stage id of the record
+        * @return      array   prev stages
+        */
+       public function getPrevStages(array &$prevStageArray, $stageId) {
+                       // Current stage is "Editing" - there is no prev stage
+               if ($stageId != self::STAGE_EDIT_ID) {
+                       $prevStageRecord = $this->getPrevStage($stageId);
+
+                       if (!empty($prevStageRecord) && is_array($prevStageRecord)) {
+                                       // Check if the user has the permission to switch to that stage
+                                       // If this prev stage record is the first previous stage before the current
+                                       // the user has always the needed permission
+                               if ($this->isStageAllowedForUser($stageId)) {
+                                       $prevStageArray[] = $prevStageRecord;
+                                       $prevStageArray = $this->getPrevStages($prevStageArray, $prevStageRecord['uid']);
+
+                               }
+                       }
+               }
+               return $prevStageArray;
+       }
+
+       /**
+        * Get array of all responsilbe be_users for a stage
+        *
+        * @param       int     stage id
+        * @return      array be_users with e-mail and name
+        */
+       public function getResponsibleBeUser($stageId) {
+               $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $this->getWorkspaceId());
+               $recipientArray = array();
+
+               switch ($stageId) {
+                       case self::STAGE_PUBLISH_EXECUTE_ID:
+                       case self::STAGE_PUBLISH_ID:
+                               $userList = $this->getResponsibleUser($workspaceRec['adminusers']);
+                               break;
+                       case self::STAGE_EDIT_ID:
+                               $userList = $this->getResponsibleUser($workspaceRec['members']);
+                               break;
+                       default:
+                               $responsible_persons = $this->getPropertyOfCurrentWorkspaceStage($stageId, 'responsible_persons');
+                               $userList = $this->getResponsibleUser($responsible_persons);
+                               break;
+               }
+
+               $userRecords = t3lib_BEfunc::getUserNames('username, uid, email, realName',
+                               'AND uid IN (' . $userList . ')');
+
+               if (!empty($userRecords) && is_array($userRecords)) {
+                       foreach ($userRecords as $userUid => $userRecord) {
+                               $recipientArray[$userUid] = $userRecord['email'] . ' ( ' . $userRecord['realName'] . ' )';
+                       }
+               }
+               return $recipientArray;
+       }
+
+       /**
+        * Get uids of all responsilbe persons for a stage
+        *
+        * @param       string  responsible_persion value from stage record
+        * @return      string  uid list of responsible be_users
+        */
+       public function getResponsibleUser($stageRespValue) {
+               $stageValuesArray = array();
+               $stageValuesArray = t3lib_div::trimExplode(',', $stageRespValue);
+
+               $beuserUidArray = array();
+               $begroupUidArray = array();
+               $allBeUserArray = array();
+               $begroupUidList = array();
+
+               foreach ($stageValuesArray as $key => $uidvalue) {
+                       if (strstr($uidvalue, 'be_users') !== FALSE) { // Current value is a uid of a be_user record
+                               $beuserUidArray[] = str_replace('be_users_', '', $uidvalue);
+                       } elseif (strstr($uidvalue, 'be_groups') !== FALSE) {
+                               $begroupUidArray[] = str_replace('be_groups_', '', $uidvalue);
+                       } else {
+                               $beuserUidArray[] = $uidvalue;
+                       }
+               }
+               if (!empty($begroupUidArray)) {
+                       $allBeUserArray = t3lib_befunc::getUserNames();
+
+                       $begroupUidList = implode(',', $begroupUidArray);
+
+                       $this->userGroups = array();
+                       $begroupUidArray = $this->fetchGroups($begroupUidList);
+
+                       foreach ($begroupUidArray as $groupkey => $groupData) {
+                               foreach ($allBeUserArray as $useruid => $userdata) {
+                                       if (t3lib_div::inList($userdata['usergroup'], $groupData['uid'])) {
+                                               $beuserUidArray[] = $useruid;
+                                       }
+                               }
+                       }
+               }
+
+               array_unique($beuserUidArray);
+               return implode(',', $beuserUidArray);
+       }
+
+
+       /**
+        * @param  $grList
+        * @param string $idList
+        * @return array
+        */
+       private function fetchGroups($grList, $idList = '') {
+
+               $cacheKey = md5($grList . $idList);
+               $groupList = array();
+               if (isset($this->fetchGroupsCache[$cacheKey])) {
+                       $groupList = $this->fetchGroupsCache[$cacheKey];
+               } else {
+                       if ($idList === '') {
+                                       // we're at the beginning of the recursion and therefore we need to reset the userGroups member
+                               $this->userGroups = array();
+                       }
+                       $groupList = $this->fetchGroupsRecursive($grList);
+                       $this->fetchGroupsCache[$cacheKey] = $groupList;
+               }
+               return $groupList;
+       }
+
+       /**
+        * @param  array        $groups
+        * @return void
+        */
+       private function fetchGroupsFromDB(array $groups) {
+               $whereSQL = 'deleted=0 AND hidden=0 AND pid=0 AND uid IN (' . implode(',', $groups) . ') ';
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', 'be_groups', $whereSQL);
+
+                       // The userGroups array is filled
+               while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
+                       $this->userGroups[$row['uid']] = $row;
+               }
+       }
+
+       /**
+        * @param  $grList
+        * @param string $idList
+        * @return array
+        */
+       private function fetchGroupsRecursive($grList, $idList = '') {
+
+               $requiredGroups = t3lib_div::intExplode(',', $grList, TRUE);
+               $existingGroups = array_keys($this->userGroups);
+               $missingGroups = array_diff($include_staticArr, $existingGroups);
+               if (count($groups) > 0) {
+                       $this->fetchGroupsFromDB($missingGroups);
+               }
+
+                       // Traversing records in the correct order
+               foreach ($requiredGroups as $uid) { // traversing list
+                               // Get row:
+                       $row = $this->userGroups[$uid];
+                       if (is_array($row) && !t3lib_div::inList($idList, $uid)) { // Must be an array and $uid should not be in the idList, because then it is somewhere previously in the grouplist
+                                       // If the localconf.php option isset the user of the sub- sub- groups will also be used
+                               if ($GLOBALS['TYPO3_CONF_VARS']['BE']['customStageShowRecipientRecursive'] == 1) {
+                                               // Include sub groups
+                                       if (trim($row['subgroup'])) {
+                                                       // Make integer list
+                                               $theList = implode(',', t3lib_div::intExplode(',', $row['subgroup']));
+                                                       // Get the subarray
+                                               $subbarray = $this->fetchGroups($theList, $idList . ',' . $uid);
+                                               list($subUid, $subArray) = each($subbarray);
+                                                       // Merge the subarray to the already existing userGroups array
+                                               $this->userGroups[$subUid] = $subArray;
+                                       }
+                               }
+                       }
+               }
+               return $this->userGroups;
+       }
+
+       /**
+        * Gets a property of a workspaces stage.
+        *
+        * @param integer $stageId
+        * @param string $property
+        * @return string
+        */
+       public function getPropertyOfCurrentWorkspaceStage($stageId, $property) {
+               $result = NULL;
+
+               if (!t3lib_div::testInt($stageId)) {
+                       throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               }
+
+               if ($stageId != self::STAGE_PUBLISH_ID && $stageId != self::STAGE_EDIT_ID) {
+                       $stageId = $this->resolveStageUid($stageId);
+               }
+
+               $workspaceStage = t3lib_BEfunc::getRecord(self::TABLE_STAGE, $stageId);
+
+               if (is_array($workspaceStage) && isset($workspaceStage[$property])) {
+                       $result = $workspaceStage[$property];
+               }
+
+               return $result;
+       }
+
+       /**
+        * Gets the position of the given workspace in the hole process f.e. 3 means step 3 of 20, by which 1 is edit and 20 is ready to publish
+        *
+        * @param integer $stageId
+        * @return array position => 3, count => 20
+        */
+       public function getPositionOfCurrentStage($stageId) {
+               $stagesOfWS = $this->getStagesForWS();
+               $countOfStages = count($stagesOfWS);
+
+               switch ($stageId) {
+                       case self::STAGE_PUBLISH_ID:
+                                       $position = $countOfStages;
+                               break;
+                       case self::STAGE_EDIT_ID:
+                                       $position = 1;
+                               break;
+                       default:
+                               $position = 1;
+                               foreach ($stagesOfWS as $key => $stageInfoArray) {
+                                       $position++;
+                                       if ($stageId == $stageInfoArray['uid']) {
+                                               break;
+                                       }
+                               }
+                               break;
+               }
+               return array('position' => $position, 'count' => $countOfStages);
+       }
+
+       /**
+        * Check if the user has access to the previous stage, relative to the given stage
+        *
+        * @param  integer $stageId
+        * @return bool
+        */
+       public function isPrevStageAllowedForUser($stageId) {
+               $isAllowed = FALSE;
+               try {
+                       $prevStage = $this->getPrevStage($stageId);
+                               // if there's no prev-stage the stageIds match,
+                               // otherwise we've to check if the user is permitted to use the stage
+                       if (!empty($prevStage) && $prevStage['uid'] != $stageId) {
+                               $isAllowed = $this->isStageAllowedForUser($prevStage['uid']);
+                       }
+               } catch (Exception $e) {
+                       // Exception raised - we're not allowed to go this way
+               }
+
+               return $isAllowed;
+       }
+
+       /**
+        * Check if the user has access to the next stage, relative to the given stage
+        *
+        * @param  integer $stageId
+        * @return bool
+        */
+       public function isNextStageAllowedForUser($stageId) {
+               $isAllowed = FALSE;
+               try {
+                       $nextStage = $this->getNextStage($stageId);
+                               // if there's no next-stage the stageIds match,
+                               // otherwise we've to check if the user is permitted to use the stage
+                       if (!empty($nextStage) && $nextStage['uid'] != $stageId) {
+                               $isAllowed = $this->isStageAllowedForUser($nextStage['uid']);
+                       }
+               } catch (Exception $e) {
+                       // Exception raised - we're not allowed to go this way
+               }
+
+               return $isAllowed;
+       }
+
+       /**
+        * @param  $stageId
+        * @return bool
+        */
+       protected function isStageAllowedForUser($stageId) {
+               $cacheKey = $this->getWorkspaceId() . '_' . $stageId;
+               $isAllowed = FALSE;
+               if (isset($this->workspaceStageAllowedCache[$cacheKey])) {
+                        $isAllowed = $this->workspaceStageAllowedCache[$cacheKey];
+                } else {
+                        $isAllowed = $GLOBALS['BE_USER']->workspaceCheckStageForCurrent($stageId);
+                        $this->workspaceStageAllowedCache[$cacheKey] = $isAllowed;
+                }
+               return $isAllowed;
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Stages.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Stages.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/Tcemain.php b/typo3/sysext/workspaces/Classes/Service/Tcemain.php
new file mode 100644 (file)
index 0000000..4821d0f
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Service
+ */
+class tx_Workspaces_Service_Tcemain {
+
+       /**
+        * In case a sys_workspace_stage record is deleted we do a hard reset
+        * for all existing records in that stage to avoid that any of these end up
+        * as orphan records.
+        *
+        * @param string $command
+        * @param string $table
+        * @param string $id
+        * @param string $value
+        * @param object $tcemain
+        * @return void
+        */
+       public function processCmdmap_postProcess($command, $table, $id, $value, $tcemain) {
+
+               if (strcmp($command, 'delete') || strcmp($table, Tx_Workspaces_Service_Stages::TABLE_STAGE)) {
+                       return;
+               }
+
+               $service = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+                       // @todo: remove the encode/decode functionality
+               $pseudoStageId = $service->encodeStageUid($id);
+
+               $fields = array('t3ver_stage' => Tx_Workspaces_Service_Stages::STAGE_EDIT_ID);
+
+               foreach ($GLOBALS['TCA'] as $tcaTable => $cfg) {
+                       if ($GLOBALS['TCA'][$tcaTable]['ctrl']['versioningWS']) {
+
+                               $where = 't3ver_stage = ' . intval($pseudoStageId);
+                               $where .= ' AND t3ver_wsid > 0 AND pid=-1';
+                               $where .= t3lib_BEfunc::deleteClause($tcaTable);
+
+                               $GLOBALS['TYPO3_DB']->exec_UPDATEquery($tcaTable, $where, $fields);
+                       }
+               }
+       }
+
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Tcemain.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Tcemain.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Classes/Service/Workspaces.php b/typo3/sysext/workspaces/Classes/Service/Workspaces.php
new file mode 100644 (file)
index 0000000..33f64cc
--- /dev/null
@@ -0,0 +1,409 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+/**
+ * @author Workspaces Team (http://forge.typo3.org/projects/show/typo3v4-workspaces)
+ * @package Workspaces
+ * @subpackage Service
+ */
+class tx_Workspaces_Service_Workspaces {
+       const SELECT_ALL_WORKSPACES = -98;
+       const LIVE_WORKSPACE_ID = 0;
+       const DRAFT_WORKSPACE_ID = -1;
+
+       /**
+        * retrieves the available workspaces from the database and checks whether
+        * they're available to the current BE user
+        *
+        * @return      array   array of worspaces available to the current user
+        */
+       public function getAvailableWorkspaces() {
+               $availableWorkspaces = array();
+
+                       // add default workspaces
+               if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::LIVE_WORKSPACE_ID))) {
+                       $availableWorkspaces[self::LIVE_WORKSPACE_ID] = $this->getWorkspaceTitle(self::LIVE_WORKSPACE_ID);
+               }
+               if ($GLOBALS['BE_USER']->checkWorkspace(array('uid' => (string) self::DRAFT_WORKSPACE_ID))) {
+                       $availableWorkspaces[self::DRAFT_WORKSPACE_ID] = $this->getWorkspaceTitle(self::DRAFT_WORKSPACE_ID);
+               }
+
+                       // add custom workspaces (selecting all, filtering by BE_USER check):
+               $customWorkspaces = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, title, adminusers, members', 'sys_workspace', 'pid = 0' . t3lib_BEfunc::deleteClause('sys_workspace'), '', 'title');
+               if (count($customWorkspaces)) {
+                       foreach ($customWorkspaces as $workspace) {
+                               if ($GLOBALS['BE_USER']->checkWorkspace($workspace)) {
+                                       $availableWorkspaces[$workspace['uid']] = $workspace['uid'] . ': ' . htmlspecialchars($workspace['title']);
+                               }
+                       }
+               }
+
+               return $availableWorkspaces;
+       }
+
+
+       /**
+        * Find the title for the requested workspace.
+        *
+        * @param integer $wsId
+        * @return string
+        */
+       public static function getWorkspaceTitle($wsId) {
+               $title = FALSE;
+               switch ($wsId) {
+                       case self::LIVE_WORKSPACE_ID:
+                               $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_onlineWS');
+                               break;
+                       case self::DRAFT_WORKSPACE_ID:
+                               $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_misc.xml:shortcut_offlineWS');
+                               break;
+                       default:
+                               $labelField = $GLOBALS['TCA']['sys_workspace']['ctrl']['label'];
+                               $wsRecord = t3lib_beFunc::getRecord('sys_workspace', $wsId, 'uid,' . $labelField);
+                               if (is_array($wsRecord)) {
+                                       $title = $wsRecord[$labelField];
+                               }
+               }
+
+               if ($title === FALSE) {
+                       throw new InvalidArgumentException('No such workspace defined');
+               }
+
+               return $title;
+       }
+
+
+       /**
+        * Building tcemain CMD-array for swapping all versions in a workspace.
+        *
+        * @param       integer         Real workspace ID, cannot be ONLINE (zero).
+        * @param       boolean         If set, then the currently online versions are swapped into the workspace in exchange for the offline versions. Otherwise the workspace is emptied.
+        * @param       integer         $pageId: ...
+        * @return      array           Command array for tcemain
+        */
+       public function getCmdArrayForPublishWS($wsid, $doSwap, $pageId = 0) {
+
+               $wsid = intval($wsid);
+               $cmd = array();
+
+               if ($wsid >= -1 && $wsid!==0) {
+
+                               // Define stage to select:
+                       $stage = -99;
+                       if ($wsid > 0) {
+                               $workspaceRec = t3lib_BEfunc::getRecord('sys_workspace', $wsid);
+                               if ($workspaceRec['publish_access'] & 1) {
+                                       $stage = Tx_Workspaces_Service_Stages::STAGE_PUBLISH_ID;
+                               }
+                       }
+
+                               // Select all versions to swap:
+                       $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ? $pageId : -1));
+
+                               // Traverse the selection to build CMD array:
+                       foreach ($versions as $table => $records) {
+                               foreach ($records as $rec) {
+                                               // Build the cmd Array:
+                                       $cmd[$table][$rec['t3ver_oid']]['version'] = array('action' => 'swap', 'swapWith' => $rec['uid'], 'swapIntoWS' => $doSwap ? 1 : 0);
+                               }
+                       }
+               }
+               return $cmd;
+       }
+
+
+       /**
+        * Building tcemain CMD-array for releasing all versions in a workspace.
+        *
+        * @param       integer         Real workspace ID, cannot be ONLINE (zero).
+        * @param       boolean         Run Flush (true) or ClearWSID (false) command
+        * @param       integer         $pageId: ...
+        * @return      array           Command array for tcemain
+        */
+       public function getCmdArrayForFlushWS($wsid, $flush = TRUE, $pageId = 0) {
+
+               $wsid = intval($wsid);
+               $cmd = array();
+
+               if ($wsid >= -1 && $wsid!==0) {
+                               // Define stage to select:
+                       $stage = -99;
+
+                               // Select all versions to swap:
+                       $versions = $this->selectVersionsInWorkspace($wsid, 0, $stage, ($pageId ? $pageId : -1));
+
+                               // Traverse the selection to build CMD array:
+                       foreach ($versions as $table => $records) {
+                               foreach ($records as $rec) {
+                                       // Build the cmd Array:
+                                       $cmd[$table][$rec['uid']]['version'] = array('action' => ($flush ? 'flush' : 'clearWSID'));
+                               }
+                       }
+               }
+               return $cmd;
+       }
+
+
+       /**
+        * Select all records from workspace pending for publishing
+        * Used from backend to display workspace overview
+        * User for auto-publishing for selecting versions for publication
+        *
+        * @param       integer         Workspace ID. If -99, will select ALL versions from ANY workspace. If -98 will select all but ONLINE. >=-1 will select from the actual workspace
+        * @param       integer         Lifecycle filter: 1 = select all drafts (never-published), 2 = select all published one or more times (archive/multiple), anything else selects all.
+        * @param       integer         Stage filter: -99 means no filtering, otherwise it will be used to select only elements with that stage. For publishing, that would be "10"
+        * @param       integer         Page id: Live page for which to find versions in workspace!
+        * @param       integer         Recursion Level - select versions recursive - parameter is only relevant if $pageId != -1
+        * @return      array           Array of all records uids etc. First key is table name, second key incremental integer. Records are associative arrays with uid, t3ver_oid and t3ver_swapmode fields. The pid of the online record is found as "livepid" the pid of the offline record is found in "wspid"
+        */
+       public function selectVersionsInWorkspace($wsid, $filter = 0, $stage = -99, $pageId = -1, $recursionLevel = 0) {
+
+               $wsid = intval($wsid);
+               $filter = intval($filter);
+               $output = array();
+
+                       // Contains either nothing or a list with live-uids
+               if ($pageId != -1 && $recursionLevel > 0) {
+                       $pageList = $this->getTreeUids($pageId, $wsid, $recursionLevel);
+               } else if ($pageId != -1) {
+                       $pageList = $pageId;
+               } else {
+                       $pageList = '';
+               }
+
+                       // Traversing all tables supporting versioning:
+               foreach ($GLOBALS['TCA'] as $table => $cfg) {
+                       if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+
+                               $recs = $this->selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage);
+                               if (intval($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) === 2) {
+                                       $moveRecs = $this->getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage);
+                                       $recs = array_merge($recs, $moveRecs);
+                               }
+                               $recs = $this->filterPermittedElements($recs, $table);
+                               if (count($recs)) {
+                                       $output[$table] = $recs;
+                               }
+                       }
+               }
+               return $output;
+       }
+
+       /**
+        * Find all versionized elements except moved records.
+        *
+        * @param string $table
+        * @param string $pageList
+        * @param integer $filter
+        * @param integer $stage
+        * @return array
+        */
+       protected function selectAllVersionsFromPages($table, $pageList, $wsid, $filter, $stage) {
+
+               $fields = 'A.uid, A.t3ver_oid,' . ($table==='pages' ? ' A.t3ver_swapmode,' : '') . 'B.pid AS wspid, B.pid AS livepid';
+               $from = $table . ' A,' . $table . ' B';
+
+                       // Table A is the offline version and pid=-1 defines offline
+               $where = 'A.pid=-1 AND A.t3ver_state!=4';
+               if ($pageList) {
+                       $pidField = ($table==='pages' ? 'uid' : 'pid');
+                       $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
+                       $where .= ' AND B.' . $pidField . $pidConstraint;
+               }
+
+               /**
+                * For "real" workspace numbers, select by that.
+                * If = -98, select all that are NOT online (zero).
+                * Anything else below -1 will not select on the wsid and therefore select all!
+                */
+               if ($wsid > self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid=' . $wsid;
+               } else if ($wsid === self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid!=0';
+               }
+
+               /**
+                * lifecycle filter:
+                * 1 = select all drafts (never-published),
+                * 2 = select all published one or more times (archive/multiple)
+                */
+               if ($filter===1 || $filter===2) {
+                       $where .= ' AND A.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
+               }
+
+               if ($stage != -99) {
+                       $where .= ' AND A.t3ver_stage=' . intval($stage);
+               }
+
+                       // Table B (online) must have PID >= 0 to signify being online.
+               $where .= ' AND B.pid>=0';
+                       // ... and finally the join between the two tables.
+               $where .= ' AND A.t3ver_oid=B.uid';
+               $where .= t3lib_BEfunc::deleteClause($table, 'A');
+               $where .= t3lib_BEfunc::deleteClause($table, 'B');
+
+               /**
+                * Select all records from this table in the database from the workspace
+                * This joins the online version with the offline version as tables A and B
+                * Order by UID, mostly to have a sorting in the backend overview module which doesn't "jump around" when swapping.
+                */
+               return $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'B.uid');
+       }
+
+       /**
+        *      Find all moved records at their new position.
+        *
+        * @param string $table
+        * @param string $pageList
+        * @param integer $wsid
+        * @return array
+        */
+       protected function getMoveToPlaceHolderFromPages($table, $pageList, $wsid, $filter, $stage) {
+
+               /**
+                * Aliases:
+                * A - moveTo placeholder
+                * B - online record
+                * C - moveFrom placeholder
+                */
+               $fields = 'A.pid AS wspid, B.uid AS t3ver_oid, C.uid AS uid, B.pid AS livepid';
+               $from = $table . ' A, ' . $table . ' B,' . $table . ' C';
+               $where = 'A.t3ver_state=3 AND B.pid>0 AND B.t3ver_state=0 AND B.t3ver_wsid=0 AND C.pid=-1 AND C.t3ver_state=4';
+
+               if ($wsid > self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid=' . $wsid . ' AND C.t3ver_wsid=' . $wsid;
+               } else if ($wsid === self::SELECT_ALL_WORKSPACES) {
+                       $where .= ' AND A.t3ver_wsid!=0 AND C.t3ver_wsid!=0 ';
+               }
+
+               /**
+                * lifecycle filter:
+                * 1 = select all drafts (never-published),
+                * 2 = select all published one or more times (archive/multiple)
+                */
+               if ($filter===1 || $filter===2) {
+                       $where .= ' AND C.t3ver_count ' . ($filter === 1 ? '= 0' : '> 0');
+               }
+
+               if ($stage != -99) {
+                       $where .= ' AND C.t3ver_stage=' . intval($stage);
+               }
+
+               if ($pageList) {
+                       $pidField = ($table==='pages' ? 'B.uid' : 'A.pid');
+                       $pidConstraint = strstr($pageList, ',') ? ' IN (' . $pageList . ')' : '=' . $pageList;
+                       $where .= ' AND ' . $pidField . $pidConstraint;
+               }
+
+               $where .= ' AND A.t3ver_move_id = B.uid AND B.uid = C.t3ver_oid';
+               $where .= t3lib_BEfunc::deleteClause($table, 'A');
+               $where .= t3lib_BEfunc::deleteClause($table, 'B');
+               $where .= t3lib_BEfunc::deleteClause($table, 'C');
+               $res = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows($fields, $from, $where, '', 'A.uid');
+
+               return $res;
+       }
+
+
+       /**
+        * Find all page uids recursive starting from a specific page
+        *
+        * @param        integer        $pageId
+        * @param        integer        $wsid
+        * @param        integer        $recursionLevel
+        * @return      string  Comma sep. uid list
+        */
+       protected function getTreeUids($pageId, $wsid, $recursionLevel) {
+               /**
+                * Reusing existing functionality with the drawback that
+                * mount points are not covered yet
+                **/
+               $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
+               $searchObj = t3lib_div::makeInstance('t3lib_fullsearch');
+               $pageList = $searchObj->getTreeList($pageId, $recursionLevel, 0, $perms_clause);
+
+               unset($searchObj);
+
+               if (intval($GLOBALS['TCA']['pages']['ctrl']['versioningWS']) === 2 && $pageList) {
+                       if ($pageList) {
+                                       // Remove the "subbranch" if a page was moved away
+                               $movedAwayPages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, pid, t3ver_move_id', 'pages', 't3ver_move_id IN (' . $pageList . ') AND t3ver_wsid=' . $wsid . t3lib_BEfunc::deleteClause($table), '', 'uid', '', 't3ver_move_id');
+                               $newList = array();
+                               $pageIds = t3lib_div::intExplode(',', $pageList, TRUE);
+
+                               foreach ($pageIds as $tmpId) {
+                                       if (isset($movedAwayPages[$tmpId]) && !empty($newList) && !in_array($movedAwayPages[$tmpId]['pid'], intval($newList))) {
+                                               break;
+                                       }
+                                       $newList[] = $tmpId;
+                               }
+                               $pageList = implode(',', $newList);
+                       }
+                               // In case moving pages is enabled we need to replace all move-to pointer with their origin
+                       $pages = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid, t3ver_move_id', 'pages', 'uid IN (' . $pageList . ')' . t3lib_BEfunc::deleteClause($table), '', 'uid', '', 'uid');
+
+                       $newList = array();
+                       $pageIds = t3lib_div::intExplode(',', $pageList, TRUE);
+                       foreach ($pageIds as $pageId) {
+                               if (intval($pages[$pageId]['t3ver_move_id']) > 0) {
+                                       $newList[] = intval($pages[$pageId]['t3ver_move_id']);
+                               } else {
+                                       $newList[] = $pageId;
+                               }
+                       }
+                       $pageList = implode(',', $newList);
+               }
+               return $pageList;
+       }
+
+       /**
+        * Remove all records which are not permitted for the user
+        *
+        * @param array $recs
+        * @param string $table
+        * @return array
+        */
+       protected function filterPermittedElements($recs, $table) {
+               $checkField = ($table == 'pages') ? 'uid' : 'pid';
+               $permittedElements = array();
+               if (is_array($recs)) {
+                       foreach ($recs as $rec) {
+                               $page = t3lib_beFunc::getRecord('pages', $rec[$checkField], 'uid,pid,perms_userid,perms_user,perms_groupid,perms_group,perms_everybody');
+                               if ($GLOBALS['BE_USER']->doesUserHaveAccess($page, 1)) {
+                                       $permittedElements[] = $rec;
+                               }
+                       }
+               }
+               return $permittedElements;
+       }
+}
+
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/workspaces/Classes/Service/Workspaces.php']);
+}
+?>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang.xml b/typo3/sysext/workspaces/Resources/Private/Language/locallang.xml
new file mode 100644 (file)
index 0000000..374274f
--- /dev/null
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <type>module</type>
+               <description>Language labels for module &quot;web_txworkspacesM1&quot;</description>
+       </meta>
+       <data type="array">
+               <languageKey index="default" type="array">
+                       <label index="legend.label">Legend:</label>
+                       <label index="legend.edited">Record editet</label>
+                       <label index="legend.moved">Record moved</label>
+                       <label index="legend.deleted">Record deleted</label>
+                       <label index="legend.new">Record created</label>
+                       <label index="legend.hidden">Record hidden</label>
+                       <label index="title">Workspaces</label>
+                       <label index="ok">ok</label>
+                       <label index="cancel">cancel</label>
+                       <label index="chooseMassAction">choose Mass Action</label>
+                       <label index="chooseAction">choose Action</label>
+                       <label index="item">item</label>
+                       <label index="items">items</label>
+                       <label index="recordsToDisplay">Records to display</label>
+                       <label index="reviewandpublish">review and publish</label>
+                       <label index="workspacelist">Workspace List</label>
+                       <label index="editorInLive">You are in LIVE workspace. You have to select a draft workspace before you can review and publish changes.</label>
+                       <label index="actionSendToStage">Send to stage </label>
+                       <label index="publish_execute_action_option">Publish  to LIVE</label>
+                       <label index="close">Close</label>
+                       <label index="runMassAction.doneProcessing">Done processing</label>
+                       <label index="runMassAction.elements"> elements</label>
+                       <label index="runMassAction.init">init</label>
+                       <label index="tooltip.publishAll">Really publish entire workspace?</label>
+                       <label index="tooltip.swapAll">Really swap entire workspace?</label>
+                       <label index="tooltip.releaseAll">Really release entire workspace?</label>
+                       <label index="previewLink">Preview Link</label>
+                       <label index="error.noResponse">The server did not send any response whether the action was successful.</label>
+                       <label index="rowDetails">Row details...</label>
+                       <label index="column.difference">Difference</label>
+                       <label index="column.changeDate">Modification</label>
+                       <label index="column.stage">Current Stage</label>
+                       <label index="column.actions">Actions</label>
+                       <label index="column.wsPath">Path</label>
+                       <label index="column.wsTitle">Changed</label>
+                       <label index="column.uid">WS-Id</label>
+                       <label index="column.oid">Live-Id</label>
+                       <label index="column.workspaceName">Workspace</label>
+                       <label index="column.wsTitle">Changed</label>
+                       <label index="column.wsTitle">Changed</label>
+                       <label index="column.wsTitle">Changed</label>
+                       <label index="column.wsTitle">Changed</label>
+                       <label index="column.livePath">Live-Path</label>
+                       <label index="column.liveTitle">Live-Title</label>
+                       <label index="tooltip.viewElementAction">Preview Element</label>
+                       <label index="tooltip.editElementAction">edit element</label>
+                       <label index="tooltip.openPage">Open version of page</label>
+                       <label index="tooltip.sendToPrevStage">Send record to previous Stage</label>
+                       <label index="tooltip.sendToNextStage">Send record to next Stage</label>
+                       <label index="tooltip.removeVersion">Remove version document</label>
+                       <label index="tooltip.swap">Swap live and workspace versions of record</label>
+                       <label index="window.remove.title">Remove version from workspace</label>
+                       <label index="window.remove.message">Do you really want to remove this version from workspace?</label>
+                       <label index="window.swap.title">Swap version</label>
+                       <label index="window.swap.message">Do you really want to swap this version??</label>
+                       <label index="window.massAction.title">Prepare to start mass action</label>
+                       <label index="error.stageId.integer">StageId is supposed to be an integer</label>
+                       <label index="error.sendToNextStage.noRecordFound">The record element could not be found.</label>
+                       <label index="error.sendToPrevStage.noPreviousStage">The record element is already in editing stage, there is no previous stage.</label>
+                       <label index="window.sendToNextStageWindow.itemsWillBeSentTo">The selected element(s) will be sent to </label>
+                       <label index="window.sendToNextStageWindow.sendMailTo">Send mail to</label>
+                       <label index="window.sendToNextStageWindow.additionalRecipients">Additional recipients</label>
+                       <label index="window.sendToNextStageWindow.comments">Comments</label>
+                       <label index="error.getStageTitle.stageNotFound">Stage not found</label>
+               </languageKey>
+       </data>
+</T3locallang>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xml b/typo3/sysext/workspaces/Resources/Private/Language/locallang_csh_sysws_stage.xml
new file mode 100644 (file)
index 0000000..c37679c
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <description>CSH for Workspace stage table.</description>
+               <type>CSH</type>
+               <csh_table>sys_workspace_stage</csh_table>
+       </meta>
+       <data type="array">
+               <languageKey index="default" type="array">
+                       <label index=".description">Defines custom workspace stages in TYPO3 which allows for groups of people to work together in a defined process. More information about workspaces can be found in the document &quot;Inside TYPO3&quot;.</label>
+                       <label index="title.description">Enter the name of the workspace stage.</label>
+                       <label index="responsible_persons.description">Define which BE users are responsible for this workspace stage. This selected BE users are possible recipients for the stage change emails.</label>
+                       <label index="default_mailcomment.description">Here its possible to define a standard mail comment which will be inserted into the stage change mail. When no standard mail comment was defined here its possible to write a comment for the mail on every stage change.</label>
+               </languageKey>
+       </data>
+</T3locallang>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xml b/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xml
new file mode 100644 (file)
index 0000000..e61cfec
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <type>database</type>
+               <description>Language labels for db fields of sys_workspace_stage and sys_workspace</description>
+       </meta>
+       <data type="array">
+               <languageKey index="default" type="array">
+                       <label index="sys_filemounts.tabs.staging">Staging</label>
+                       <label index="sys_workspace.custom_stages">Custom stages:</label>
+                       <label index="sys_workspace_stage.responsible_persons">Responsible persons:</label>
+                       <label index="sys_workspace_stage.default_mailcomment">Default mail comment:</label>
+                       <label index="sys_workspace_stage.parentid">UID of parent record:</label>
+                       <label index="sys_workspace_stage.parenttable">Parent table:</label>
+                       <label index="sys_workspace_stage">Workspace Stage</label>
+               </languageKey>
+       </data>
+</T3locallang>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang_mod.xml b/typo3/sysext/workspaces/Resources/Private/Language/locallang_mod.xml
new file mode 100644 (file)
index 0000000..8c611d7
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+       <meta type="array">
+               <type>module</type>
+               <description>Language labels for module &quot;web_txworkspacesM1&quot; - header, description</description>
+       </meta>
+       <data type="array">
+               <languageKey index="default" type="array">
+                       <label index="mlang_tabs_tab">Workspaces</label>
+                       <label index="autopublishTask.name">Workspaces auto-publication</label>
+                       <label index="autopublishTask.description">This tasks checks any workspace that has a publication date set in the past and automatically publishes it.</label>
+                       <label index="stage_ready_to_publish">Ready to publish</label>
+               </languageKey>
+       </data>
+</T3locallang>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Layouts/module.html b/typo3/sysext/workspaces/Resources/Private/Layouts/module.html
new file mode 100644 (file)
index 0000000..7b0ba19
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- ###FULLDOC### begin -->
+<div class="typo3-fullDoc">
+<!-- Page header with buttons, path details and csh -->
+<div id="typo3-docheader">
+<div id="typo3-docheader-row1">
+<div class="buttonsleft">
+       <f:if condition="{pageUid} > 0">
+               <a href="#" onclick="TYPO3.Workspaces.Actions.generateWorkspacePreviewLink();return false;" title="Generate page preview" id="goPreviewLinkButton"><span class="t3-icon t3-icon-actions t3-icon-actions-move t3-icon-move-right">&nbsp;</span>Generate Workspace Preview Link</a>
+       </f:if>
+</div>
+<div class="buttonsright"><f:be.buttons.shortcut /></div>
+</div>
+<div id="typo3-docheader-row2">
+<div class="docheader-row2-left"></div>
+<div class="docheader-row2-right"><f:be.pagePath /><f:be.pageInfo /></div>
+</div>
+</div>
+<!-- Content of module, for instance listing, info or editing -->
+<div id="typo3-docbody">
+<div id="typo3-inner-docbody">
+       <f:render partial="navigation" arguments="{workspaceList: workspaceList, activeWorkspaceUid: activeWorkspaceUid, showAllWorkspaceTab:showAllWorkspaceTab}" />
+       <div class="typo3-dyntabmenu-divs"><f:render section="main" /></div>
+       <f:if condition="{showLegend}"><f:render partial="legend" /></f:if>
+</div>
+</div>
+</div>
diff --git a/typo3/sysext/workspaces/Resources/Private/Layouts/nodoc.html b/typo3/sysext/workspaces/Resources/Private/Layouts/nodoc.html
new file mode 100644 (file)
index 0000000..3c5e0bc
--- /dev/null
@@ -0,0 +1,9 @@
+<!-- ###FULLDOC### begin -->
+<div class="typo3-noDoc">
+       <!-- Content of module, for instance listing, info or editing -->
+       <div id="typo3-docbody">
+               <div id="typo3-inner-docbody">
+                       <f:render section="main"/>
+               </div>
+       </div>
+</div>
diff --git a/typo3/sysext/workspaces/Resources/Private/Layouts/popup.html b/typo3/sysext/workspaces/Resources/Private/Layouts/popup.html
new file mode 100644 (file)
index 0000000..fc86011
--- /dev/null
@@ -0,0 +1,8 @@
+<!-- ###FULLDOC### begin -->
+<f:render section="main" />
+<script type="text/javascript">
+       var liveUrl = '{liveUrl}';
+       var wsUrl = '{wsUrl}';
+       var wsSettingsUrl = '{wsSettingsUrl}';
+       var wsHelpUrl = '{wsHelpUrl}';
+</script>
diff --git a/typo3/sysext/workspaces/Resources/Private/Partials/legend.html b/typo3/sysext/workspaces/Resources/Private/Partials/legend.html
new file mode 100644 (file)
index 0000000..9accc01
--- /dev/null
@@ -0,0 +1,8 @@
+<dl class="legend">
+       <dt><f:translate key="legend.label" /></dt>
+       <dd><span class="item-state-modified"><f:translate key="legend.edited" /></span>&nbsp;&nbsp;&bull;&nbsp;</dd>
+       <dd><span class="item-state-moved"><f:translate key="legend.moved" /></span>&nbsp;&nbsp;&bull;&nbsp;</dd>
+       <dd><span class="item-state-new"><f:translate key="legend.new" /></span>&nbsp;&nbsp;&bull;&nbsp;</dd>
+       <dd><span  class="item-state-hidden"><f:translate key="legend.hidden" /></span>&nbsp;&nbsp;&bull;&nbsp;</dd>
+       <dd><span class="item-state-deleted"><f:translate key="legend.deleted" /></span></dd>
+</dl>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Partials/navigation.html b/typo3/sysext/workspaces/Resources/Private/Partials/navigation.html
new file mode 100644 (file)
index 0000000..5e7c32a
--- /dev/null
@@ -0,0 +1,23 @@
+<ul class="x-tab-strip x-tab-strip-top">
+       <f:for each="{workspaceList}" as="workspace" key="uid">
+               <li class="{f:if(condition: '{uid}=={activeWorkspaceUid}', then: 'x-tab-strip-active')} x-tab-strip-closable">
+                       <f:link.action controller="Review" action="index" additionalParams="{workspace:uid}" class="x-tab-right">
+                       <em class="x-tab-left"><span class="x-tab-strip-inner"><span class="x-tab-strip-text">{workspace}</span></span></em>
+                       </f:link.action>
+               </li>
+       </f:for>
+       <f:if condition="{showAllWorkspaceTab}">
+               <li class="last {f:if(condition: '-98=={activeWorkspaceUid}', then: 'x-tab-strip-active')}">
+                       <f:link.action controller="Review" action="fullIndex" class="x-tab-right">
+                               <em class="x-tab-left">
+                                       <span class="x-tab-strip-inner">
+                                               <span class="x-tab-strip-text">
+                                                       All
+                                               </span>
+                                       </span>
+                               </em>
+                       </f:link.action>
+               </li>
+       </f:if>
+       <div class="x-clear"></div>
+ </ul>
diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Help.html
new file mode 100644 (file)
index 0000000..9b56cff
--- /dev/null
@@ -0,0 +1,3 @@
+<f:layout name="nodoc" />
+
+<f:section name="main">Help contents - not yet defined</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html b/typo3/sysext/workspaces/Resources/Private/Templates/Preview/Index.html
new file mode 100644 (file)
index 0000000..29d3534
--- /dev/null
@@ -0,0 +1,3 @@
+<f:layout name="popup" />
+
+<f:section name="main"></f:section>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html b/typo3/sysext/workspaces/Resources/Private/Templates/Review/FullIndex.html
new file mode 100644 (file)
index 0000000..e80d90f
--- /dev/null
@@ -0,0 +1,5 @@
+<f:layout name="module" />
+
+<f:section name="main">
+<div id="workspacegrid"></div>
+</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html b/typo3/sysext/workspaces/Resources/Private/Templates/Review/Index.html
new file mode 100644 (file)
index 0000000..50005c5
--- /dev/null
@@ -0,0 +1,10 @@
+<f:layout name="module" />
+
+<f:section name="main">
+<f:if condition="{showGrid}">
+       <f:then><div id="workspacegrid"></div>
+       </f:then>
+       <f:else><f:translate key="editorInLive" /></f:else>
+</f:if>
+
+</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html b/typo3/sysext/workspaces/Resources/Private/Templates/Review/SingleIndex.html
new file mode 100644 (file)
index 0000000..00a2686
--- /dev/null
@@ -0,0 +1,5 @@
+<f:layout name="nodoc" />
+
+<f:section name="main">
+<div id="workspacegrid"></div>
+</f:section>
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/Images/bg.gif b/typo3/sysext/workspaces/Resources/Public/Images/bg.gif
new file mode 100644 (file)
index 0000000..8624666
Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/bg.gif differ
diff --git a/typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif b/typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif
new file mode 100644 (file)
index 0000000..d389a76
Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/moduleicon.gif differ
diff --git a/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png
new file mode 100644 (file)
index 0000000..b1a1819
Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtonextstage.png differ
diff --git a/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png
new file mode 100644 (file)
index 0000000..50d36c7
Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/version-workspace-sendtoprevstage.png differ
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js
new file mode 100644 (file)
index 0000000..f7fcc44
--- /dev/null
@@ -0,0 +1,233 @@
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+
+Ext.ns('TYPO3.Workspaces');
+
+TYPO3.Workspaces.Actions = {
+
+       runningMassAction: null,
+       triggerMassAction: function(action) {
+
+               switch (action) {
+                       case 'publish':
+                       case 'swap':
+                               this.runningMassAction = TYPO3.Workspaces.ExtDirectMassActions.publishWorkspace;
+                               break;
+                       case 'release':
+                               this.runningMassAction = TYPO3.Workspaces.ExtDirectMassActions.flushWorkspace;
+                               break;
+               }
+
+               this.runMassAction({
+                       init: true,
+                       total:0,
+                       processed:0,
+                       swap: (action == 'swap')
+               });
+       },
+
+       runMassAction: function(parameters) {
+               if (parameters.init) {
+                       top.Ext.getCmp('executeMassActionForm').hide();
+                       top.Ext.getCmp('executeMassActionProgressBar').show();
+                       top.Ext.getCmp('executeMassActionOkButton').disable();
+               }
+
+               var progress = parameters.total > 0 ? parameters.processed / parameters.total : 0;
+               var label = parameters.total > 0 ? parameters.processed + '/' + parameters.total : TYPO3.lang["runMassAction.init"];
+               top.Ext.getCmp('executeMassActionProgressBar').updateProgress(progress, label, true);
+
+               this.runningMassAction(parameters, TYPO3.Workspaces.Actions.runMassActionCallback);
+       },
+
+       runMassActionCallback: function(response) {
+               if (response.error) {
+                       top.Ext.getCmp('executeMassActionProgressBar').hide();
+                       top.Ext.getCmp('executeMassActionOkButton').hide();
+                       top.Ext.getCmp('executeMassActionCancleButton').setText(TYPO3.lang.close);
+                       top.Ext.getCmp('executeMassActionForm').show();
+                       top.Ext.getCmp('executeMassActionForm').update(response.error);
+               } else {
+                       if (response.total > response.processed) {
+                               TYPO3.Workspaces.Actions.runMassAction(response);
+                       } else {
+                               top.Ext.getCmp('executeMassActionProgressBar').hide();
+                               top.Ext.getCmp('executeMassActionOkButton').hide();
+                               top.Ext.getCmp('executeMassActionCancleButton').setText(TYPO3.lang.close);
+                               top.Ext.getCmp('executeMassActionForm').show();
+                               top.Ext.getCmp('executeMassActionForm').update(TYPO3.lang["runMassAction.doneProcessing"] + response.total + TYPO3.lang["runMassAction.elements"]);
+                       }
+               }
+       },
+       generateWorkspacePreviewLink: function() {
+               TYPO3.Workspaces.ExtDirectActions.generateWorkspacePreviewLink(TYPO3.settings.Workspaces.id, function(response) {
+                       top.TYPO3.Dialog.getInformationDialog({title: TYPO3.lang.previewLink, msg: response});
+               });
+       },
+       swapSingleRecord: function(table, t3ver_oid, orig_uid) {
+               TYPO3.Workspaces.ExtDirectActions.swapSingleRecord(table, t3ver_oid, orig_uid, function(response) {
+                       TYPO3.Workspaces.MainStore.load();
+               });
+       },
+       deleteSingleRecord: function(table, uid) {
+               TYPO3.Workspaces.ExtDirectActions.deleteSingleRecord(table, uid, function(response) {
+                       TYPO3.Workspaces.MainStore.load();
+               });
+       },
+       viewSingleRecord: function(pid) {
+               TYPO3.Workspaces.ExtDirectActions.viewSingleRecord(pid, function(response) {
+                       eval(response);
+               });
+       },
+       sendToNextStageWindow: function(table, uid, t3ver_oid) {
+               TYPO3.Workspaces.ExtDirectActions.sendToNextStageWindow(table, uid, t3ver_oid, function(response) {
+                       if (Ext.isObject(response.error)) {
+                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                       } else {
+                               var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
+                                       title: response.title,
+                                       items: response.items,
+                                       executeHandler: function(event) {
+                                               var values = top.Ext.getCmp('sendToStageForm').getForm().getValues();
+
+                                               var parameters = {
+                                                       affects: response.affects,
+                                                       receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'),
+                                                       additional: values.additional,
+                                                       comments: values.comments
+                                               };
+
+                                               TYPO3.Workspaces.Actions.sendToNextStageExecute(parameters);
+                                               top.TYPO3.Windows.close('sendToStageWindow');
+                                               TYPO3.Workspaces.MainStore.reload();
+                                       }
+                               });
+                       }
+               });
+       },
+       sendToPrevStageWindow: function(table, uid, t3ver_oid) {
+               TYPO3.Workspaces.ExtDirectActions.sendToPrevStageWindow(table, uid, function(response) {
+                       if (Ext.isObject(response.error)) {
+                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                       } else {
+                               var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
+                                       title: response.title,
+                                       items: response.items,
+                                       executeHandler: function(event) {
+                                               var values = top.Ext.getCmp('sendToStageForm').getForm().getValues();
+
+                                               var parameters = {
+                                                       affects: response.affects,
+                                                       receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'),
+                                                       additional: values.additional,
+                                                       comments: values.comments
+                                               };
+
+                                               TYPO3.Workspaces.Actions.sendToPrevStageExecute(parameters);
+                                               top.TYPO3.Windows.close('sendToStageWindow');
+                                               TYPO3.Workspaces.MainStore.reload();
+                                       }
+                               });
+                       }
+               });
+       },
+       sendToSpecificStageWindow: function(selection, nextStage) {
+               TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageWindow(nextStage, function(response) {
+                       if (Ext.isObject(response.error)) {
+                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                       } else {
+                               var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
+                                       title: response.title,
+                                       items: response.items,
+                                       executeHandler: function(event) {
+                                               var values = top.Ext.getCmp('sendToStageForm').getForm().getValues();
+
+                                               var parameters = {
+                                                       affects: {
+                                                               nextStage: response.affects.nextStage,
+                                                               elements: TYPO3.Workspaces.Helpers.getElementsArrayOfSelection(selection)
+                                                       },
+                                                       receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'),
+                                                       additional: values.additional,
+                                                       comments: values.comments
+                                               };
+
+                                               TYPO3.Workspaces.Actions.sendToSpecificStageExecute(parameters);
+                                               top.TYPO3.Windows.close('sendToStageWindow');
+                                               TYPO3.Workspaces.MainStore.reload();
+                                       }
+                               });
+                       }
+               });
+       },
+       sendToNextStageExecute: function (parameters) {
+               TYPO3.Workspaces.ExtDirectActions.sendToNextStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction);
+       },
+       sendToPrevStageExecute: function (parameters) {
+               TYPO3.Workspaces.ExtDirectActions.sendToPrevStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction);
+       },
+       sendToSpecificStageExecute: function (parameters) {
+               TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageExecute(parameters, TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction);
+       },
+       updateColModel: function(colModel) {
+               var dataArray = [];
+               for (var i = 0; i < colModel.config.length; i++) {
+                       if (colModel.config[i].dataIndex !== '') {
+                               dataArray.push({
+                                       'position': i,
+                                       'column': colModel.config[i].dataIndex,
+                                       'hidden': colModel.config[i].hidden ? 1 : 0
+                               });
+                       }
+               }
+               TYPO3.Workspaces.ExtDirectActions.saveColumnModel(dataArray);
+       },
+       loadColModel: function(grid) {
+               TYPO3.Workspaces.ExtDirectActions.loadColumnModel(function(response) {
+                       var colModel = grid.getColumnModel();
+                       for (var field in response) {
+                               var colIndex = colModel.getIndexById(field);
+                               if (colIndex != -1) {
+                                       colModel.setHidden(colModel.getIndexById(field), (response[field].hidden == 1 ? true : false));
+                                       colModel.moveColumn(colModel.getIndexById(field), response[field].position);
+                               }
+                       }
+               });
+
+       },
+       handlerResponseOnExecuteAction: function(response) {
+               if (!Ext.isObject(response)) {
+                       response = { error: { message: TYPO3.lang["error.noResponse"] }};
+               }
+
+               if (Ext.isObject(response.error)) {
+                       var error = response.error;
+                       var code = (error.code ? ' #' + error.code : '');
+                       top.TYPO3.Dialog.ErrorDialog({ title: 'Error' + code, msg: error.message });
+               }
+       }
+};
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/component.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/component.js
new file mode 100644 (file)
index 0000000..784819b
--- /dev/null
@@ -0,0 +1,262 @@
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+
+Ext.ns('TYPO3.Workspaces');
+
+TYPO3.Workspaces.Component = {};
+
+TYPO3.Workspaces.RowDetail = {};
+TYPO3.Workspaces.RowDetail.rowDataStore = new Ext.data.DirectStore({
+       storeId : 'rowDetailService',
+       root : 'data',
+       totalProperty : 'total',
+       idProperty : 'uid',
+       fields : [
+               {name : 'uid'},
+               {name : 't3ver_oid'},
+               {name : 'table'},
+               {name : 'stage'},
+               {name : 'diff'},
+               {name : 'path_Live'},
+               {name : 'label_Stage'},
+               {name : 'stage_position'},
+               {name : 'stage_count'},
+               {name : 'live_record'},
+               {name : 'comments'},
+               {name : 'icon_Live'},
+               {name : 'icon_Workspace'}
+       ]
+});
+TYPO3.Workspaces.RowDetail.rowDetailTemplate = new Ext.XTemplate(
+       '<div class="t3-workspaces-foldoutWrapper">',
+       '<tpl for=".">',
+               '<tpl>',
+                       '<table class="char_select_template" width="100%">',
+                               '<tr class="header">',
+                                       '<th class="char_select_profile_title">',
+                                               'Workspace Version',
+                                       '</th>',
+                                       '<th class="char_select_profile_title">',
+                                               'Live Workspace',
+                                       '</th>',
+                               '</tr>',
+                               '<tr>',
+                                       '<td class="t3-workspaces-foldout-subheader">',
+                                               '<b>Current stage step:</b> {label_Stage} (<b>{stage_position}</b>/{stage_count})',
+                                       '</td>',
+                                       '<td class="t3-workspaces-foldout-subheader">',
+                                               '<b>Path:</b> {path_Live}',
+                                       '</td>',
+                               '</tr>',
+                               '<tr>',
+                                       '<td class="t3-workspaces-foldout-td-contentDiff">',
+                                               '<table class="t3-workspaces-foldout-contentDiff">',
+                                                       '<span class="{icon_Workspace}">&nbsp;</span>',
+                                                       '<tpl for="diff">',
+                                                               '<tr><th>{label}</th><td>{content}</td></tr>',
+                                                       '</tpl>',
+                                               '</table>',
+                                       '</td>',
+                                       '<td class="t3-workspaces-foldout-td-contentDiff">',
+                                               '<table class="t3-workspaces-foldout-contentDiff">',
+                                                       '<span class="{icon_Live}"></span>',
+                                                       '<tpl for="live_record">',
+                                                               '<tr><th>{label}</th><td>{content}</td></tr>',
+                                                       '</tpl>',
+                                               '</table>',
+                                       '</td>',
+                               '</tr>',
+                               '<tr>',
+                                       '<td class="t3-workspaces-foldout-subheader">',
+                                               'User comments for <b>step {stage_position} of stage</b> "{label_Stage}"',
+                                       '</td>',
+                                       '<td class="t3-workspaces-foldout-subheader">',
+                                               '&nbsp;',
+                                       '</td>',
+                               '</tr>',
+                               '<tr>',
+                                       '<td class="char_select_profile_stats">',
+                                               '<div class="t3-workspaces-comments">',
+                                               '<tpl for="comments">',
+                                                       '<div class="t3-workspaces-comments-singleComment">',
+                                                               '<div class="t3-workspaces-comments-singleComment-author">',
+                                                                       '{user_username}',
+                                                               '</div>',
+                                                               '<div class="t3-workspaces-comments-singleComment-content">',
+                                                                       '<span class="t3-workspaces-comments-singleComment-content-date">{tstamp}</span> @ Stage {stage_title}',
+                                                                       '<div class="t3-workspaces-comments-singleComment-content-text">{user_comment}</div>',
+                                                               '</div>',
+                                                       '</div>',
+                                               '</tpl>',
+                                               '</div>',
+                                       '</td>',
+                                       '<td class="char_select_profile_title">',
+                                               '&nbsp;',
+                                       '</td>',
+                               '</tr>',
+                       '</table>',
+               '</tpl>',
+       '</tpl>',
+       '</div>',
+       '<div class="x-clear"></div>'
+);
+
+TYPO3.Workspaces.RowDetail.rowDataView = new Ext.DataView({
+       store: TYPO3.Workspaces.RowDetail.rowDataStore,
+       tpl: TYPO3.Workspaces.RowDetail.rowDetailTemplate
+});
+
+Ext.ns('Ext.ux.TYPO3.Workspace');
+Ext.ux.TYPO3.Workspace.RowPanel = Ext.extend(Ext.Panel, {
+       constructor: function(config) {
+               config = config || {
+                       frame:true,
+                       width:'100%',
+                       autoHeight:true,
+                       layout:'fit',
+                       title: TYPO3.lang.rowDetails
+               };
+               Ext.apply(this, config);
+               Ext.ux.TYPO3.Workspace.RowPanel.superclass.constructor.call(this, config);
+       }
+});
+
+TYPO3.Workspaces.RowExpander = new Ext.grid.RowExpander({
+       menuDisabled: true,
+       hideable: false,
+       remoteDataMethod : function (record, index) {
+               TYPO3.Workspaces.RowDetail.rowDataStore.baseParams = {
+                       uid: record.json.uid,
+                       table: record.json.table,
+                       stage: record.json.stage,
+                       t3ver_oid: record.json.t3ver_oid,
+                       path_Live: record.json.path_Live,
+                       label_Stage: record.json.label_Stage
+               };
+               TYPO3.Workspaces.RowDetail.rowDataStore.load({
+                       callback: function(r, options, success) {
+                               TYPO3.Workspaces.RowExpander.expandRow(index);
+                       }
+               });
+               new Ext.ux.TYPO3.Workspace.RowPanel({
+                       renderTo: 'remData' + index,
+                       items: TYPO3.Workspaces.RowDetail.rowDataView
+               });
+       },
+       onMouseDown : function(e, t) {
+               tObject = Ext.get(t);
+               if (tObject.hasClass('x-grid3-row-expander')) {
+                       e.stopEvent();
+                       var row = e.getTarget('.x-grid3-row');
+                       this.toggleRow(row);
+               }
+       },
+       toggleRow : function(row) {
+               this[Ext.fly(row).hasClass('x-grid3-row-collapsed') ? 'beforeExpand' : 'collapseRow'](row);
+       },
+       beforeExpand : function(row) {
+               if (typeof row == 'number') {
+                       row = this.grid.view.getRow(row);
+               }
+               var record = this.grid.store.getAt(row.rowIndex);
+               var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
+
+               if (this.fireEvent('beforexpand', this, record, body, row.rowIndex) !== false) {
+                       this.tpl = new Ext.Template("<div id=\"remData" + row.rowIndex + "\" class=\"rem-data-expand\"><\div>");
+                       if (this.tpl && this.lazyRender) {
+                               body.innerHTML = this.getBodyContent(record, row.rowIndex);
+                       }
+               }
+                       // toggle remoteData loading
+               this.remoteDataMethod(record, row.rowIndex);
+               return true;
+       },
+       expandRow : function(row) {
+               if (typeof row == 'number') {
+                       row = this.grid.view.getRow(row);
+               }
+               var record = this.grid.store.getAt(row.rowIndex);
+               var body = Ext.DomQuery.selectNode('tr:nth(2) div.x-grid3-row-body', row);
+               this.state[record.id] = true;
+               Ext.fly(row).replaceClass('x-grid3-row-collapsed', 'x-grid3-row-expanded');
+               this.fireEvent('expand', this, record, body, row.rowIndex);
+               var i;
+               for(i = 0; i < this.grid.store.getCount(); i++) {
+                       if(i != row.rowIndex) {
+                               this.collapseRow(i);
+                       }
+               }
+       },
+       collapseRow : function(row) {
+               if (typeof row == 'number') {
+                       row = this.grid.view.getRow(row);
+               }
+               var record = this.grid.store.getAt(row.rowIndex);
+               var body = Ext.fly(row).child('tr:nth(1) div.x-grid3-row-body', true);
+               if (this.fireEvent('beforcollapse', this, record, body, row.rowIndex) !== false) {
+                       this.state[record.id] = false;
+                       Ext.fly(row).replaceClass('x-grid3-row-expanded', 'x-grid3-row-collapsed');
+                       this.fireEvent('collapse', this, record, body, row.rowIndex);
+               }
+       }
+});
+
+
+TYPO3.Workspaces.MainStore = new Ext.data.GroupingStore({
+       storeId : 'workspacesMainStore',
+       reader : new Ext.data.JsonReader({
+               idProperty : 'uid',
+               root : 'data',
+               totalProperty : 'total'
+       }, TYPO3.Workspaces.Configuration.StoreFieldArray),
+       groupField: 'path_Workspace',
+       remoteGroup: false,
+       paramsAsHash : true,
+       sortInfo : {
+               field : 'label_Live',
+               direction : "ASC"
+       },
+       remoteSort : true,
+       baseParams: {
+               depth : 990,
+               id: TYPO3.settings.Workspaces.id,
+               query: '',
+               start: 0,
+               limit: 10
+       },
+
+       showAction : false,
+       listeners : {
+               beforeload : function() {
+               },
+               load : function(store, records) {
+               },
+               datachanged : function(store) {
+               },
+               scope : this
+       }
+});
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js
new file mode 100644 (file)
index 0000000..c9fa34f
--- /dev/null
@@ -0,0 +1,370 @@
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+Ext.ns('TYPO3.Workspaces');
+TYPO3.Workspaces.Configuration = {};
+
+TYPO3.Workspaces.Configuration.GridFilters = new Ext.ux.grid.GridFilters({
+       encode : false, // json encode the filter query
+       local : true, // defaults to false (remote filtering)
+       filters : [
+               {
+                       type : 'numeric',
+                       dataIndex : 'uid'
+               },
+               {
+                       type : 'string',
+                       dataIndex : 'workspace_Title'
+               },
+               {
+                       type : 'string',
+                       dataIndex : 'label_Live'
+               },
+               {
+                       type : 'string',
+                       dataIndex : 'label_Workspace'
+               },
+               {
+                       type : 'numeric',
+                       dataIndex : 'change'
+               }
+       ]
+});
+TYPO3.Workspaces.Configuration.StoreFieldArray = [
+       {name : 'table'},
+       {name : 'uid', type : 'int'},
+       {name : 't3ver_oid', type : 'int'},
+       {name : 'livepid', type : 'int'},
+       {name : 'stage', type: 'int'},
+       {name : 'change',type : 'int'},
+       {name : 'label_Live'},
+       {name : 'label_Workspace'},
+       {name : 'label_Stage'},
+       {name : 'workspace_Title'},
+       {name : 'actions'},
+       {name : 'icon_Workspace'},
+       {name : 'icon_Live'},
+       {name : 'path_Live'},
+       {name : 'path_Workspace'},
+       {name : 'state_Workspace'},
+       {name : 'workspace_Tstamp'},
+       {name : 'workspace_Formated_Tstamp'},
+       {name : 'allowedAction_nextStage'},
+       {name : 'allowedAction_prevStage'},
+       {name : 'allowedAction_swap'},
+       {name : 'allowedAction_delete'},
+       {name : 'allowedAction_edit'},
+       {name : 'allowedAction_editVersionedPage'},
+       {name : 'allowedAction_view'}
+
+];
+
+TYPO3.Workspaces.Configuration.WsPath = {
+       id: 'path_Workspace',
+       dataIndex : 'path_Workspace',
+       width: 120,
+       hidden: true,
+       hideable: false,
+       sortable: true,
+       header : TYPO3.lang["column.wsPath"],
+       renderer: function(value, metaData, record, rowIndex, colIndex, store) {
+               var path = record.json.path_Workspace;
+               return path;
+       },
+       filter : {type: 'string'}
+};
+TYPO3.Workspaces.Configuration.LivePath = {
+       id: 'path_Live',
+       dataIndex : 'path_Live',
+       width: 120,
+       hidden: true,
+       hideable: true,
+       sortable: true,
+       header : TYPO3.lang["column.livePath"],
+       renderer: function(value, metaData, record, rowIndex, colIndex, store) {
+               var path = record.json.path_Live;
+               return path;
+       },
+       filter : {type: 'string'}
+};
+TYPO3.Workspaces.Configuration.WsTitleWithIcon = {
+       id: 'label_Workspace',
+       dataIndex : 'label_Workspace',
+       width: 120,
+       hideable: false,
+       sortable: true,
+       header : TYPO3.lang["column.wsTitle"],
+       renderer: function(value, metaData, record, rowIndex, colIndex, store) {
+               var dekoClass = 'item-state-' + record.json.state_Workspace;
+               value = "<span class=\"" + dekoClass + "\">" + value + "</span>";
+               if (record.json.icon_Live === record.json.icon_Workspace) {
+                       return value;
+               } else {
+                       return "<span class=\"" + record.json.icon_Workspace + "\">&nbsp;</span>&nbsp;" + value;
+               }
+
+       },
+       filter : {type: 'string'}
+};
+TYPO3.Workspaces.Configuration.TitleWithIcon = {
+       id: 'label_Live',
+       dataIndex : 'label_Live',
+       width: 120,
+       hideable: false,
+       sortable: true,
+       header : TYPO3.lang["column.liveTitle"],
+       renderer: function(value, metaData, record, rowIndex, colIndex, store) {
+               if (record.json.state_Workspace == 'unhidden') {
+                       var dekoClass = 'item-state-hidden';
+               } else {
+                       var dekoClass = '';
+               }
+
+               value = "<span class=\"" + dekoClass + "\">" + value + "</span>";
+               return "<span class=\"" + record.json.icon_Live + "\">&nbsp;</span>&nbsp;" + value;
+       },
+       filter : {type: 'string'}
+};
+TYPO3.Workspaces.Configuration.ChangeState = {
+       id: 'state-change',
+       dataIndex : 'change',
+       width: 80,
+       sortable: true,
+       header : TYPO3.lang["column.difference"],
+       renderer: function(value, metaData) {
+               return value + "%";
+       },
+       filter : {type: 'numeric'}
+};
+TYPO3.Workspaces.Configuration.ChangeDate = {
+       id: 'workspace_Tstamp',
+       dataIndex : 'workspace_Tstamp',
+       width: 120,
+       sortable: true,
+       header : TYPO3.lang["column.changeDate"],
+       renderer: function(value, metaData, record, rowIndex, colIndex, store) {
+               return record.json.workspace_Formated_Tstamp;
+       },
+       hidden: true,
+       filter : {type : 'string'}
+};
+
+TYPO3.Workspaces.Configuration.SendToPrevStageButton = {
+       xtype: 'actioncolumn',
+       header:'',
+       width: 18,
+       items:[
+               {
+                       iconCls: 't3-icon t3-icon-extensions t3-icon-extensions-workspaces t3-icon-workspaces-sendtoprevstage'
+                       ,tooltip: TYPO3.lang["tooltip.sendToPrevStage"]
+                       ,handler: function(grid, rowIndex, colIndex) {
+                               var record = TYPO3.Workspaces.MainStore.getAt(rowIndex);
+                               TYPO3.Workspaces.Actions.sendToPrevStageWindow(record.json.table, record.json.uid);
+                       }
+               }
+       ]
+};
+
+TYPO3.Workspaces.Configuration.SendToNextStageButton = {
+       xtype: 'actioncolumn',
+       header:'',
+       width: 18,
+       items: [
+               {},{    // empty dummy important!!!!
+                       iconCls: 't3-icon t3-icon-extensions t3-icon-extensions-workspaces t3-icon-workspaces-sendtonextstage',
+                       tooltip: TYPO3.lang["tooltip.sendToNextStage"],
+                       handler: function(grid, rowIndex, colIndex) {
+                               var record = TYPO3.Workspaces.MainStore.getAt(rowIndex);
+                               TYPO3.Workspaces.Actions.sendToNextStageWindow(record.json.table, record.json.uid, record.json.t3ver_oid);
+                       }
+               }
+       ]
+};
+
+TYPO3.Workspaces.Configuration.Stage = {
+       id: 'label_Stage',
+       dataIndex : 'label_Stage',
+       width: 80,
+       sortable: true,
+       header : TYPO3.lang["column.stage"],
+       hidden: false,
+       filter : {
+               type : 'string'
+       },
+       renderer: function(value, metaData, record, rowIndex, colIndex, store) {
+               var returnCode = '';
+               if (record.json.allowedAction_prevStage) {
+                       var prevButton = new Ext.grid.ActionColumn(TYPO3.Workspaces.Configuration.SendToPrevStageButton);
+                       returnCode += prevButton.renderer(1, metaData, record, rowIndex, 1, store);
+               } else {
+                       returnCode += "<span class=\"t3-icon t3-icon-empty t3-icon-empty-empty\">&nbsp;</span>";
+               }
+               returnCode += record.json.label_Stage;
+               if (record.json.allowedAction_nextStage) {
+                       var nextButton = new Ext.grid.ActionColumn(TYPO3.Workspaces.Configuration.SendToNextStageButton);
+                       returnCode += nextButton.renderer(2, metaData, record, rowIndex, 2, store);
+               } else {
+                       returnCode += "<span class=\"t3-icon t3-icon-empty t3-icon-empty-empty\">&nbsp;</span>";
+               }
+               return returnCode;
+       },
+       processEvent : function(name, e, grid, rowIndex, colIndex){
+               var m = e.getTarget().className.match(/x-action-col-(\d+)/);
+               if(m && m[1] == 0) {
+                       TYPO3.Workspaces.Configuration.SendToPrevStageButton.items[0].handler(grid, rowIndex, colIndex);
+                       return false;
+               } else if (m && m[1] == 1 ) {
+                       TYPO3.Workspaces.Configuration.SendToNextStageButton.items[1].handler(grid, rowIndex, colIndex);
+                       return false;
+               }
+               return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments);
+       }
+}
+
+TYPO3.Workspaces.Configuration.RowButtons = {
+       xtype: 'actioncolumn',
+       header: TYPO3.lang["column.actions"],
+       width: 50,
+       hideable: false,
+       menuDisabled: true,
+       items: [
+               {
+                       iconCls:'t3-icon t3-icon-actions t3-icon-actions-version t3-icon-version-workspace-preview'
+                       ,tooltip: TYPO3.lang["tooltip.viewElementAction"]
+                       ,handler: function(grid, rowIndex, colIndex) {
+                               var record = TYPO3.Workspaces.MainStore.getAt(rowIndex);
+                               if (record.json.table == 'pages') {
+                                       TYPO3.Workspaces.Actions.viewSingleRecord(record.json.t3ver_oid);
+                               } else {
+                                       TYPO3.Workspaces.Actions.viewSingleRecord(record.json.livepid);
+                               }
+                       },
+                       getClass: function(v, meta, rec) {
+                               if(!rec.json.allowedAction_view) {
+                                       return 'icon-hidden';
+                               } else {
+                                       return '';
+                               }
+                       }
+               },
+               {
+                       iconCls:'t3-icon t3-icon-actions t3-icon-actions-document t3-icon-document-open',
+                       tooltip: TYPO3.lang["tooltip.editElementAction"],
+                       handler: function(grid, rowIndex, colIndex) {
+                               var record = TYPO3.Workspaces.MainStore.getAt(rowIndex);
+                               window.location.href = 'alt_doc.php?returnUrl=' + Ext.urlEncode({}, document.location.href) + '&id=' + TYPO3.settings.Workspaces.id + '&edit[' + record.json.table + '][' + record.json.uid + ']=edit';
+                       },
+                       getClass: function(v, meta, rec) {
+                               if(!rec.json.allowedAction_edit) {
+                                       return 'icon-hidden';
+                               } else {
+                                       return '';
+                               }
+                       }
+               },
+               {
+                       iconCls:'t3-icon t3-icon-actions t3-icon-actions-system t3-icon-system-pagemodule-open',
+                       tooltip: TYPO3.lang["tooltip.openPage"],
+                       handler: function(grid, rowIndex, colIndex) {
+                               var record = TYPO3.Workspaces.MainStore.getAt(rowIndex);
+                               if (record.json.table == 'pages') {
+                                       top.loadEditId(record.json.t3ver_oid);
+                               } else {
+                                       top.loadEditId(record.json.realpid);
+                               }
+                       },
+                       getClass: function(v, meta, rec) {
+                               if(!rec.json.allowedAction_editVersionedPage) {
+                                       return 'icon-hidden';
+                               } else {
+                                       return '';
+                               }
+                       }
+               },
+               {
+                       iconCls:'t3-icon t3-icon-actions t3-icon-actions-version t3-icon-version-document-remove',
+                       tooltip: TYPO3.lang["tooltip.removeVersion"],
+                       handler: function(grid, rowIndex, colIndex) {
+                               var record = TYPO3.Workspaces.MainStore.getAt(rowIndex);
+                               var configuration = {
+                                       title: TYPO3.lang["window.remove.title"],
+                                       msg: TYPO3.lang["window.remove.message"],
+                                       fn: function(result) {
+                                               if (result == 'yes') {
+                                                       TYPO3.Workspaces.Actions.deleteSingleRecord(record.json.table, record.json.uid);
+                                               }
+                                       }
+                               };
+
+                               top.TYPO3.Dialog.QuestionDialog(configuration);
+                       },
+                       getClass: function(v, meta, rec) {
+                               if(!rec.json.allowedAction_delete) {
+                                       return 'icon-hidden';
+                               } else {
+                                       return '';
+                               }
+                       }
+               }
+       ]
+};
+
+TYPO3.Workspaces.Configuration.SwapButton = {
+       xtype: 'actioncolumn',
+       header: '',
+       id: 'wsSwapColumn',
+       width: 18,
+       menuDisabled: true,
+       sortable: false,
+       items: [
+               {
+                       iconCls:'t3-icon t3-icon-actions t3-icon-actions-version t3-icon-version-swap-workspace'
+                       ,tooltip: TYPO3.lang["tooltip.swap"]
+                       ,handler: function(grid, rowIndex, colIndex) {
+                               var record = TYPO3.Workspaces.MainStore.getAt(rowIndex);
+                               var configuration = {
+                                       title: TYPO3.lang["window.swap.title"],
+                                       msg: TYPO3.lang["window.swap.message"],
+                                       fn: function(result) {
+                                               if (result == 'yes') {
+                                                       TYPO3.Workspaces.Actions.swapSingleRecord(record.json.table, record.json.wsversion, record.json.uid);
+                                               }
+                                       }
+                               };
+
+                               top.TYPO3.Dialog.QuestionDialog(configuration);
+                       },
+                       getClass: function(v, meta, rec) {
+                               if(!rec.json.allowedAction_swap) {
+                                       return 'icon-hidden';
+                               } else {
+                                       return '';
+                               }
+                       }
+               }
+       ]
+};
+
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/grid.js
new file mode 100644 (file)
index 0000000..3282add
--- /dev/null
@@ -0,0 +1,118 @@
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2010 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!
+ ***************************************************************/
+
+Ext.ns('TYPO3.Workspaces');
+
+Ext.override(Ext.grid.GridView, {
+       beforeColMenuShow : function() {
+               var colModel = this.cm,
+                       colCount = colModel.getColumnCount(),
+                       colMenu = this.colMenu,
+                       i, text;
+
+               colMenu.removeAll();
+
+               for (i = 0; i < colCount; i++) {
+                       if (colModel.config[i].hideable !== false) {
+                               text = colModel.getColumnHeader(i);
+                               if (colModel.getColumnId(i) === 'wsSwapColumn') {
+                                       text = 'Swap workspaces';  //use language label
+                               }
+                               colMenu.add(new Ext.menu.CheckItem({
+                                       text: text,
+                                       itemId: 'col-' + colModel.getColumnId(i),
+                                       checked: !colModel.isHidden(i),
+                                       disabled: colModel.config[i].hideable === false,
+                                       hideOnClick: false
+                               }));
+                       }
+               }
+       }
+});
+
+TYPO3.Workspaces.SelectionModel = new Ext.grid.CheckboxSelectionModel({
+       singleSelect: false,
+       hidden: true,
+       listeners: {
+               selectionchange: function (selection) {
+                       var record = selection.grid.getSelectionModel().getSelections();
+                       if (record.length > 0) {
+                               TYPO3.Workspaces.Toolbar.selectStateActionCombo.setDisabled(false);
+                               TYPO3.Workspaces.Toolbar.selectStateMassActionCombo.setDisabled(true);
+                       } else {
+                               TYPO3.Workspaces.Toolbar.selectStateActionCombo.setDisabled(true);
+                               TYPO3.Workspaces.Toolbar.selectStateMassActionCombo.setDisabled(false);
+                       }
+               }
+       }
+});
+
+TYPO3.Workspaces.WorkspaceGrid = new Ext.grid.GridPanel({
+       border : true,
+       store : TYPO3.Workspaces.MainStore,
+       colModel : new Ext.grid.ColumnModel({
+               columns: [
+                       TYPO3.Workspaces.SelectionModel,
+                       TYPO3.Workspaces.RowExpander,
+                       {id: 'uid', dataIndex : 'uid', width: 40, sortable: true, header : TYPO3.lang["column.uid"], hidden: true, filterable : true },
+                       {id: 't3ver_oid', dataIndex : 't3ver_oid', width: 40, sortable: true, header : TYPO3.lang["column.oid"], hidden: true, filterable : true },
+                       {id: 'workspace_Title', dataIndex : 'workspace_Title', width: 120, sortable: true, header : TYPO3.lang["column.workspaceName"], hidden: true, filter : {type : 'string'}},
+                       TYPO3.Workspaces.Configuration.WsPath,
+                       TYPO3.Workspaces.Configuration.LivePath,
+                       TYPO3.Workspaces.Configuration.WsTitleWithIcon,
+                       TYPO3.Workspaces.Configuration.SwapButton,
+                       TYPO3.Workspaces.Configuration.TitleWithIcon,
+                       TYPO3.Workspaces.Configuration.ChangeDate,
+                       TYPO3.Workspaces.Configuration.ChangeState,
+                       TYPO3.Workspaces.Configuration.Stage,
+                       TYPO3.Workspaces.Configuration.RowButtons
+               ],
+               listeners: {
+                       columnmoved: function(colModel) {
+                               TYPO3.Workspaces.Actions.updateColModel(colModel);
+                       },
+                       hiddenchange: function(colModel) {
+                               TYPO3.Workspaces.Actions.updateColModel(colModel);
+                       }
+               }
+       }),
+       sm: TYPO3.Workspaces.SelectionModel,
+       loadMask : true,
+       height: 630,
+       stripeRows: true,
+       plugins : [
+               TYPO3.Workspaces.RowExpander
+               ,TYPO3.Workspaces.Configuration.GridFilters
+               ,new Ext.ux.plugins.FitToParent()],
+       view : new Ext.grid.GroupingView({
+               forceFit: true,
+               groupTextTpl : '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "' + TYPO3.lang["items"] + '" : "' + TYPO3.lang["item"] + '"]})',
+               enableGroupingMenu: false,
+               enableNoGroups: false
+       }),
+       bbar : TYPO3.Workspaces.Toolbar.FullBottomBar,
+       tbar : TYPO3.Workspaces.Toolbar.FullTopToolbar
+});
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/GridFilters.js
new file mode 100644 (file)
index 0000000..07dbfe3
--- /dev/null
@@ -0,0 +1,740 @@
+/*!
+ * Ext JS Library 3.3.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+Ext.namespace('Ext.ux.grid');
+
+/**
+ * @class Ext.ux.grid.GridFilters
+ * @extends Ext.util.Observable
+ * <p>GridFilter is a plugin (<code>ptype='gridfilters'</code>) for grids that
+ * allow for a slightly more robust representation of filtering than what is
+ * provided by the default store.</p>
+ * <p>Filtering is adjusted by the user using the grid's column header menu
+ * (this menu can be disabled through configuration). Through this menu users
+ * can configure, enable, and disable filters for each column.</p>
+ * <p><b><u>Features:</u></b></p>
+ * <div class="mdetail-params"><ul>
+ * <li><b>Filtering implementations</b> :
+ * <div class="sub-desc">
+ * Default filtering for Strings, Numeric Ranges, Date Ranges, Lists (which can
+ * be backed by a Ext.data.Store), and Boolean. Additional custom filter types
+ * and menus are easily created by extending Ext.ux.grid.filter.Filter.
+ * </div></li>
+ * <li><b>Graphical indicators</b> :
+ * <div class="sub-desc">
+ * Columns that are filtered have {@link #filterCls a configurable css class}
+ * applied to the column headers.
+ * </div></li>
+ * <li><b>Paging</b> :
+ * <div class="sub-desc">
+ * If specified as a plugin to the grid's configured PagingToolbar, the current page
+ * will be reset to page 1 whenever you update the filters.
+ * </div></li>
+ * <li><b>Automatic Reconfiguration</b> :
+ * <div class="sub-desc">
+ * Filters automatically reconfigure when the grid 'reconfigure' event fires.
+ * </div></li>
+ * <li><b>Stateful</b> :
+ * Filter information will be persisted across page loads by specifying a
+ * <code>stateId</code> in the Grid configuration.
+ * <div class="sub-desc">
+ * The filter collection binds to the
+ * <code>{@link Ext.grid.GridPanel#beforestaterestore beforestaterestore}</code>
+ * and <code>{@link Ext.grid.GridPanel#beforestatesave beforestatesave}</code>
+ * events in order to be stateful.
+ * </div></li>
+ * <li><b>Grid Changes</b> :
+ * <div class="sub-desc"><ul>
+ * <li>A <code>filters</code> <i>property</i> is added to the grid pointing to
+ * this plugin.</li>
+ * <li>A <code>filterupdate</code> <i>event</i> is added to the grid and is
+ * fired upon onStateChange completion.</li>
+ * </ul></div></li>
+ * <li><b>Server side code examples</b> :
+ * <div class="sub-desc"><ul>
+ * <li><a href="http://www.vinylfox.com/extjs/grid-filter-php-backend-code.php">PHP</a> - (Thanks VinylFox)</li>
+ * <li><a href="http://extjs.com/forum/showthread.php?p=77326#post77326">Ruby on Rails</a> - (Thanks Zyclops)</li>
+ * <li><a href="http://extjs.com/forum/showthread.php?p=176596#post176596">Ruby on Rails</a> - (Thanks Rotomaul)</li>
+ * <li><a href="http://www.debatablybeta.com/posts/using-extjss-grid-filtering-with-django/">Python</a> - (Thanks Matt)</li>
+ * <li><a href="http://mcantrell.wordpress.com/2008/08/22/extjs-grids-and-grails/">Grails</a> - (Thanks Mike)</li>
+ * </ul></div></li>
+ * </ul></div>
+ * <p><b><u>Example usage:</u></b></p>
+ * <pre><code>
+var store = new Ext.data.GroupingStore({
+       ...
+});
+
+var filters = new Ext.ux.grid.GridFilters({
+       autoReload: false, //don&#39;t reload automatically
+       local: true, //only filter locally
+       // filters may be configured through the plugin,
+       // or in the column definition within the column model configuration
+       filters: [{
+               type: 'numeric',
+               dataIndex: 'id'
+       }, {
+               type: 'string',
+               dataIndex: 'name'
+       }, {
+               type: 'numeric',
+               dataIndex: 'price'
+       }, {
+               type: 'date',
+               dataIndex: 'dateAdded'
+       }, {
+               type: 'list',
+               dataIndex: 'size',
+               options: ['extra small', 'small', 'medium', 'large', 'extra large'],
+               phpMode: true
+       }, {
+               type: 'boolean',
+               dataIndex: 'visible'
+       }]
+});
+var cm = new Ext.grid.ColumnModel([{
+       ...
+}]);
+
+var grid = new Ext.grid.GridPanel({
+        ds: store,
+        cm: cm,
+        view: new Ext.grid.GroupingView(),
+        plugins: [filters],
+        height: 400,
+        width: 700,
+        bbar: new Ext.PagingToolbar({
+                store: store,
+                pageSize: 15,
+                plugins: [filters] //reset page to page 1 if filters change
+        })
+ });
+
+store.load({params: {start: 0, limit: 15}});
+
+// a filters property is added to the grid
+grid.filters
+ * </code></pre>
+ */
+Ext.ux.grid.GridFilters = Ext.extend(Ext.util.Observable, {
+       /**
+        * @cfg {Boolean} autoReload
+        * Defaults to true, reloading the datasource when a filter change happens.
+        * Set this to false to prevent the datastore from being reloaded if there
+        * are changes to the filters.  See <code>{@link updateBuffer}</code>.
+        */
+       autoReload : true,
+       /**
+        * @cfg {Boolean} encode
+        * Specify true for {@link #buildQuery} to use Ext.util.JSON.encode to
+        * encode the filter query parameter sent with a remote request.
+        * Defaults to false.
+        */
+       /**
+        * @cfg {Array} filters
+        * An Array of filters config objects. Refer to each filter type class for
+        * configuration details specific to each filter type. Filters for Strings,
+        * Numeric Ranges, Date Ranges, Lists, and Boolean are the standard filters
+        * available.
+        */
+       /**
+        * @cfg {String} filterCls
+        * The css class to be applied to column headers with active filters.
+        * Defaults to <tt>'ux-filterd-column'</tt>.
+        */
+       filterCls : 'ux-filtered-column',
+       /**
+        * @cfg {Boolean} local
+        * <tt>true</tt> to use Ext.data.Store filter functions (local filtering)
+        * instead of the default (<tt>false</tt>) server side filtering.
+        */
+       local : false,
+       /**
+        * @cfg {String} menuFilterText
+        * defaults to <tt>'Filters'</tt>.
+        */
+       menuFilterText : 'Filters',
+       /**
+        * @cfg {String} paramPrefix
+        * The url parameter prefix for the filters.
+        * Defaults to <tt>'filter'</tt>.
+        */
+       paramPrefix : 'filter',
+       /**
+        * @cfg {Boolean} showMenu
+        * Defaults to true, including a filter submenu in the default header menu.
+        */
+       showMenu : true,
+       /**
+        * @cfg {String} stateId
+        * Name of the value to be used to store state information.
+        */
+       stateId : undefined,
+       /**
+        * @cfg {Integer} updateBuffer
+        * Number of milliseconds to defer store updates since the last filter change.
+        */
+       updateBuffer : 500,
+
+       /** @private */
+       constructor : function (config) {
+               config = config || {};
+               this.deferredUpdate = new Ext.util.DelayedTask(this.reload, this);
+               this.filters = new Ext.util.MixedCollection();
+               this.filters.getKey = function (o) {
+                       return o ? o.dataIndex : null;
+               };
+               this.addFilters(config.filters);
+               delete config.filters;
+               Ext.apply(this, config);
+       },
+
+       /** @private */
+       init : function (grid) {
+               if (grid instanceof Ext.grid.GridPanel) {
+                       this.grid = grid;
+
+                       this.bindStore(this.grid.getStore(), true);
+                       // assumes no filters were passed in the constructor, so try and use ones from the colModel
+                       if(this.filters.getCount() == 0){
+                               this.addFilters(this.grid.getColumnModel());
+                       }
+
+                       this.grid.filters = this;
+
+                       this.grid.addEvents({'filterupdate': true});
+
+                       grid.on({
+                               scope: this,
+                               beforestaterestore: this.applyState,
+                               beforestatesave: this.saveState,
+                               beforedestroy: this.destroy,
+                               reconfigure: this.onReconfigure
+                       });
+
+                       if (grid.rendered){
+                               this.onRender();
+                       } else {
+                               grid.on({
+                                       scope: this,
+                                       single: true,
+                                       render: this.onRender
+                               });
+                       }
+
+               } else if (grid instanceof Ext.PagingToolbar) {
+                       this.toolbar = grid;
+               }
+       },
+
+       /**
+        * @private
+        * Handler for the grid's beforestaterestore event (fires before the state of the
+        * grid is restored).
+        * @param {Object} grid The grid object
+        * @param {Object} state The hash of state values returned from the StateProvider.
+        */
+       applyState : function (grid, state) {
+               var key, filter;
+               this.applyingState = true;
+               this.clearFilters();
+               if (state.filters) {
+                       for (key in state.filters) {
+                               filter = this.filters.get(key);
+                               if (filter) {
+                                       filter.setValue(state.filters[key]);
+                                       filter.setActive(true);
+                               }
+                       }
+               }
+               this.deferredUpdate.cancel();
+               if (this.local) {
+                       this.reload();
+               }
+               delete this.applyingState;
+               delete state.filters;
+       },
+
+       /**
+        * Saves the state of all active filters
+        * @param {Object} grid
+        * @param {Object} state
+        * @return {Boolean}
+        */
+       saveState : function (grid, state) {
+               var filters = {};
+               this.filters.each(function (filter) {
+                       if (filter.active) {
+                               filters[filter.dataIndex] = filter.getValue();
+                       }
+               });
+               return (state.filters = filters);
+       },
+
+       /**
+        * @private
+        * Handler called when the grid is rendered
+        */
+       onRender : function () {
+               this.grid.getView().on('refresh', this.onRefresh, this);
+               this.createMenu();
+       },
+
+       /**
+        * @private
+        * Handler called by the grid 'beforedestroy' event
+        */
+       destroy : function () {
+               this.removeAll();
+               this.purgeListeners();
+
+               if(this.filterMenu){
+                       Ext.menu.MenuMgr.unregister(this.filterMenu);
+                       this.filterMenu.destroy();
+                        this.filterMenu = this.menu.menu = null;
+               }
+       },
+
+       /**
+        * Remove all filters, permanently destroying them.
+        */
+       removeAll : function () {
+               if(this.filters){
+                       Ext.destroy.apply(Ext, this.filters.items);
+                       // remove all items from the collection
+                       this.filters.clear();
+               }
+       },
+
+
+       /**
+        * Changes the data store bound to this view and refreshes it.
+        * @param {Store} store The store to bind to this view
+        */
+       bindStore : function(store, initial){
+               if(!initial && this.store){
+                       if (this.local) {
+                               store.un('load', this.onLoad, this);
+                       } else {
+                               store.un('beforeload', this.onBeforeLoad, this);
+                       }
+               }
+               if(store){
+                       if (this.local) {
+                               store.on('load', this.onLoad, this);
+                       } else {
+                               store.on('beforeload', this.onBeforeLoad, this);
+                       }
+               }
+               this.store = store;
+       },
+
+       /**
+        * @private
+        * Handler called when the grid reconfigure event fires
+        */
+       onReconfigure : function () {
+               this.bindStore(this.grid.getStore());
+               this.store.clearFilter();
+               this.removeAll();
+               this.addFilters(this.grid.getColumnModel());
+               this.updateColumnHeadings();
+       },
+
+       createMenu : function () {
+               var view = this.grid.getView(),
+                       hmenu = view.hmenu;
+
+               if (this.showMenu && hmenu) {
+
+                       this.sep  = hmenu.addSeparator();
+                       this.filterMenu = new Ext.menu.Menu({
+                               id: this.grid.id + '-filters-menu'
+                       });
+                       this.menu = hmenu.add({
+                               checked: false,
+                               itemId: 'filters',
+                               text: this.menuFilterText,
+                               menu: this.filterMenu
+                       });
+
+                       this.menu.on({
+                               scope: this,
+                               checkchange: this.onCheckChange,
+                               beforecheckchange: this.onBeforeCheck
+                       });
+                       hmenu.on('beforeshow', this.onMenu, this);
+               }
+               this.updateColumnHeadings();
+       },
+
+       /**
+        * @private
+        * Get the filter menu from the filters MixedCollection based on the clicked header
+        */
+       getMenuFilter : function () {
+               var view = this.grid.getView();
+               if (!view || view.hdCtxIndex === undefined) {
+                       return null;
+               }
+               return this.filters.get(
+                       view.cm.config[view.hdCtxIndex].dataIndex
+               );
+       },
+
+       /**
+        * @private
+        * Handler called by the grid's hmenu beforeshow event
+        */
+       onMenu : function (filterMenu) {
+               var filter = this.getMenuFilter();
+
+               if (filter) {
+/*
+TODO: lazy rendering
+                       if (!filter.menu) {
+                               filter.menu = filter.createMenu();
+                       }
+*/
+                       this.menu.menu = filter.menu;
+                       this.menu.setChecked(filter.active, false);
+                       // disable the menu if filter.disabled explicitly set to true
+                       this.menu.setDisabled(filter.disabled === true);
+               }
+
+               this.menu.setVisible(filter !== undefined);
+               this.sep.setVisible(filter !== undefined);
+       },
+
+       /** @private */
+       onCheckChange : function (item, value) {
+               this.getMenuFilter().setActive(value);
+       },
+
+       /** @private */
+       onBeforeCheck : function (check, value) {
+               return !value || this.getMenuFilter().isActivatable();
+       },
+
+       /**
+        * @private
+        * Handler for all events on filters.
+        * @param {String} event Event name
+        * @param {Object} filter Standard signature of the event before the event is fired
+        */
+       onStateChange : function (event, filter) {
+               if (event === 'serialize') {
+                       return;
+               }
+
+               if (filter == this.getMenuFilter()) {
+                       this.menu.setChecked(filter.active, false);
+               }
+
+               if ((this.autoReload || this.local) && !this.applyingState) {
+                       this.deferredUpdate.delay(this.updateBuffer);
+               }
+               this.updateColumnHeadings();
+
+               if (!this.applyingState) {
+                       this.grid.saveState();
+               }
+               this.grid.fireEvent('filterupdate', this, filter);
+       },
+
+       /**
+        * @private
+        * Handler for store's beforeload event when configured for remote filtering
+        * @param {Object} store
+        * @param {Object} options
+        */
+       onBeforeLoad : function (store, options) {
+               options.params = options.params || {};
+               this.cleanParams(options.params);
+               var params = this.buildQuery(this.getFilterData());
+               Ext.apply(options.params, params);
+       },
+
+       /**
+        * @private
+        * Handler for store's load event when configured for local filtering
+        * @param {Object} store
+        * @param {Object} options
+        */
+       onLoad : function (store, options) {
+               store.filterBy(this.getRecordFilter());
+       },
+
+       /**
+        * @private
+        * Handler called when the grid's view is refreshed
+        */
+       onRefresh : function () {
+               this.updateColumnHeadings();
+       },
+
+       /**
+        * Update the styles for the header row based on the active filters
+        */
+       updateColumnHeadings : function () {
+               var view = this.grid.getView(),
+                       i, len, filter;
+               if (view.mainHd) {
+                       for (i = 0, len = view.cm.config.length; i < len; i++) {
+                               filter = this.getFilter(view.cm.config[i].dataIndex);
+                               Ext.fly(view.getHeaderCell(i))[filter && filter.active ? 'addClass' : 'removeClass'](this.filterCls);
+                       }
+               }
+       },
+
+       /** @private */
+       reload : function () {
+               if (this.local) {
+                       this.grid.store.clearFilter(true);
+                       this.grid.store.filterBy(this.getRecordFilter());
+               } else {
+                       var start,
+                               store = this.grid.store;
+                       this.deferredUpdate.cancel();
+                       if (this.toolbar) {
+                               start = store.paramNames.start;
+                               if (store.lastOptions && store.lastOptions.params && store.lastOptions.params[start]) {
+                                       store.lastOptions.params[start] = 0;
+                               }
+                       }
+                       store.reload();
+               }
+       },
+
+       /**
+        * Method factory that generates a record validator for the filters active at the time
+        * of invokation.
+        * @private
+        */
+       getRecordFilter : function () {
+               var f = [], len, i;
+               this.filters.each(function (filter) {
+                       if (filter.active) {
+                               f.push(filter);
+                       }
+               });
+
+               len = f.length;
+               return function (record) {
+                       for (i = 0; i < len; i++) {
+                               if (!f[i].validateRecord(record)) {
+                                       return false;
+                               }
+                       }
+                       return true;
+               };
+       },
+
+       /**
+        * Adds a filter to the collection and observes it for state change.
+        * @param {Object/Ext.ux.grid.filter.Filter} config A filter configuration or a filter object.
+        * @return {Ext.ux.grid.filter.Filter} The existing or newly created filter object.
+        */
+       addFilter : function (config) {
+               var Cls = this.getFilterClass(config.type),
+                       filter = config.menu ? config : (new Cls(config));
+               this.filters.add(filter);
+
+               Ext.util.Observable.capture(filter, this.onStateChange, this);
+               return filter;
+       },
+
+       /**
+        * Adds filters to the collection.
+        * @param {Array/Ext.grid.ColumnModel} filters Either an Array of
+        * filter configuration objects or an Ext.grid.ColumnModel.  The columns
+        * of a passed Ext.grid.ColumnModel will be examined for a <code>filter</code>
+        * property and, if present, will be used as the filter configuration object.
+        */
+       addFilters : function (filters) {
+               if (filters) {
+                       var i, len, filter, cm = false, dI;
+                       if (filters instanceof Ext.grid.ColumnModel) {
+                               filters = filters.config;
+                               cm = true;
+                       }
+                       for (i = 0, len = filters.length; i < len; i++) {
+                               filter = false;
+                               if (cm) {
+                                       dI = filters[i].dataIndex;
+                                       filter = filters[i].filter || filters[i].filterable;
+                                       if (filter){
+                                               filter = (filter === true) ? {} : filter;
+                                               Ext.apply(filter, {dataIndex:dI});
+                                               // filter type is specified in order of preference:
+                                               //     filter type specified in config
+                                               //     type specified in store's field's type config
+                                               filter.type = filter.type || this.store.fields.get(dI).type.type;
+                                       }
+                               } else {
+                                       filter = filters[i];
+                               }
+                               // if filter config found add filter for the column
+                               if (filter) {
+                                       this.addFilter(filter);
+                               }
+                       }
+               }
+       },
+
+       /**
+        * Returns a filter for the given dataIndex, if one exists.
+        * @param {String} dataIndex The dataIndex of the desired filter object.
+        * @return {Ext.ux.grid.filter.Filter}
+        */
+       getFilter : function (dataIndex) {
+               return this.filters.get(dataIndex);
+       },
+
+       /**
+        * Turns all filters off. This does not clear the configuration information
+        * (see {@link #removeAll}).
+        */
+       clearFilters : function () {
+               this.filters.each(function (filter) {
+                       filter.setActive(false);
+               });
+       },
+
+       /**
+        * Returns an Array of the currently active filters.
+        * @return {Array} filters Array of the currently active filters.
+        */
+       getFilterData : function () {
+               var filters = [], i, len;
+
+               this.filters.each(function (f) {
+                       if (f.active) {
+                               var d = [].concat(f.serialize());
+                               for (i = 0, len = d.length; i < len; i++) {
+                                       filters.push({
+                                               field: f.dataIndex,
+                                               data: d[i]
+                                       });
+                               }
+                       }
+               });
+               return filters;
+       },
+
+       /**
+        * Function to take the active filters data and build it into a query.
+        * The format of the query depends on the <code>{@link #encode}</code>
+        * configuration:
+        * <div class="mdetail-params"><ul>
+        *
+        * <li><b><tt>false</tt></b> : <i>Default</i>
+        * <div class="sub-desc">
+        * Flatten into query string of the form (assuming <code>{@link #paramPrefix}='filters'</code>:
+        * <pre><code>
+filters[0][field]="someDataIndex"&
+filters[0][data][comparison]="someValue1"&
+filters[0][data][type]="someValue2"&
+filters[0][data][value]="someValue3"&
+        * </code></pre>
+        * </div></li>
+        * <li><b><tt>true</tt></b> :
+        * <div class="sub-desc">
+        * JSON encode the filter data
+        * <pre><code>
+filters[0][field]="someDataIndex"&
+filters[0][data][comparison]="someValue1"&
+filters[0][data][type]="someValue2"&
+filters[0][data][value]="someValue3"&
+        * </code></pre>
+        * </div></li>
+        * </ul></div>
+        * Override this method to customize the format of the filter query for remote requests.
+        * @param {Array} filters A collection of objects representing active filters and their configuration.
+        *    Each element will take the form of {field: dataIndex, data: filterConf}. dataIndex is not assured
+        *    to be unique as any one filter may be a composite of more basic filters for the same dataIndex.
+        * @return {Object} Query keys and values
+        */
+       buildQuery : function (filters) {
+               var p = {}, i, f, root, dataPrefix, key, tmp,
+                       len = filters.length;
+
+               if (!this.encode){
+                       for (i = 0; i < len; i++) {
+                               f = filters[i];
+                               root = [this.paramPrefix, '[', i, ']'].join('');
+                               p[root + '[field]'] = f.field;
+
+                               dataPrefix = root + '[data]';
+                               for (key in f.data) {
+                                       p[[dataPrefix, '[', key, ']'].join('')] = f.data[key];
+                               }
+                       }
+               } else {
+                       tmp = [];
+                       for (i = 0; i < len; i++) {
+                               f = filters[i];
+                               tmp.push(Ext.apply(
+                                       {},
+                                       {field: f.field},
+                                       f.data
+                               ));
+                       }
+                       // only build if there is active filter
+                       if (tmp.length > 0){
+                               p[this.paramPrefix] = Ext.util.JSON.encode(tmp);
+                       }
+               }
+               return p;
+       },
+
+       /**
+        * Removes filter related query parameters from the provided object.
+        * @param {Object} p Query parameters that may contain filter related fields.
+        */
+       cleanParams : function (p) {
+               // if encoding just delete the property
+               if (this.encode) {
+                       delete p[this.paramPrefix];
+               // otherwise scrub the object of filter data
+               } else {
+                       var regex, key;
+                       regex = new RegExp('^' + this.paramPrefix + '\[[0-9]+\]');
+                       for (key in p) {
+                               if (regex.test(key)) {
+                                       delete p[key];
+                               }
+                       }
+               }
+       },
+
+       /**
+        * Function for locating filter classes, overwrite this with your favorite
+        * loader to provide dynamic filter loading.
+        * @param {String} type The type of filter to load ('Filter' is automatically
+        * appended to the passed type; eg, 'string' becomes 'StringFilter').
+        * @return {Class} The Ext.ux.grid.filter.Class
+        */
+       getFilterClass : function (type) {
+               // map the supported Ext.data.Field type values into a supported filter
+               switch(type) {
+                       case 'auto':
+                         type = 'string';
+                         break;
+                       case 'int':
+                       case 'float':
+                         type = 'numeric';
+                         break;
+                       case 'bool':
+                         type = 'boolean';
+                         break;
+               }
+               return Ext.ux.grid.filter[type.substr(0, 1).toUpperCase() + type.substr(1) + 'Filter'];
+       }
+});
+
+// register ptype
+Ext.preg('gridfilters', Ext.ux.grid.GridFilters);
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/GridFilters.css
new file mode 100644 (file)
index 0000000..83eca21
--- /dev/null
@@ -0,0 +1,54 @@
+/*!
+ * Ext JS Library 3.3.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+/**
+ * GridFilters Styles
+ **/
+/*
+.x-grid3-hd-row .ux-filtered-column {
+       border-left:  1px solid #C7E3B4;
+       border-right: 1px solid #C7E3B4;
+}
+
+.x-grid3-hd-row .ux-filtered-column .x-grid3-hd-inner {
+       background-image: url(../images/header_bg.gif);
+}
+
+.ux-filtered-column .x-grid3-hd-btn {
+       background-image: url(../images/hd-btn.gif);
+}
+*/
+.x-grid3-hd-row td.ux-filtered-column {   
+       font-style: italic;  
+       font-weight: bold;
+}              
+
+.ux-filtered-column.sort-asc .x-grid3-sort-icon {
+       background-image: url(../images/sort_filtered_asc.gif) !important;
+}
+
+.ux-filtered-column.sort-desc .x-grid3-sort-icon {
+       background-image: url(../images/sort_filtered_desc.gif) !important;
+}
+
+.ux-gridfilter-text-icon {
+       background-image: url(../images/find.png) !important;
+}
+
+/* Temporary Patch for Bug ??? */
+.x-menu-list-item-indent .x-menu-item-icon {
+       position: relative;
+       top: 3px;
+       left: 3px;
+       margin-right: 10px;
+}
+li.x-menu-list-item-indent {
+       padding-left:0px;
+}
+li.x-menu-list-item div {
+       display: block;
+}
+
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/css/RangeMenu.css
new file mode 100644 (file)
index 0000000..9495126
--- /dev/null
@@ -0,0 +1,20 @@
+/*!
+ * Ext JS Library 3.3.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+/**
+ * RangeMenu Styles
+ **/
+.ux-rangemenu-gt {
+       background-image: url(../images/greater_than.png) !important;
+}
+
+.ux-rangemenu-lt {
+       background-image: url(../images/less_than.png) !important;
+}
+
+.ux-rangemenu-eq {
+       background-image: url(../images/equals.png) !important;
+}
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/BooleanFilter.js
new file mode 100644 (file)
index 0000000..3d9af5b
--- /dev/null
@@ -0,0 +1,103 @@
+/*!
+ * Ext JS Library 3.3.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+/** 
+ * @class Ext.ux.grid.filter.BooleanFilter
+ * @extends Ext.ux.grid.filter.Filter
+ * Boolean filters use unique radio group IDs (so you can have more than one!)
+ * <p><b><u>Example Usage:</u></b></p>
+ * <pre><code>    
+var filters = new Ext.ux.grid.GridFilters({
+       ...
+       filters: [{
+               // required configs
+               type: 'boolean',
+               dataIndex: 'visible'
+
+               // optional configs
+               defaultValue: null, // leave unselected (false selected by default)
+               yesText: 'Yes',     // default
+               noText: 'No'        // default
+       }]
+});
+ * </code></pre>
+ */
+Ext.ux.grid.filter.BooleanFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
+       /**
+        * @cfg {Boolean} defaultValue
+        * Set this to null if you do not want either option to be checked by default. Defaults to false.
+        */
+       defaultValue : false,
+       /**
+        * @cfg {String} yesText
+        * Defaults to 'Yes'.
+        */
+       yesText : 'Yes',
+       /**
+        * @cfg {String} noText
+        * Defaults to 'No'.
+        */
+       noText : 'No',
+
+       /**
+        * @private
+        * Template method that is to initialize the filter and install required menu items.
+        */
+       init : function (config) {
+               var gId = Ext.id();
+               this.options = [
+                       new Ext.menu.CheckItem({text: this.yesText, group: gId, checked: this.defaultValue === true}),
+                       new Ext.menu.CheckItem({text: this.noText, group: gId, checked: this.defaultValue === false})];
+
+               this.menu.add(this.options[0], this.options[1]);
+
+               for(var i=0; i<this.options.length; i++){
+                       this.options[i].on('click', this.fireUpdate, this);
+                       this.options[i].on('checkchange', this.fireUpdate, this);
+               }
+       },
+
+       /**
+        * @private
+        * Template method that is to get and return the value of the filter.
+        * @return {String} The value of this filter
+        */
+       getValue : function () {
+               return this.options[0].checked;
+       },
+
+       /**
+        * @private
+        * Template method that is to set the value of the filter.
+        * @param {Object} value The value to set the filter
+        */
+       setValue : function (value) {
+               this.options[value ? 0 : 1].setChecked(true);
+       },
+
+       /**
+        * @private
+        * Template method that is to get and return serialized filter data for
+        * transmission to the server.
+        * @return {Object/Array} An object or collection of objects containing
+        * key value pairs representing the current configuration of the filter.
+        */
+       getSerialArgs : function () {
+               var args = {type: 'boolean', value: this.getValue()};
+               return args;
+       },
+
+       /**
+        * Template method that is to validate the provided Ext.data.Record
+        * against the filters configuration.
+        * @param {Ext.data.Record} record The record to validate
+        * @return {Boolean} true if the record is valid within the bounds
+        * of the filter, false otherwise.
+        */
+       validateRecord : function (record) {
+               return record.get(this.dataIndex) == this.getValue();
+       }
+});
\ No newline at end of file
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/gridfilters/filter/DateFilter.js
new file mode 100644 (file)
index 0000000..af8e3f1
--- /dev/null
@@ -0,0 +1,313 @@
+/*!
+ * Ext JS Library 3.3.0
+ * Copyright(c) 2006-2010 Ext JS, Inc.
+ * licensing@extjs.com
+ * http://www.extjs.com/license
+ */
+/** 
+ * @class Ext.ux.grid.filter.DateFilter
+ * @extends Ext.ux.grid.filter.Filter
+ * Filter by a configurable Ext.menu.DateMenu
+ * <p><b><u>Example Usage:</u></b></p>
+ * <pre><code>    
+var filters = new Ext.ux.grid.GridFilters({
+       ...
+       filters: [{
+               // required configs
+               type: 'date',
+               dataIndex: 'dateAdded',
+
+               // optional configs
+               dateFormat: 'm/d/Y',  // default
+               beforeText: 'Before', // default
+               afterText: 'After',   // default
+               onText: 'On',         // default
+               pickerOpts: {
+                       // any DateMenu configs
+               },
+
+               active: true // default is false
+       }]
+});
+ * </code></pre>
+ */
+Ext.ux.grid.filter.DateFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
+       /**
+        * @cfg {String} afterText
+        * Defaults to 'After'.
+        */
+       afterText : 'After',
+       /**
+        * @cfg {String} beforeText
+        * Defaults to 'Before'.
+        */
+       beforeText : 'Before',
+       /**
+        * @cfg {Object} compareMap
+        * Map for assigning the comparison values used in serialization.
+        */
+       compareMap : {
+               before: 'lt',
+               after:  'gt',
+               on:     'eq'
+       },
+       /**
+        * @cfg {String} dateFormat
+        * The date format to return when using getValue.
+        * Defaults to 'm/d/Y'.
+        */
+       dateFormat : 'm/d/Y',
+
+       /**
+        * @cfg {Date} maxDate
+        * Allowable date as passed to the Ext.DatePicker
+        * Defaults to undefined.
+        */
+       /**
+        * @cfg {Date} minDate
+        * Allowable date as passed to the Ext.DatePicker
+        * Defaults to undefined.
+        */
+       /**
+        * @cfg {Array} menuItems
+        * The items to be shown in this menu
+        * Defaults to:<pre>
+        * menuItems : ['before', 'after', '-', 'on'],
+        * </pre>
+        */
+       menuItems : ['before', 'after', '-', 'on'],
+
+       /**
+        * @cfg {Object} menuItemCfgs
+        * Default configuration options for each menu item
+        */
+       menuItemCfgs : {
+               selectOnFocus: true,
+               width: 125
+       },
+
+       /**
+        * @cfg {String} onText
+        * Defaults to 'On'.
+        */
+       onText : 'On',
+
+       /**
+        * @cfg {Object} pickerOpts
+        * Configuration options for the date picker associated with each field.
+        */
+       pickerOpts : {},
+
+       /**
+        * @private
+        * Template method that is to initialize the filter and install required menu items.
+        */
+       init : function (config) {
+               var menuCfg, i, len, item, cfg, Cls;
+
+               menuCfg = Ext.apply(this.pickerOpts, {
+                       minDate: this.minDate,
+                       maxDate: this.maxDate,
+                       format:  this.dateFormat,
+                       listeners: {
+                               scope: this,
+                               select: this.onMenuSelect
+                       }
+               });
+
+               this.fields = {};
+               for (i = 0, len = this.menuItems.length; i < len; i++) {
+                       item = this.menuItems[i];
+                       if (item !== '-') {
+                               cfg = {
+                                       itemId: 'range-' + item,
+                                       text: this[item + 'Text'],
+                                       menu: new Ext.menu.DateMenu(
+                                               Ext.apply(menuCfg, {
+                                                       itemId: item
+                                               })
+                                       ),
+                                       listeners: {
+                                               scope: this,
+                                               checkchange: this.onCheckChange
+                                       }
+                               };
+                               Cls = Ext.menu.CheckItem;
+                               item = this.fields[item] = new Cls(cfg);
+                       }
+                       //this.add(item);
+                       this.menu.add(item);
+               }
+       },
+
+       onCheckChange : function () {
+               this.setActive(this.isActivatable());
+               this.fireEvent('update', this);
+       },
+
+       /**
+        * @private
+        * Handler method called when there is a keyup event on an input
+        * item of this menu.
+        */
+       onInputKeyUp : function (field, e) {
+               var k = e.getKey();
+               if (k == e.RETURN && field.isValid()) {
+                       e.stopEvent();
+                       this.menu.hide(true);
+                       return;
+               }
+       },
+
+       /**
+        * Handler for when the menu for a field fires the 'select' event
+        * @param {Object} date
+        * @param {Object} menuItem
+        * @param {Object} value
+        * @param {Object} picker
+        */
+       onMenuSelect : function (menuItem, value, picker) {
+               var fields = this.fields,
+                       field = this.fields[menuItem.itemId];
+
+               field.setChecked(true);
+
+               if (field == fields.on) {
+                       fields.before.setChecked(false, true);
+                       fields.after.setChecked(false, true);
+               } else {
+                       fields.on.setChecked(false, true);
+                       if (field == fields.after && fields.before.menu.picker.value < value) {
+                               fields.before.setChecked(false, true);
+                       } else if (field == fields.before && fields.after.menu.picker.value > value) {
+                               fields.after.setChecked(false, true);
+                       }
+               }
+               this.fireEvent('update', this);
+       },
+
+       /**
+        * @private
+        * Template method that is to get and return the value of the filter.
+        * @return {String} The value of this filter
+        */
+       getValue : function () {
+               var key, result = {};
+               for (key in this.fields) {
+                       if (this.fields[key].checked) {
+                               result[key] = this.fields[key].menu.picker.getValue();
+                       }
+               }
+               return result;
+       },
+
+       /**
+        * @private
+        * Template method that is to set the value of the filter.
+        * @param {Object} value The value to set the filter
+        * @param {Boolean} preserve true to preserve the checked status
+        * of the other fields.  Defaults to false, unchecking the
+        * other fields
+        */
+       setValue : function (value, preserve) {
+               var key;
+               for (key in this.fields) {
+                       if(value[key]){
+                               this.fields[key].menu.picker.setValue(value[key]);
+                               this.fields[key].setChecked(true);
+                       } else if (!preserve) {
+                               this.fields[key].setChecked(false);
+                       }
+               }
+               this.fireEvent('update', this);
+       },
+
+       /**
+        * @private
+        * Template method that is to return <tt>true</tt> if the filter
+        * has enough configuration information to be activated.
+        * @return {Boolean}
+        */
+       isActivatable : function () {
+               var key;
+               for (key in this.fields) {
+                       if (this.fields[key].checked) {
+                               return true;
+                       }
+               }
+               return false;
+       },
+
+       /**
+        * @private
+        * Template method that is to get and return serialized filter data for
+        * transmission to the server.
+        * @return {Object/Array} An object or collection of objects containing
+        * key value pairs representing the current configuration of the filter.
+        */
+       getSerialArgs : function () {
+               var args = [];
+               for (var key in this.fields) {
+                       if(this.fields[key].checked){
+                               args.push({
+                                       type: 'date',
+                                       comparison: this.compareMap[key],
+                                       value: this.getFieldValue(key).format(this.dateFormat)
+                               });
+                       }
+               }
+               return args;
+       },
+
+       /**
+        * Get and return the date menu picker value
+        * @param {String} item The field identifier ('before', 'after', 'on')
+        * @return {Date} Gets the current selected value of the date field
+        */
+       getFieldValue : function(item){
+               return this.fields[item].menu.picker.getValue();
+       },
+
+       /**
+        * Gets the menu picker associated with the passed field
+        * @param {String} item The field identifier ('before', 'after', 'on')
+        * @return {Object} The menu picker
+        */
+       getPicker : function(item){
+               return this.fields[item].menu.picker;
+       },
+
+       /**
+        * Template method that is to validate the provided Ext.data.Record
+        * against the filters configuration.
+        * @param {Ext.data.Record} record The record to validate
+        * @return {Boolean} true if the record is valid within the bounds
+        * of the filter, false otherwise.
+        */
+       validateRecord : function (record) {
+               var key,
+                       pickerValue,
+                       val = record.get(this.dataIndex);
+
+               if(!Ext.isDate(val)){
+                       return false;
+               }
+               val = val.clearTime(true).getTime();
+
+               for (key in this.fields) {
+                       if (this.fields[key].checked) {
+                               pickerValue = this.getFieldValue(key).clearTime(true).getTime();
+                               if (key == 'before' && pickerValue <= val) {
+                                       return false;
+                               }
+                               if (key == 'after' && pickerValue >= val) {
+                                       return false;
+                               }
+                               if (key == 'on' &&