--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2007-2010 Ingo Renner <ingo@typo3.org>
+* (c) 2010 Steffen Ritter
+* 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!
+***************************************************************/
+
+ // TODO remove the include once the autoloader is in place
+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 TYPO3
+ * @subpackage core
+ */
+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']);
+}
+
+?>
--- /dev/null
+<?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!
+ ***************************************************************/
+
+require_once($GLOBALS['BACK_PATH'] . 'template.php');
+
+class Tx_Workspaces_Controller_AbstractController extends Tx_Extbase_MVC_Controller_ActionController {
+
+ /**
+ * @var t3lib_PageRenderer
+ */
+ var $pageRenderer;
+
+ public function initializeAction() {
+ $id = t3lib_div::_GP('id');
+ $this->pageRenderer->addInlineSetting('Workspaces', 'id', $id);
+ $this->pageRenderer->addInlineSetting('Workspaces', 'depth', $id == 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
+ * @return void
+ * @throws Tx_Extbase_MVC_Exception_UnsupportedRequestType if the controller doesn't support the current request type
+ * @api
+ */
+ 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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Tolleiv Nietsch (nietsch@aoemedia.de)
+ * 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_PreviewController extends Tx_Workspaces_Controller_AbstractController {
+
+ /**
+ *
+ * @param bool $skipObjectInit
+ * @return void
+ */
+ public function __construct($skipObjectInit = false) {
+
+ // removed this because the injection breaks for some reason when viewOnClick hook inits this object
+ if (!$skipObjectInit) {
+ $this->initializeObjects();
+ }
+ list(, $this->extensionName) = explode('_', get_class($this));
+ }
+
+ /**
+ * 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() {
+ $pageId = intval(t3lib_div::_GP('id'));
+ $language = intval(t3lib_div::_GP('L'));
+
+ $ctrl = 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(), $ctrl, '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=' . $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);
+ }
+
+ public function helpAction() {
+
+ }
+
+ /**
+ * Triggered before real action takes place
+ *
+ * @return void
+ */
+ public 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());
+ }
+
+
+ /**
+ * 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
--- /dev/null
+<?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);
+ }
+
+
+ /**
+ * Preparation before the actual actions are fired, loads resources
+ * and initialized global objects like the pageRenderer
+ *
+ * @return void
+ */
+ public 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
--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Tolleiv Nietsch <info@tolleiv.de>
+* 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!
+***************************************************************/
+
+abstract class tx_Workspaces_ExtDirect_AbstractHandler {
+
+ /**
+ * @return int workspaceId
+ */
+ protected function getCurrentWorkspace() {
+ if ($GLOBALS['BE_USER']->isAdmin()) {
+ $wsId = $GLOBALS['BE_USER']->getSessionData('tx_workspace_activeWorkspace');
+ } else {
+ $wsId = $GLOBALS['BE_USER']->workspace;
+ }
+ return $wsId;
+ }
+}
+
+
+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
--- /dev/null
+<?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_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_AbstractHandler {
+ /**
+ * @var Tx_Workspaces_Service_Stages
+ */
+ protected $stageService;
+
+ public function __construct() {
+ $this->stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+ }
+
+ /**
+ * @param integer $uid
+ * @return array
+ */
+ 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);
+ }
+
+ /**
+ * @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();
+ }
+
+ /**
+ * @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 deleteSingleRecord($table, $uid) {
+ $cmd[$table][$uid]['version'] = array(
+ 'action' => 'clearWSID'
+ );
+
+ $tce = t3lib_div::makeInstance ('t3lib_TCEmain');
+ $tce->start(array(), $cmd);
+ $tce->process_cmdmap();
+ }
+
+ /**
+ * @param string $pid
+ * @return void
+ */
+ public function viewSingleRecord($pid) {
+ return t3lib_BEfunc::viewOnClick($pid);
+ }
+
+
+ /**
+ * @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();
+ }
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @param unknown_type $table
+ * @param unknown_type $t3ver_oid
+ * @param integer $t3ver_oid
+ * @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;
+ }
+
+ /**
+ * @param int $nextStage
+ * @return array
+ */
+ public function sendToSpecificStageWindow($nextStageId) {
+ $result = $this->getSentToStageWindow($nextStageId);
+ $result['affects'] = array(
+ 'nextStage' => $nextStageId,
+ );
+
+ return $result;
+ }
+
+ /**
+ *
+ * @param array list of recipients
+ * @param string given user string of additional recipients
+ * @return array
+ */
+ public function getRecipientList($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 = explode("\n",$additionalRecipients);
+ } else {
+ $additionalRecipients = array();
+ }
+
+ $finalRecipients = array_merge($recipients,$additionalRecipients);
+ $finalRecipients = array_unique($finalRecipients);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * @param integer $stage
+ * @return string
+ */
+ protected function getDefaultCommentOfStage($stage) {
+ $result = '';
+
+ $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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 1999-2010 Kasper Skårhøj (kasperYYYY@typo3.com)
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Class encapsulates all actions which are triggered for all elements within the current workspace
+ *
+ * @author Kasper Skårhøj (kasperYYYY@typo3.com)
+ * @coauthor Tolleiv Nietsch (info@tolleiv.de)
+ *
+ */
+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;
+ }
+
+ /**
+ *
+ * @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();
+ $result['total'] = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_total');
+ }
+ } catch (Exception $e) {
+ $result['error'] = $e->getMessage();
+ }
+ return $result;
+ }
+
+ /**
+ *
+ * @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();
+ $result['total'] = $GLOBALS['BE_USER']->getSessionData('workspaceMassAction_total');
+ }
+ } catch (Exception $e) {
+ $result['error'] = $e->getMessage();
+ }
+ return $result;
+ }
+
+ /**
+ *
+ * @param $workspace
+ * @param $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;
+ }
+
+ /**
+ *
+ * @param $workspace
+ * @param $swap
+ * @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;
+ }
+
+ /**
+ *
+ * @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
--- /dev/null
+<?php
+/***************************************************************
+* Copyright notice
+*
+* (c) 2010 Steffen Kamper (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_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
+ *
+ * @author Michael Klapper <michael.klapper@aoemedia.de>
+ * @author Sonja Scholz <ss@cabag.ch>
+ */
+ 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
+ *
+ * @author Sonja Scholz <ss@cabag.ch>
+ */
+ 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
--- /dev/null
+<?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_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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 François Suter <francois@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 François Suter <francois@typo3.org>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * This class provides a wrapper around the autopublication
+ * mechanism of workspaces, as a Scheduler task
+ *
+ * @author François Suter <francois@typo3.org>
+ * @package TYPO3
+ * @subpackage tx_version
+ *
+ * $Id: class.tx_scheduler_sleeptask.php 7905 2010-06-13 14:42:33Z ohader $
+ */
+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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Tolleiv Nietsch (info@tolleiv.de)
+ * 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 Tolleiv Nietsch (info@tolleiv.de)
+ *
+ */
+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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Marco Bresch (marco.bresch@starfinanz.de)
+ * 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_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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Sonja Scholz (ss@cabag.ch)
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ * A copy is found in the textfile GPL.txt and important notices to the license
+ * from the author is found in LICENSE.txt distributed with these scripts.
+ *
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+/**
+ * Library with Workspace related functionality
+ *
+ * @author Sonja Scholz <ss@cabag.ch>
+ */
+
+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
--- /dev/null
+<?php
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Tolleiv Nietsch (nietsch@aoemedia.de)
+ * 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 Tolleiv Nietsch (nietsch@aoemedia.de)
+ *
+ */
+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
--- /dev/null
+<?php
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Tolleiv Nietsch (nietsch@aoemedia.de)
+ * 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 Tolleiv Nietsch <info@tolleiv.de>
+ *
+ */
+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 [type] $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
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+ <meta type="array">
+ <type>module</type>
+ <description>Language labels for module "web_txworkspacesM1"</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
--- /dev/null
+<?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 "Inside TYPO3".</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
--- /dev/null
+<?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
--- /dev/null
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3locallang>
+ <meta type="array">
+ <type>module</type>
+ <description>Language labels for module "web_txworkspacesM1" - 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
--- /dev/null
+<!-- ###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"> </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>
--- /dev/null
+<!-- ###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>
--- /dev/null
+<!-- ###FULLDOC### begin -->
+<f:render section="main" />
+<script type="text/javascript">
+ var liveUrl = '{liveUrl}';
+ var wsUrl = '{wsUrl}';
+ var wsSettingsUrl = '{wsSettingsUrl}';
+ var wsHelpUrl = '{wsHelpUrl}';
+</script>
--- /dev/null
+<dl class="legend">
+ <dt><f:translate key="legend.label" /></dt>
+ <dd><span class="item-state-modified"><f:translate key="legend.edited" /></span> • </dd>
+ <dd><span class="item-state-moved"><f:translate key="legend.moved" /></span> • </dd>
+ <dd><span class="item-state-new"><f:translate key="legend.new" /></span> • </dd>
+ <dd><span class="item-state-hidden"><f:translate key="legend.hidden" /></span> • </dd>
+ <dd><span class="item-state-deleted"><f:translate key="legend.deleted" /></span></dd>
+</dl>
\ No newline at end of file
--- /dev/null
+<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>
--- /dev/null
+<f:layout name="nodoc" />
+
+<f:section name="main">Help contents - not yet defined</f:section>
\ No newline at end of file
--- /dev/null
+<f:layout name="popup" />
+
+<f:section name="main"></f:section>
\ No newline at end of file
--- /dev/null
+<f:layout name="module" />
+
+<f:section name="main">
+<div id="workspacegrid"></div>
+</f:section>
\ No newline at end of file
--- /dev/null
+<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
--- /dev/null
+<f:layout name="nodoc" />
+
+<f:section name="main">
+<div id="workspacegrid"></div>
+</f:section>
\ No newline at end of file
--- /dev/null
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Workspace Team <worksapceteam@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!
+ ***************************************************************/
+
+
+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
--- /dev/null
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Workspace Team <worksapceteam@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!
+ ***************************************************************/
+
+
+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}"> </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">',
+ ' ',
+ '</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">',
+ ' ',
+ '</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
+ }
+});
--- /dev/null
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Workspace Team <worksapceteam@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!
+ ***************************************************************/
+
+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 + "\"> </span> " + 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 + "\"> </span> " + 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\"> </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\"> </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 '';
+ }
+ }
+ }
+ ]
+};
+
--- /dev/null
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2010 Workspace Team <worksapceteam@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!
+ ***************************************************************/
+
+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
+});
--- /dev/null
+/*!
+ * 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'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);
--- /dev/null
+/*!
+ * 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;
+}
+
--- /dev/null
+/*!
+ * 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;
+}
--- /dev/null
+/*!
+ * 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
--- /dev/null
+/*!
+ * 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' && pickerValue != val) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+});
\ No newline at end of file
--- /dev/null
+/*!
+ * 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.filter');
+
+/**
+ * @class Ext.ux.grid.filter.Filter
+ * @extends Ext.util.Observable
+ * Abstract base class for filter implementations.
+ */
+Ext.ux.grid.filter.Filter = Ext.extend(Ext.util.Observable, {
+ /**
+ * @cfg {Boolean} active
+ * Indicates the initial status of the filter (defaults to false).
+ */
+ active : false,
+ /**
+ * True if this filter is active. Use setActive() to alter after configuration.
+ * @type Boolean
+ * @property active
+ */
+ /**
+ * @cfg {String} dataIndex
+ * The {@link Ext.data.Store} dataIndex of the field this filter represents.
+ * The dataIndex does not actually have to exist in the store.
+ */
+ dataIndex : null,
+ /**
+ * The filter configuration menu that will be installed into the filter submenu of a column menu.
+ * @type Ext.menu.Menu
+ * @property
+ */
+ menu : null,
+ /**
+ * @cfg {Number} updateBuffer
+ * Number of milliseconds to wait after user interaction to fire an update. Only supported
+ * by filters: 'list', 'numeric', and 'string'. Defaults to 500.
+ */
+ updateBuffer : 500,
+
+ constructor : function (config) {
+ Ext.apply(this, config);
+
+ this.addEvents(
+ /**
+ * @event activate
+ * Fires when an inactive filter becomes active
+ * @param {Ext.ux.grid.filter.Filter} this
+ */
+ 'activate',
+ /**
+ * @event deactivate
+ * Fires when an active filter becomes inactive
+ * @param {Ext.ux.grid.filter.Filter} this
+ */
+ 'deactivate',
+ /**
+ * @event serialize
+ * Fires after the serialization process. Use this to attach additional parameters to serialization
+ * data before it is encoded and sent to the server.
+ * @param {Array/Object} data A map or collection of maps representing the current filter configuration.
+ * @param {Ext.ux.grid.filter.Filter} filter The filter being serialized.
+ */
+ 'serialize',
+ /**
+ * @event update
+ * Fires when a filter configuration has changed
+ * @param {Ext.ux.grid.filter.Filter} this The filter object.
+ */
+ 'update'
+ );
+ Ext.ux.grid.filter.Filter.superclass.constructor.call(this);
+
+ this.menu = new Ext.menu.Menu();
+ this.init(config);
+ if(config && config.value){
+ this.setValue(config.value);
+ this.setActive(config.active !== false, true);
+ delete config.value;
+ }
+ },
+
+ /**
+ * Destroys this filter by purging any event listeners, and removing any menus.
+ */
+ destroy : function(){
+ if (this.menu){
+ this.menu.destroy();
+ }
+ this.purgeListeners();
+ },
+
+ /**
+ * Template method to be implemented by all subclasses that is to
+ * initialize the filter and install required menu items.
+ * Defaults to Ext.emptyFn.
+ */
+ init : Ext.emptyFn,
+
+ /**
+ * Template method to be implemented by all subclasses that is to
+ * get and return the value of the filter.
+ * Defaults to Ext.emptyFn.
+ * @return {Object} The 'serialized' form of this filter
+ * @methodOf Ext.ux.grid.filter.Filter
+ */
+ getValue : Ext.emptyFn,
+
+ /**
+ * Template method to be implemented by all subclasses that is to
+ * set the value of the filter and fire the 'update' event.
+ * Defaults to Ext.emptyFn.
+ * @param {Object} data The value to set the filter
+ * @methodOf Ext.ux.grid.filter.Filter
+ */
+ setValue : Ext.emptyFn,
+
+ /**
+ * Template method to be implemented by all subclasses that is to
+ * return <tt>true</tt> if the filter has enough configuration information to be activated.
+ * Defaults to <tt>return true</tt>.
+ * @return {Boolean}
+ */
+ isActivatable : function(){
+ return true;
+ },
+
+ /**
+ * Template method to be implemented by all subclasses that is to
+ * get and return serialized filter data for transmission to the server.
+ * Defaults to Ext.emptyFn.
+ */
+ getSerialArgs : Ext.emptyFn,
+
+ /**
+ * Template method to be implemented by all subclasses that is to
+ * validates the provided Ext.data.Record against the filters configuration.
+ * Defaults to <tt>return true</tt>.
+ * @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(){
+ return true;
+ },
+
+ /**
+ * Returns the serialized filter data for transmission to the server
+ * and fires the 'serialize' event.
+ * @return {Object/Array} An object or collection of objects containing
+ * key value pairs representing the current configuration of the filter.
+ * @methodOf Ext.ux.grid.filter.Filter
+ */
+ serialize : function(){
+ var args = this.getSerialArgs();
+ this.fireEvent('serialize', args, this);
+ return args;
+ },
+
+ /** @private */
+ fireUpdate : function(){
+ if (this.active) {
+ this.fireEvent('update', this);
+ }
+ this.setActive(this.isActivatable());
+ },
+
+ /**
+ * Sets the status of the filter and fires the appropriate events.
+ * @param {Boolean} active The new filter state.
+ * @param {Boolean} suppressEvent True to prevent events from being fired.
+ * @methodOf Ext.ux.grid.filter.Filter
+ */
+ setActive : function(active, suppressEvent){
+ if(this.active != active){
+ this.active = active;
+ if (suppressEvent !== true) {
+ this.fireEvent(active ? 'activate' : 'deactivate', this);
+ }
+ }
+ }
+});
\ No newline at end of file
--- /dev/null
+/*!
+ * 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.ListFilter
+ * @extends Ext.ux.grid.filter.Filter
+ * <p>List filters are able to be preloaded/backed by an Ext.data.Store to load
+ * their options the first time they are shown. ListFilter utilizes the
+ * {@link Ext.ux.menu.ListMenu} component.</p>
+ * <p>Although not shown here, this class accepts all configuration options
+ * for {@link Ext.ux.menu.ListMenu}.</p>
+ *
+ * <p><b><u>Example Usage:</u></b></p>
+ * <pre><code>
+var filters = new Ext.ux.grid.GridFilters({
+ ...
+ filters: [{
+ type: 'list',
+ dataIndex: 'size',
+ phpMode: true,
+ // options will be used as data to implicitly creates an ArrayStore
+ options: ['extra small', 'small', 'medium', 'large', 'extra large']
+ }]
+});
+ * </code></pre>
+ *
+ */
+Ext.ux.grid.filter.ListFilter = Ext.extend(Ext.ux.grid.filter.Filter, {
+
+ /**
+ * @cfg {Array} options
+ * <p><code>data</code> to be used to implicitly create a data store
+ * to back this list when the data source is <b>local</b>. If the
+ * data for the list is remote, use the <code>{@link #store}</code>
+ * config instead.</p>
+ * <br><p>Each item within the provided array may be in one of the
+ * following formats:</p>
+ * <div class="mdetail-params"><ul>
+ * <li><b>Array</b> :
+ * <pre><code>
+options: [
+ [11, 'extra small'],
+ [18, 'small'],
+ [22, 'medium'],
+ [35, 'large'],
+ [44, 'extra large']
+]
+ * </code></pre>
+ * </li>
+ * <li><b>Object</b> :
+ * <pre><code>
+labelField: 'name', // override default of 'text'
+options: [
+ {id: 11, name:'extra small'},
+ {id: 18, name:'small'},
+ {id: 22, name:'medium'},
+ {id: 35, name:'large'},
+ {id: 44, name:'extra large'}
+]
+ * </code></pre>
+ * </li>
+ * <li><b>String</b> :
+ * <pre><code>
+ * options: ['extra small', 'small', 'medium', 'large', 'extra large']
+ * </code></pre>
+ * </li>
+ */
+ /**
+ * @cfg {Boolean} phpMode
+ * <p>Adjust the format of this filter. Defaults to false.</p>
+ * <br><p>When GridFilters <code>@cfg encode = false</code> (default):</p>
+ * <pre><code>
+// phpMode == false (default):
+filter[0][data][type] list
+filter[0][data][value] value1
+filter[0][data][value] value2
+filter[0][field] prod
+
+// phpMode == true:
+filter[0][data][type] list
+filter[0][data][value] value1, value2
+filter[0][field] prod
+ * </code></pre>
+ * When GridFilters <code>@cfg encode = true</code>:
+ * <pre><code>
+// phpMode == false (default):
+filter : [{"type":"list","value":["small","medium"],"field":"size"}]
+
+// phpMode == true:
+filter : [{"type":"list","value":"small,medium","field":"size"}]
+ * </code></pre>
+ */
+ phpMode : false,
+ /**
+ * @cfg {Ext.data.Store} store
+ * The {@link Ext.data.Store} this list should use as its data source
+ * when the data source is <b>remote</b>. If the data for the list
+ * is local, use the <code>{@link #options}</code> config instead.
+ */
+
+ /**
+ * @private
+ * Template method that is to initialize the filter and install required menu items.
+ * @param {Object} config
+ */
+ init : function (config) {
+ this.dt = new Ext.util.DelayedTask(this.fireUpdate, this);
+
+ // if a menu already existed, do clean up first
+ if (this.menu){
+ this.menu.destroy();
+ }
+ this.menu = new Ext.ux.menu.ListMenu(config);
+ this.menu.on('checkchange', this.onCheckChange, 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.menu.getSelected();
+ },
+ /**
+ * @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.menu.setSelected(value);
+ 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 () {
+ return this.getValue().length > 0;
+ },
+
+ /**
+ * @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: 'list', value: this.phpMode ? this.getValue().join(',') : this.getValue()};
+ return args;
+ },
+
+ /** @private */
+ onCheckChange : function(){
+ this.dt.delay(this.updateBuffer);
+ },
+
+
+ /**
+ * Template method that is to validate the provided Ext.data.Record
+ * against the filters configuration.
+ * @param {Ext.data.Record}