[FEATURE] Introduce stage change buttons in frontent preview
authorMichael Klapper <development@morphodo.com>
Thu, 26 May 2011 11:01:51 +0000 (13:01 +0200)
committerMichael Klapper <development@morphodo.com>
Sun, 24 Jul 2011 00:33:03 +0000 (02:33 +0200)
Enables easy reviews on a per-page level for a quicker
review process. Depending on the state of the elements on a
page up to three buttons (next,previous,discard) are presented
to the user.

Change-Id: If72c21dac25cd70eda9fba375a8e7e2f062b09bc
Resolves: #27139
Releases: 4.6

16 files changed:
typo3/sysext/workspaces/Classes/Controller/PreviewController.php
typo3/sysext/workspaces/Classes/Controller/ReviewController.php
typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php
typo3/sysext/workspaces/Classes/Service/Stages.php
typo3/sysext/workspaces/Classes/Service/Workspaces.php
typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf
typo3/sysext/workspaces/Resources/Public/Images/button_approve.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/button_discard.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/Images/button_reject.png [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/Ext.ux.plugins.TabStripContainer.js
typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js [new file with mode: 0644]
typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js
typo3/sysext/workspaces/Resources/Public/JavaScript/component.js
typo3/sysext/workspaces/Resources/Public/JavaScript/configuration.js
typo3/sysext/workspaces/Resources/Public/JavaScript/preview.js
typo3/sysext/workspaces/Resources/Public/StyleSheet/preview.css

index 2f9d7dd..90e6d6a 100644 (file)
 class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controller_AbstractController {
 
        /**
+        * @var Tx_Workspaces_Service_Stages
+        */
+       protected $stageService;
+
+       /**
+        * @var tx_Workspaces_Service_Workspaces
+        */
+       protected $workspaceService;
+
+       /**
         * Initializes the controller before invoking an action method.
         *
         * @return void
         */
        protected function initializeAction() {
                parent::initializeAction();
-
+               $this->stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+               $this->workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
                $this->template->setExtDirectStateProvider();
 
-               $resourcePath = t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/';
-               $GLOBALS['TBE_STYLES']['extJS']['theme'] = $resourcePath . 'StyleSheet/preview.css';
+               $resourcePath = t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/StyleSheet/preview.css';
+               $GLOBALS['TBE_STYLES']['extJS']['theme'] = $resourcePath;
+
                $this->pageRenderer->loadExtJS();
                $this->pageRenderer->enableExtJSQuickTips();
 
-
                        // Load  JavaScript:
                $this->pageRenderer->addExtDirectCode(array(
                        'TYPO3.Workspaces',
@@ -64,7 +75,20 @@ class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controlle
                $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/ux/flashmessages.js');
                $this->pageRenderer->addJsFile($this->backPath . 'js/extjs/iframepanel.js');
 
-               $this->pageRenderer->addJsFile($resourcePath . 'JavaScript/Ext.ux.plugins.TabStripContainer.js');
+               $this->pageRenderer->addJsFile($this->backPath . '../t3lib/js/extjs/notifications.js');
+
+               $resourcePathJavaScript = t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/JavaScript/';
+
+               $jsFiles = array(
+                       'Ext.ux.plugins.TabStripContainer.js',
+                       'Store/mainstore.js',
+                       'helpers.js',
+                       'actions.js',
+               );
+
+               foreach ($jsFiles as $jsFile) {
+                       $this->pageRenderer->addJsFile($resourcePathJavaScript . $jsFile);
+               }
 
                        // todo this part should be done with inlineLocallanglabels
                $this->pageRenderer->addJsInlineCode('workspace-inline-code', $this->generateJavascript());
@@ -84,6 +108,11 @@ class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controlle
                // @todo Evaluate how the intval() call can be used with Extbase validators/filters
                $language = intval(t3lib_div::_GP('L'));
 
+                       // fetch the next and previous stage
+               $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), $filter = 1, $stage = -99, $this->pageId, $recursionLevel = 0, $selectionType = 'tables_modify');
+               list(, $nextStage) = $this->stageService->getNextStageForElementCollection($workspaceItemsArray);
+               list(, $previousStage) = $this->stageService->getPreviousStageForElementCollection($workspaceItemsArray);
+
                /** @var $wsService tx_Workspaces_Service_Workspaces */
                $wsService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
                $wsList = $wsService->getAvailableWorkspaces();
@@ -97,7 +126,6 @@ class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controlle
                        }
                }
 
-               $controller = t3lib_div::makeInstance('Tx_Workspaces_Controller_ReviewController', TRUE);
                /** @var $uriBuilder Tx_Extbase_MVC_Web_Routing_UriBuilder */
                $uriBuilder = $this->objectManager->create('Tx_Extbase_MVC_Web_Routing_UriBuilder');
 
@@ -131,7 +159,13 @@ class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controlle
                $this->pageRenderer->addInlineSetting('Workspaces', 'SplitPreviewModes', $splitPreviewModes);
 
                $GLOBALS['BE_USER']->setAndSaveSessionData('workspaces.backend_domain', t3lib_div::getIndpEnv('TYPO3_HOST_ONLY'));
-               $this->pageRenderer->addJsInlineCode("workspaces.preview.lll" , "TYPO3.LLL.Workspaces = {
+
+               $this->pageRenderer->addInlineSetting('Workspaces', 'disableNextStageButton', $this->isInvalidStage($nextStage));
+               $this->pageRenderer->addInlineSetting('Workspaces', 'disablePreviousStageButton', $this->isInvalidStage($previousStage));
+               $this->pageRenderer->addInlineSetting('Workspaces', 'disableDiscardStageButton', $this->isInvalidStage($nextStage) && $this->isInvalidStage($previousStage));
+
+               $this->pageRenderer->addJsInlineCode("workspaces.preview.lll", "
+               TYPO3.LLL.Workspaces = {
                        visualPreview: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.visualPreview', TRUE) . "',
                        listView: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.listView', TRUE) . "',
                        livePreview: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.livePreview', TRUE) . "',
@@ -140,7 +174,10 @@ class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controlle
                        workspacePreviewDetail: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.workspacePreviewDetail', TRUE) . "',
                        modeSlider: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.modeSlider', TRUE) . "',
                        modeVbox: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.modeVbox', TRUE) . "',
-                       modeHbox: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.modeHbox', TRUE) . "'
+                       modeHbox: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:preview.modeHbox', TRUE) . "',
+                       discard: '" . $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:label_doaction_discard', TRUE) . "',
+                       nextStage: '" . $nextStage['title'] . "',
+                       previousStage: '" . $previousStage['title'] . "'
                };\n");
 
                $resourcePath = t3lib_extMgm::extRelPath('workspaces') . 'Resources/Public/';
@@ -148,6 +185,18 @@ class Tx_Workspaces_Controller_PreviewController extends Tx_Workspaces_Controlle
        }
 
        /**
+        * Evaluate the activate state based on given $stageArray.
+        *
+        * @param array $stageArray
+        * @return boolean
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       protected function isInvalidStage($stageArray) {
+               return !(is_array($stageArray) && count($stageArray) > 0);
+       }
+
+       /**
         * @return void
         */
        public function newPageAction() {
index 45e5768..5ea3734 100644 (file)
@@ -176,6 +176,7 @@ class Tx_Workspaces_Controller_ReviewController extends Tx_Workspaces_Controller
                        'gridfilters/filter/NumericFilter.js',
                        'gridfilters/filter/BooleanFilter.js',
                        'gridfilters/filter/BooleanFilter.js',
+                       'Store/mainstore.js',
 
                        'configuration.js',
                        'helpers.js',
index 793067a..d2e3c25 100644 (file)
@@ -31,6 +31,7 @@
  * @subpackage ExtDirect
  */
 class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_AbstractHandler {
+
        /**
         * @var Tx_Workspaces_Service_Stages
         */
@@ -70,9 +71,7 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
                        'swapIntoWS' => 1
                );
 
-               $tce = t3lib_div::makeInstance ('t3lib_TCEmain');
-               $tce->start(array(), $cmd);
-               $tce->process_cmdmap();
+               $this->processTcaCmd($cmd);
        }
 
        /**
@@ -89,9 +88,7 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
                        'action' => 'clearWSID'
                );
 
-               $tce = t3lib_div::makeInstance ('t3lib_TCEmain');
-               $tce->start(array(), $cmd);
-               $tce->process_cmdmap();
+               $this->processTcaCmd($cmd);
        }
 
        /**
@@ -226,7 +223,11 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
         */
        public function getRecipientList(array $uidOfRecipients, $additionalRecipients, $stageId) {
                $finalRecipients = array();
-
+               if (!$this->getStageService()->isValid($stageId)) {
+                       throw new InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:error.stageId.integer'));
+               } else {
+                       $stageId = (int)$stageId;
+               }
                $recipients = array();
                foreach ($uidOfRecipients as $userUid) {
                        $beUserRecord = t3lib_befunc::getRecord('be_users',intval($userUid));
@@ -268,6 +269,103 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
        }
 
        /**
+        * Discard all items from given page id.
+        *
+        * @param  integer $pageId
+        * @return array
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function discardStagesFromPage($pageId) {
+               $cmdMapArray      = array();
+                       /** @var $workspaceService tx_Workspaces_Service_Workspaces */
+               $workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+                       /** @var $stageService Tx_Workspaces_Service_Stages */
+               $stageService     = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($stageService->getWorkspaceId(), $filter = 1, $stage = -99, $pageId, $recursionLevel = 0, $selectionType = 'tables_modify');
+
+               foreach ($workspaceItemsArray as $tableName => $items) {
+                       foreach ($items as $item) {
+                               $cmdMapArray[$tableName][$item['uid']]['version']['action'] = 'clearWSID';
+                       }
+               }
+
+               $this->processTcaCmd($cmdMapArray);
+
+               return array (
+                       'success' => TRUE,
+               );
+       }
+
+       /**
+        * Push the given element collection to the next workspace stage.
+        *
+        * <code>
+        * $parameters->additional = your@mail.com
+        * $parameters->affects->__TABLENAME__
+        * $parameters->comments
+        * $parameters->receipients
+        * $parameters->stageId
+        * </code>
+        *
+        * @param stdClass $parameters
+        * @return array
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function sentCollectionToStage(stdClass $parameters) {
+               $cmdMapArray = array();
+               $comment     = $parameters->comments;
+               $stageId     = $parameters->stageId;
+               $recipients  = $this->getRecipientList($parameters->receipients, $parameters->additional);
+
+               if (t3lib_div::testInt($stageId) === FALSE) {
+                       throw new InvalidArgumentException('Missing "stageId" in $parameters array.');
+               }
+
+               if (! is_array($parameters->affects) && count($parameters->affects) == 0) {
+                       throw new InvalidArgumentException('Missing "affected items" in $parameters array.');
+               }
+               foreach ($parameters->affects as $tableName => $items) {
+                       foreach ($items as $item) {
+                               if ($stageId == Tx_Workspaces_Service_Stages::STAGE_PUBLISH_EXECUTE_ID) {
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['action'] = 'swap';
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['swapWith'] = $item->uid;
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['comment'] = $comment;
+                                       $cmdMapArray[$tableName][$item->t3ver_oid]['version']['notificationAlternativeRecipients'] = $recipients;
+                               } else {
+                                       $cmdMapArray[$tableName][$item->uid]['version']['action'] = 'setStage';
+                                       $cmdMapArray[$tableName][$item->uid]['version']['stageId'] = $stageId;
+                                       $cmdMapArray[$tableName][$item->uid]['version']['comment'] = $comment;
+                                       $cmdMapArray[$tableName][$item->uid]['version']['notificationAlternativeRecipients'] = $recipients;
+                               }
+                       }
+               }
+
+               $this->processTcaCmd($cmdMapArray);
+
+               return array (
+                       'success' => TRUE,
+                               // force refresh after publishing changes
+                       'refreshLivePanel' => ($parameters->stageId == -20) ? TRUE : FALSE
+               );
+       }
+
+       /**
+        * Process TCA command map array.
+        *
+        * @param  array $cmdMapArray
+        * @return void
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       protected function processTcaCmd(array $cmdMapArray) {
+               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
+               $tce->start(array(), $cmdMapArray);
+               $tce->process_cmdmap();
+       }
+
+       /**
         * Gets an object with this structure:
         *
         *      affects: object
@@ -305,9 +403,7 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
                        $cmdArray[$table][$uid]['version']['notificationAlternativeRecipients'] = $recipients;
                }
 
-               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
-               $tce->start(array(), $cmdArray);
-               $tce->process_cmdmap();
+               $this->processTcaCmd($cmdArray);
 
                $result = array(
                        'success' => TRUE,
@@ -345,9 +441,7 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
                $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();
+               $this->processTcaCmd($cmdArray);
 
                $result = array(
                        'success' => TRUE,
@@ -399,9 +493,7 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
                        }
                }
 
-               $tce = t3lib_div::makeInstance('t3lib_TCEmain');
-               $tce->start(array(), $cmdArray);
-               $tce->process_cmdmap();
+               $this->processTcaCmd($cmdArray);
 
                $result = array(
                        'success' => TRUE,
@@ -555,6 +647,89 @@ class tx_Workspaces_ExtDirect_ActionHandler extends tx_Workspaces_ExtDirect_Abst
                }
                return $this->stageService;
        }
+
+       /**
+        * Send all available workspace records to the previous stage.
+        *
+        * @param  integer $id Current page id to process items to previous stage.
+        * @return array
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function sendPageToPreviousStage($id) {
+               $workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), $filter = 1, $stage = -99, $id, $recursionLevel = 0, $selectionType = 'tables_modify');
+               list($currentStage, $previousStage) = $this->getStageService()->getPreviousStageForElementCollection($workspaceItemsArray);
+
+                       // get only the relevant items for processing
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), $filter = 1, $currentStage['uid'], $id, $recursionLevel = 0, $selectionType = 'tables_modify');
+
+               return array (
+                       'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $previousStage['title'],
+                       'items' => $this->getSentToStageWindow($previousStage['uid']),
+                       'affects' => $workspaceItemsArray,
+                       'stageId' => $previousStage['uid'],
+               );
+       }
+
+       /**
+        *
+        * @param integer $id Current Page id to select Workspace items from.
+        *
+        * @return array
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function sendPageToNextStage($id) {
+               $workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), $filter = 1, $stage = -99, $id, $recursionLevel = 0, $selectionType = 'tables_modify');
+               list($currentStage, $nextStage) = $this->getStageService()->getNextStageForElementCollection($workspaceItemsArray);
+                       // get only the relevant items for processing
+               $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace($this->stageService->getWorkspaceId(), $filter = 1, $currentStage['uid'], $id, $recursionLevel = 0, $selectionType = 'tables_modify');
+
+               return array (
+                       'title' => 'Status message: Page send to next stage - ID: ' . $id . ' - Next stage title: ' . $nextStage['title'],
+                       'items' => $this->getSentToStageWindow($nextStage['uid']),
+                       'affects' => $workspaceItemsArray,
+                       'stageId' => $nextStage['uid'],
+               );
+       }
+
+       /**
+        * Fetch the current label and visible state of the buttons.
+        *
+        * @param integer $id
+        * @return array Contains the visibility state and label of the stage change buttons.
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function updateStageChangeButtons($id) {
+
+               $stageService = t3lib_div::makeInstance('Tx_Workspaces_Service_Stages');
+               $workspaceService = t3lib_div::makeInstance('tx_Workspaces_Service_Workspaces');
+
+                       // fetch the next and previous stage
+               $workspaceItemsArray   = $workspaceService->selectVersionsInWorkspace($stageService->getWorkspaceId(), $filter = 1, $stage = -99, $id, $recursionLevel = 0, $selectionType = 'tables_modify');
+               list(, $nextStage)     = $stageService->getNextStageForElementCollection($workspaceItemsArray);
+               list(, $previousStage) = $stageService->getPreviousStageForElementCollection($workspaceItemsArray);
+
+               $toolbarButtons = array(
+                       'feToolbarButtonNextStage' => array(
+                               'visible' => is_array($nextStage) && count($nextStage) > 0,
+                               'text' => $nextStage['title'],
+                       ),
+                       'feToolbarButtonPreviousStage' => array(
+                               'visible' => is_array($previousStage) && count($previousStage),
+                               'text' => $previousStage['title'],
+                       ),
+                       'feToolbarButtonDiscardStage' => array(
+                               'visible' => (is_array($nextStage) && count($nextStage) > 0) || (is_array($previousStage) && count($previousStage) > 0),
+                               'text' =>  $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xml:label_doaction_discard', TRUE),
+                       ),
+               );
+
+               return $toolbarButtons;
+       }
 }
 
 
index f334669..c8d069d 100644 (file)
@@ -106,6 +106,110 @@ class Tx_Workspaces_Service_Stages {
        }
 
        /**
+        * Find the highest possible "previous" stage for all $byTableName
+        *
+        * @param array $workspaceItems
+        * @param array $byTableName
+        * @return array Current and next highest possible stage
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function getPreviousStageForElementCollection($workspaceItems, array $byTableName = array('tt_content', 'pages', 'pages_language_overlay')) {
+               $currentStage = array();
+               $previousStage = array();
+               $usedStages = array();
+               $found = FALSE;
+               $availableStagesForWS = array_reverse($this->getStagesForWS());
+               $availableStagesForWSUser = $this->getStagesForWSUser();
+               $byTableName = array_flip($byTableName);
+
+               foreach ($workspaceItems as $tableName => $items) {
+                       if (!array_key_exists($tableName, $byTableName)) {
+                               continue;
+                       }
+                       foreach ($items as $item) {
+                               $usedStages[$item['t3ver_stage']] = TRUE;
+                       }
+               }
+
+               foreach ($availableStagesForWS as $stage) {
+                       if (isset($usedStages[$stage['uid']])) {
+                               $currentStage = $stage;
+                               $previousStage = $this->getPrevStage($stage['uid']);
+                               break;
+                       }
+               }
+
+               foreach ($availableStagesForWSUser as $userWS) {
+                       if ($previousStage['uid'] == $userWS['uid']) {
+                               $found = TRUE;
+                               break;
+                       }
+               }
+
+               if ($found === FALSE) {
+                       $previousStage = array();
+               }
+
+               return array (
+                       $currentStage,
+                       $previousStage
+               );
+       }
+
+       /**
+        * Retrieve the next stage based on the lowest stage given in the $workspaceItems record array.
+        *
+        * @param array $workspaceItems
+        * @param array $byTableName
+        * @return array Current and next possible stage.
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       public function getNextStageForElementCollection($workspaceItems, array $byTableName = array('tt_content', 'pages', 'pages_language_overlay')) {
+               $currentStage = array();
+               $usedStages = array();
+               $nextStage = array();
+               $availableStagesForWS = $this->getStagesForWS();
+               $availableStagesForWSUser = $this->getStagesForWSUser();
+               $byTableName = array_flip($byTableName);
+               $found = FALSE;
+
+               foreach ($workspaceItems as $tableName => $items) {
+                       if (! array_key_exists($tableName, $byTableName)) {
+                               continue;
+                       }
+                       foreach ($items as $item) {
+                               $usedStages[$item['t3ver_stage']] = TRUE;
+                       }
+               }
+
+               foreach ($availableStagesForWS as $stage) {
+                       if (isset($usedStages[$stage['uid']])) {
+                               $currentStage = $stage;
+                               $nextStage = $this->getNextStage($stage['uid']);
+                               break;
+                       }
+               }
+
+               foreach ($availableStagesForWSUser as $userWS) {
+                       if ($nextStage['uid'] == $userWS['uid']) {
+                               $found = TRUE;
+                               break;
+                       }
+               }
+
+               if ($found === FALSE) {
+                       $nextStage = array();
+               }
+
+               return array (
+                       $currentStage,
+                       $nextStage
+               );
+       }
+
+       /**
         * Building an array with all stage ids and titles related to the given workspace
         *
         * @return array id and title of the stages
index f1f1dbe..51ec1d6 100644 (file)
@@ -237,13 +237,14 @@ class tx_Workspaces_Service_Workspaces implements t3lib_Singleton {
         *
         * @param string $table
         * @param string $pageList
+        * @param integer $wsid
         * @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';
+               $fields = 'A.uid, A.t3ver_oid, A.t3ver_stage, ' . ($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
index 88167bd..8dd86d6 100644 (file)
                                <source>Discard workspace version of record.</source>
                                <target approved="yes">Discard workspace version of record.</target>
                        </trans-unit>
+                       <trans-unit id="window.discardAll.title">
+                               <source>Discard all workspace version of current page.</source>
+                               <target approved="yes">Discard all workspace version of current page.</target>
+                       </trans-unit>
                        <trans-unit id="window.discard.message">
                                <source>Do you really want to discard this version from workspace?</source>
                                <target approved="yes">Do you really want to discard this version from workspace?</target>
                        </trans-unit>
+                       <trans-unit id="window.discardAll.message">
+                               <source>Do you really want to discard all versions from page?</source>
+                               <target approved="yes">Do you really want to discard all versions from page?</target>
+                       </trans-unit>
                        <trans-unit id="window.swap.title">
                                <source>Swap version</source>
                                <target approved="yes">Swap version</target>
diff --git a/typo3/sysext/workspaces/Resources/Public/Images/button_approve.png b/typo3/sysext/workspaces/Resources/Public/Images/button_approve.png
new file mode 100644 (file)
index 0000000..8385dc9
Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/button_approve.png differ
diff --git a/typo3/sysext/workspaces/Resources/Public/Images/button_discard.png b/typo3/sysext/workspaces/Resources/Public/Images/button_discard.png
new file mode 100644 (file)
index 0000000..72e9de8
Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/button_discard.png differ
diff --git a/typo3/sysext/workspaces/Resources/Public/Images/button_reject.png b/typo3/sysext/workspaces/Resources/Public/Images/button_reject.png
new file mode 100644 (file)
index 0000000..8f2b0d4
Binary files /dev/null and b/typo3/sysext/workspaces/Resources/Public/Images/button_reject.png differ
index 05eee41..07fd958 100644 (file)
@@ -70,7 +70,6 @@ Ext.ux.plugins.TabStripContainer = Ext.extend(Object, {
                                        : this.tabPanel.header;
                height = this.headerFooterEl.getComputedHeight();
                stripTarget = tabPanel[tabPanel.stripTarget];
-
                stripTarget.applyStyles('position: relative;');
 
                panelDiv = this.headerFooterEl.createChild({
@@ -79,15 +78,12 @@ Ext.ux.plugins.TabStripContainer = Ext.extend(Object, {
                        style : {
                                position : 'absolute',
                                right: 0,
-                               top: 0
+                               top: '1px'
                        }
                });
                panelDiv.setSize(this.width, height, false);
                config = Ext.applyIf({
                        layout: 'hbox',
-                       layoutConfig: {
-                               align: 'stretchmax'
-                       },
                        height: height,
                        width: this.width,
                        renderTo: panelDiv
@@ -95,7 +91,11 @@ Ext.ux.plugins.TabStripContainer = Ext.extend(Object, {
                this.panelContainer = new Ext.Panel(config);
                this.panelContainer.add(this.items);
                this.panelContainer.doLayout();
+       },
+
+       doLayout: function () {
+               this.panelContainer.doLayout();
        }
 
 });
-Ext.preg('Ext.ux.plugins.TabStripContainer', Ext.ux.plugins.TabStripContainer);
\ No newline at end of file
+Ext.preg('Ext.ux.plugins.TabStripContainer', Ext.ux.plugins.TabStripContainer);
diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/Store/mainstore.js
new file mode 100644 (file)
index 0000000..fe39c0c
--- /dev/null
@@ -0,0 +1,64 @@
+Ext.ns('TYPO3.Workspaces.Configuration');
+
+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.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',
+       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
+       }
+});
\ No newline at end of file
index e93afb5..500825a 100644 (file)
@@ -126,8 +126,6 @@ TYPO3.Workspaces.Actions = {
                                                comments: values.comments
                                        };
 
-
-
                                        TYPO3.Workspaces.Actions.sendToStageExecute(parameters);
                                        top.TYPO3.Windows.close('sendToStageWindow');
                                        TYPO3.Workspaces.MainStore.reload();
@@ -192,7 +190,6 @@ TYPO3.Workspaces.Actions = {
                                }
                        }
                });
-
        },
        handlerResponseOnExecuteAction: function(response) {
                if (!Ext.isObject(response)) {
@@ -204,5 +201,153 @@ TYPO3.Workspaces.Actions = {
                        var code = (error.code ? ' #' + error.code : '');
                        top.TYPO3.Dialog.ErrorDialog({ title: 'Error' + code, msg: error.message });
                }
+       },
+
+       /**
+        * Process "send to next stage" action.
+        *
+        * This method is used in the split frontend preview part.
+        *
+        * @return void
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       sendPageToNextStage: function () {
+               TYPO3.Workspaces.ExtDirectActions.sendPageToNextStage(TYPO3.settings.Workspaces.id, function (response) {
+                       if (Ext.isObject(response.error)) {
+                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                       } else {
+                               var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
+                                       title: TYPO3.LLL.Workspaces.nextStage,
+                                       items: response.items.items,
+                                       executeHandler: function(event) {
+                                               var values = top.Ext.getCmp('sendToStageForm').getForm().getValues();
+                                               affects = response.affects;
+                                               var parameters = {
+                                                       affects: affects,
+                                                       receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'),
+                                                       additional: values.additional,
+                                                       comments: values.comments,
+                                                       stageId: response.stageId
+                                               };
+                                               TYPO3.Workspaces.ExtDirectActions.sentCollectionToStage(parameters, function (response) {
+                                                       TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                                                       TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons);
+
+                                                       if (response.refreshLivePanel == true) {
+                                                               Ext.getCmp('livePanel').refresh();
+                                                               Ext.getCmp('livePanel-hbox').refresh();
+                                                               Ext.getCmp('livePanel-vbox').refresh();
+                                                       }
+                                               });
+                                               top.TYPO3.Windows.close('sendToStageWindow');
+                                       }
+                               });
+                       }
+               });
+       },
+
+       /**
+        * Process "send to previous stage" action.
+        *
+        * This method is used in the split frontend preview part.
+        *
+        * @return void
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       sendPageToPrevStage: function () {
+               TYPO3.Workspaces.ExtDirectActions.sendPageToPreviousStage(TYPO3.settings.Workspaces.id, function (response) {
+                       if (Ext.isObject(response.error)) {
+                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                       } else {
+                               var dialog = TYPO3.Workspaces.Helpers.getSendToStageWindow({
+                                       title: TYPO3.LLL.Workspaces.nextStage,
+                                       items: response.items.items,
+                                       executeHandler: function(event) {
+                                               var values = top.Ext.getCmp('sendToStageForm').getForm().getValues();
+
+                                               affects = response.affects;
+                                               var parameters = {
+                                                       affects: affects,
+                                                       receipients: TYPO3.Workspaces.Helpers.getElementIdsFromFormValues(values, 'receipients'),
+                                                       additional: values.additional,
+                                                       comments: values.comments,
+                                                       stageId: response.stageId
+                                               };
+                                               TYPO3.Workspaces.ExtDirectActions.sentCollectionToStage(parameters, function (response) {
+                                                       TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                                                       TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons);
+                                               });
+                                               top.TYPO3.Windows.close('sendToStageWindow');
+                                       }
+                               });
+                       }
+               });
+       },
+
+       /**
+        * Update the visible state for the buttons "next stage", "prev stage" and "discard".
+        *
+        * This method is used in the split frontend preview part.
+        *
+        * @param object response
+        * @return void
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       updateStageChangeButtons: function (response) {
+
+               if (Ext.isObject(response.error)) {
+                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+               } else {
+                       for (componentId in response) {
+                               if (response[componentId].visible) {
+                                       if (!top.Ext.getCmp(componentId).isVisible()) {
+                                               top.Ext.getCmp(componentId).show();
+                                       }
+                                       top.Ext.getCmp(componentId).setText(response[componentId].text);
+                               } else {
+                                       if (top.Ext.getCmp(componentId).isVisible()) {
+                                               top.Ext.getCmp(componentId).hide();
+                                       }
+                               }
+                       }
+                               // force doLayout on each plugin containing the preview panel
+                       Ext.getCmp('preview').plugins.each(function (item, index) {
+                               if (Ext.isFunction(item.doLayout)) {
+                                       item.doLayout();
+                               }
+                       });
+               }
+       },
+
+       /**
+        * Process the discard all items from current page action.
+        *
+        * This method is used in the split frontend preview part.
+        *
+        * @return void
+        *
+        * @author Michael Klapper <development@morphodo.com>
+        */
+       discardPage: function () {
+               var configuration = {
+                       title: TYPO3.l10n.localize('window.discardAll.title'),
+                       msg: TYPO3.l10n.localize('window.discardAll.message'),
+                       fn: function(result) {
+                               if (result == 'yes') {
+                                       TYPO3.Workspaces.ExtDirectActions.discardStagesFromPage(TYPO3.settings.Workspaces.id, function (response) {
+                                               TYPO3.Workspaces.Actions.handlerResponseOnExecuteAction(response);
+                                               TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons);
+                                               Ext.getCmp('wsPanel').refresh();
+                                               Ext.getCmp('wsPanel-hbox').refresh();
+                                               Ext.getCmp('wsPanel-vbox').refresh();
+                                       });
+                               }
+                       }
+               };
+
+               top.TYPO3.Dialog.QuestionDialog(configuration);
        }
 };
index cfe9081..3c562b9 100644 (file)
@@ -271,36 +271,4 @@ TYPO3.Workspaces.RowExpander = new Ext.grid.RowExpander({
 });
 
 
-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',
-       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
-       }
-});
index f85453e..7082e8c 100644 (file)
@@ -53,34 +53,6 @@ TYPO3.Workspaces.Configuration.GridFilters = new Ext.ux.grid.GridFilters({
                }
        ]
 });
-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',
index 95246ba..69ac2e0 100644 (file)
@@ -34,7 +34,10 @@ Ext.onReady(function() {
        if (Ext.isObject(TYPO3.settings.Workspaces.States)) {
                Ext.state.Manager.getProvider().initState(TYPO3.settings.Workspaces.States);
        }
-
+       // late binding of ExtDirect
+       TYPO3.Workspaces.MainStore.proxy = new Ext.data.DirectProxy({
+               directFn : TYPO3.Workspaces.ExtDirect.getWorkspaceInfos
+       });
 
        var iconClsChecked = 't3-icon t3-icon-status t3-icon-status-status t3-icon-status-checked';
        var iconClsEmpty = 't3-icon t3-icon-empty t3-icon-empty-empty t3-icon-empty';
@@ -51,7 +54,7 @@ Ext.onReady(function() {
                        plugins : [{
                                ptype : 'Ext.ux.plugins.TabStripContainer',
                                id: 'controls',
-                               width: 600,
+                               width: 1000,
                                items: [
                                        {
                                                xtype: 'panel',
@@ -112,10 +115,51 @@ Ext.onReady(function() {
                                                                }
                                                        ]
                                                }]
-                                       },
-                                       {
-                                               id: 'visual-mode-toolbar',
+                                       }, {
+                                               xtype: 'buttongroup',
+                                               id: 'stageButtonGroup',
+                                               columns: 4,
+                                               width: 400,
                                                items: [{
+                                                       text: TYPO3.LLL.Workspaces.nextStage,
+                                                       xtype: 'button',
+                                                       iconCls: 'x-btn-text',
+                                                       id: 'feToolbarButtonNextStage',
+                                                       hidden: TYPO3.settings.Workspaces.disableNextStageButton,
+                                                       listeners: {
+                                                               click: {
+                                                                       fn: function () {
+                                                                               TYPO3.Workspaces.Actions.sendPageToNextStage();
+                                                                       }
+                                                               }
+                                                       }
+                                               }, {
+                                                       text: TYPO3.LLL.Workspaces.previousStage,
+                                                       xtype: 'button',
+                                                       iconCls: 'x-btn-text',
+                                                       id: 'feToolbarButtonPreviousStage',
+                                                       hidden: TYPO3.settings.Workspaces.disablePreviousStageButton,
+                                                       listeners: {
+                                                               click: {
+                                                                       fn: function () {
+                                                                               TYPO3.Workspaces.Actions.sendPageToPrevStage();
+                                                                       }
+                                                               }
+                                                       }
+                                               }, {
+                                                       text: TYPO3.LLL.Workspaces.discard,
+                                                       iconCls: 'x-btn-text',
+                                                       xtype: 'button',
+                                                       id: 'feToolbarButtonDiscardStage',
+                                                       hidden: TYPO3.settings.Workspaces.disableDiscardStageButton,
+                                                       listeners: {
+                                                               click: {
+                                                                       fn: function () {
+                                                                               TYPO3.Workspaces.Actions.discardPage();
+                                                                       }
+                                                               }
+                                                       }
+                                               }, {
                                                        xtype: 'button',
                                                        iconCls: 'x-btn-icon t3-icon t3-icon-actions t3-icon-actions-system t3-icon-system-options-view',
                                                        id: 'visual-mode-options',
@@ -169,6 +213,7 @@ Ext.onReady(function() {
                                                if (Ext.isObject(top.Ext.getCmp('slider'))) {
                                                        top.Ext.getCmp('slider').show();
                                                        top.Ext.getCmp('visual-mode-options').show();
+                                                       TYPO3.Workspaces.ExtDirectActions.updateStageChangeButtons(TYPO3.settings.Workspaces.id, TYPO3.Workspaces.Actions.updateStageChangeButtons);
                                                }
                                        }
                                },
@@ -279,6 +324,9 @@ Ext.onReady(function() {
                                        activate: function () {
                                                top.Ext.getCmp('slider').hide();
                                                top.Ext.getCmp('visual-mode-options').hide();
+                                               top.Ext.getCmp('feToolbarButtonNextStage').hide();
+                                               top.Ext.getCmp('feToolbarButtonPreviousStage').hide();
+                                               top.Ext.getCmp('feToolbarButtonDiscardStage').hide();
                                        }
                                },
                                items:  [{
index a313621..b6797f0 100644 (file)
@@ -151,7 +151,7 @@ ul.x-tab-strip-top {
 #visual-mode-options {
        display: block;
        height: 20px;
-       margin: 5px 0 0 0;
+       margin: 0px 0 0 10px;
 }
 #visual-mode-options.x-btn-menu-active {
        background-color: #f9f9f9;
@@ -278,6 +278,38 @@ ul.x-tab-strip-top {
 .x-btn {
        color: #FFF;
 }
+.t3-icon-system-options-view {
+       float: right;
+}
+#feToolbarButtonNextStage.x-btn, #feToolbarButtonPreviousStage.x-btn, #feToolbarButtonDiscardStage.x-btn {
+       background-image: url('../Images/button_approve.png');
+       background-repeat: repeat-x;
+       border: 1px solid #7c7c7c;
+       -moz-border-radius: 1px;
+       -webkit-border-radius: 1px;
+       border-radius: 1px;
+       height: 13px;
+       line-height: 8px;
+       margin-right: 10px;
+       font-size: 13px;
+       margin-top:0px;
+}
+#feToolbarButtonNextStage.x-btn .x-btn-text, #feToolbarButtonPreviousStage.x-btn .x-btn-text, #feToolbarButtonDiscardStage.x-btn .x-btn-text {
+       color: #FFF;
+       font-size: 11px;
+       line-height: 8px;
+       height: 13px;
+       padding: 0 3px 0 3px;
+}
+#feToolbarButtonPreviousStage.x-btn .x-btn-text {
+       color:#7c7c7c;
+}
+#feToolbarButtonPreviousStage.x-btn {
+       background-image: url('../Images/button_reject.png');
+}
+#feToolbarButtonDiscardStage.x-btn {
+       background-image: url('../Images/button_discard.png');
+}
 #sendToStageWindow .x-btn {
        background-color: #d5d5d5;
        background-image: url('../../../../../../typo3/sysext/t3skin/extjs/images/backgrounds/button.png');