[FEATURE] Rework workspace notification settings 60/31160/9
authorOliver Hader <oliver@typo3.org>
Thu, 26 Jun 2014 11:38:11 +0000 (13:38 +0200)
committerBenni Mack <benni@typo3.org>
Wed, 30 Sep 2015 19:11:53 +0000 (21:11 +0200)
The current notification settings have some drawbacks and are not
easy to understand if it comes the the expected behavior in the
workspace module. The settings are defined in each sys_workspace
and sys_workspace_stage record and are evaluated in the workspace
module if sending a particular element to be reviewed to the
previous or next stage.

This change extends the meaning and configuration possibilities
on defining the notification settings. In general the notification
modes are replaced by the definition whether the notification
dialog shall be shown and if the preselection can be changed.
Besides that, the preselection is cumulative and defined by
owners, members, editors and responsible persons (for stages).

Resolves: #35245
Releases: master
Change-Id: Icb680fe85fab61a51d53e3afb94b51a4930e180c
Reviewed-on: http://review.typo3.org/31160
Reviewed-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Tested-by: Morton Jonuschat <m.jonuschat@mojocode.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
16 files changed:
typo3/sysext/core/Documentation/Changelog/master/Feature-35245-ReworkWorkspaceNotificationSettings.rst [new file with mode: 0644]
typo3/sysext/install/Classes/Updates/WorkspacesNotificationSettingsUpdate.php [new file with mode: 0644]
typo3/sysext/install/ext_localconf.php
typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php
typo3/sysext/workspaces/Classes/Domain/Record/AbstractRecord.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Domain/Record/StageRecord.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Domain/Record/WorkspaceRecord.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php
typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php
typo3/sysext/workspaces/Classes/Service/RecordService.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/StagesService.php
typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php
typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php
typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf
typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js
typo3/sysext/workspaces/ext_tables.sql

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-35245-ReworkWorkspaceNotificationSettings.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-35245-ReworkWorkspaceNotificationSettings.rst
new file mode 100644 (file)
index 0000000..6d44b39
--- /dev/null
@@ -0,0 +1,51 @@
+========================================================
+Feature: #35245 - Rework workspace notification settings
+========================================================
+
+Description
+===========
+
+The current notification settings have some drawbacks and are not easy to
+understand if it comes the the expected behavior in the workspace module.
+The settings are defined in each sys_workspace and sys_workspace_stage
+record and are evaluated in the workspace module if sending a particular
+element to be reviewed to the previous or next stage.
+
+Currently there are the following notification settings:
+* on stages
+  * "edit stage": takes recipients from "adminusers" field
+     (workspace owners)
+  * "ready to publish" stage: takes recipients from "members" field
+     (workspace members)
+* on preselection of recipients
+  * "all (non-strict)": if users from workspace setting (field "adminusers"
+    or "members") are also in the specific "default_users" setting for the
+    stage, the checkbox is enabled by default and cannot be changed,
+    otherwise it's not checked
+  * "all (strict)": all users from workspace setting (field "adminusers"
+    or "members") are checked and cannot be changed
+  * "some (strict)": all users from workspace setting (field "adminusers"
+     or "members") are checked, but still can be changed
+* behavior
+  * sending to "edit" stage: members are notified per default
+  * sending to "ready to publish" stage: owners are notified per default
+
+The changes extends the possibilities to define notification settings:
+* on stages
+  * add settings for "publish-execute" stage (actual publishing process)
+* on preselection of recipients
+  * remove modes
+  * replace settings for showing the dialog and whether modifying the
+    preselection is allowed at all (getting rid of the "strict" modes)
+  * add possibilities to defined notification recipients
+    * owner & members as defined in the accordant fields
+    * editors that have been working on a particular element
+    * responsible persons (on custom stages only)
+
+Impact
+======
+
+The meaning and behavior of the workspaces notification settings concerning
+preselected recipients and the possibility to modify the selection on moving
+an element to a particular change is different now. However, an upgrade wizard
+helps to upgrade the settings to the new definitions.
\ No newline at end of file
diff --git a/typo3/sysext/install/Classes/Updates/WorkspacesNotificationSettingsUpdate.php b/typo3/sysext/install/Classes/Updates/WorkspacesNotificationSettingsUpdate.php
new file mode 100644 (file)
index 0000000..d79f781
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+namespace TYPO3\CMS\Install\Updates;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+
+/**
+ * Migrate the workspaces notification settings to the enhanced schema.
+ */
+class WorkspacesNotificationSettingsUpdate extends AbstractUpdate {
+
+       /**
+        * @var string
+        */
+       protected $title = 'Migrate the workspaces notification settings to the enhanced schema';
+
+       /**
+        * Checks if an update is needed
+        *
+        * @param string &$description The description for the update
+        * @return bool Whether an update is needed (TRUE) or not (FALSE)
+        */
+       public function checkForUpdate(&$description) {
+               if (!ExtensionManagementUtility::isLoaded('workspaces')) {
+                       return FALSE;
+               }
+
+               if ($this->isWizardDone()) {
+                       return FALSE;
+               }
+
+               $workspacesCount = $this->getDatabaseConnection()->exec_SELECTcountRows(
+                       'uid',
+                       'sys_workspace',
+                       'deleted=0'
+               );
+
+               $stagesCount = $this->getDatabaseConnection()->exec_SELECTcountRows(
+                       'uid',
+                       'sys_workspace_stage',
+                       'deleted=0'
+               );
+
+               if ($workspacesCount + $stagesCount > 0) {
+                       $description = 'The workspaces notification settings have been extended'
+                               . ' and need to be migrated to the new definitions. This update wizard'
+                               . ' upgrades the accordant settings in the availble workspaces and stages.';
+                       return TRUE;
+               } else {
+                       $this->markWizardAsDone();
+               }
+
+               return FALSE;
+       }
+
+       /**
+        * Perform the database updates for workspace records
+        *
+        * @param array &$databaseQueries Queries done in this update
+        * @param mixed &$customMessages Custom messages
+        * @return bool
+        */
+       public function performUpdate(array &$databaseQueries, &$customMessages) {
+               $databaseConnection = $this->getDatabaseConnection();
+
+               $workspaceRecords = $databaseConnection->exec_SELECTgetRows('*', 'sys_workspace', 'deleted=0');
+               foreach ($workspaceRecords as $workspaceRecord) {
+                       $update = $this->prepareWorkspaceUpdate($workspaceRecord);
+                       if ($update !== NULL) {
+                               $databaseConnection->exec_UPDATEquery('sys_workspace', 'uid=' . (int)$workspaceRecord['uid'], $update);
+                               $databaseQueries[] = $databaseConnection->debug_lastBuiltQuery;
+                       }
+               }
+
+               $stageRecords = $databaseConnection->exec_SELECTgetRows('*', 'sys_workspace_stage', 'deleted=0');
+               foreach ($stageRecords as $stageRecord) {
+                       $update = $this->prepareStageUpdate($stageRecord);
+                       if ($update !== NULL) {
+                               $databaseConnection->exec_UPDATEquery('sys_workspace_stage', 'uid=' . (int)$stageRecord['uid'], $update);
+                               $databaseQueries[] = $databaseConnection->debug_lastBuiltQuery;
+                       }
+               }
+
+               $this->markWizardAsDone();
+               return TRUE;
+       }
+
+       /**
+        * Prepares SQL updates for workspace records.
+        *
+        * @param array $workspaceRecord
+        * @return array|NULL
+        */
+       protected function prepareWorkspaceUpdate(array $workspaceRecord) {
+               if (empty($workspaceRecord['uid'])) {
+                       return NULL;
+               }
+
+               $update = array();
+               $update = $this->mapSettings($workspaceRecord, $update, 'edit', 'edit');
+               $update = $this->mapSettings($workspaceRecord, $update, 'publish', 'publish');
+               $update = $this->mapSettings($workspaceRecord, $update, 'publish', 'execute');
+               return $update;
+       }
+
+       /**
+        * Prepares SQL update for stage records.
+        *
+        * @param array $stageRecord
+        * @return array|null
+        */
+       protected function prepareStageUpdate(array $stageRecord) {
+               if (empty($stageRecord['uid'])) {
+                       return NULL;
+               }
+
+               $update = array();
+               $update = $this->mapSettings($stageRecord, $update);
+               return $update;
+       }
+
+       /**
+        * Maps settings to new meaning.
+        *
+        * @param array $record
+        * @param array $update
+        * @param string $from
+        * @param string $to
+        * @return array
+        */
+       protected function mapSettings(array $record, array $update, $from = '', $to = '') {
+               $fromPrefix = ($from ? $from . '_' : '');
+               $toPrefix = ($to ? $to . '_' : '');
+
+               $settings = 0;
+               // Previous setting: "Allow notification settings during stage change"
+               if ($record[$fromPrefix . 'allow_notificaton_settings']) {
+                       $settings += 1;
+               }
+               // Previous setting: "All are selected per default (can be changed)"
+               if ((int)$record[$fromPrefix . 'notification_mode'] === 0) {
+                       $settings += 2;
+               }
+
+               // Custom stages: preselect responsible persons (8)
+               if (isset($record['responsible_persons'])) {
+                       $preselection = 8;
+               // Workspace "edit" stage: preselect members (2)
+               } elseif ($to === 'edit') {
+                       $preselection = 2;
+               // Workspace "publish" stage: preselect owners (1)
+               } elseif ($to === 'publish') {
+                       $preselection = 1;
+               // Workspace "execute" stage: preselect owners (1) and members (2) as default
+               } else {
+                       $preselection = 1 + 2;
+               }
+
+               $update[$toPrefix . 'allow_notificaton_settings'] = $settings;
+               $update[$toPrefix . 'notification_preselection'] = $preselection;
+
+               return $update;
+       }
+
+}
index 157a657..dfc3361 100644 (file)
@@ -11,6 +11,7 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['filesReplace
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['tableCType'] = \TYPO3\CMS\Install\Updates\TableFlexFormToTtContentFieldsUpdate::class;
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][\TYPO3\CMS\Install\Updates\FileListIsStartModuleUpdate::class] = \TYPO3\CMS\Install\Updates\FileListIsStartModuleUpdate::class;
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['textmediaCType'] = \TYPO3\CMS\Install\Updates\ContentTypesToTextMediaUpdate::class;
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update'][\TYPO3\CMS\Install\Updates\WorkspacesNotificationSettingsUpdate::class] = \TYPO3\CMS\Install\Updates\WorkspacesNotificationSettingsUpdate::class;
 
 $signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class);
 $signalSlotDispatcher->connect(
index 142b6e9..372ebc9 100644 (file)
@@ -104,7 +104,7 @@ class DatabaseRecord {
         * @return void
         */
        public function setUid($uid) {
-               $this->uid = $uid;
+               $this->uid = (int)$uid;
        }
 
        /**
diff --git a/typo3/sysext/workspaces/Classes/Domain/Record/AbstractRecord.php b/typo3/sysext/workspaces/Classes/Domain/Record/AbstractRecord.php
new file mode 100644 (file)
index 0000000..7768aae
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Domain\Record;
+
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Workspaces\Service\StagesService;
+
+/**
+ * Combined record class
+ */
+abstract class AbstractRecord {
+
+       /**
+        * @var array
+        */
+       protected $record;
+
+       static protected function fetch($tableName, $uid) {
+               $record = static::getDatabaseConnection()->exec_SELECTgetSingleRow('*', $tableName, 'deleted=0 AND uid=' . (int)$uid);
+               if (empty($record)) {
+                       throw new \RuntimeException('Record "' . $tableName . ':' . $uid . '" not found');
+               }
+               return $record;
+       }
+
+       /**
+        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       static protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
+       /**
+        * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+        */
+       static protected function getBackendUser() {
+               return $GLOBALS['BE_USER'];
+       }
+
+       /**
+        * @return \TYPO3\CMS\Lang\LanguageService
+        */
+       static protected function getLanguageService() {
+               return $GLOBALS['LANG'];
+       }
+
+       /**
+        * @param array $record
+        */
+       public function __construct(array $record) {
+               $this->record = $record;
+       }
+
+       /**
+        * @return string
+        */
+       public function __toString() {
+               return (string)$this->getUid();
+       }
+
+       /**
+        * @return int
+        */
+       public function getUid() {
+               return (int)$this->record['uid'];
+       }
+
+       /**
+        * @return string
+        */
+       public function getTitle() {
+               return (string)$this->record['title'];
+       }
+
+       /**
+        * @return StagesService
+        */
+       protected function getStagesService() {
+               return GeneralUtility::makeInstance(StagesService::class);
+       }
+
+}
diff --git a/typo3/sysext/workspaces/Classes/Domain/Record/StageRecord.php b/typo3/sysext/workspaces/Classes/Domain/Record/StageRecord.php
new file mode 100644 (file)
index 0000000..b198cba
--- /dev/null
@@ -0,0 +1,340 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Domain\Record;
+
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Workspaces\Service\StagesService;
+
+/**
+ * Combined record class
+ */
+class StageRecord extends AbstractRecord{
+
+       /**
+        * @var WorkspaceRecord
+        */
+       protected $workspace;
+
+       /**
+        * @var bool
+        */
+       protected $internal = FALSE;
+
+       /**
+        * @var array
+        */
+       protected $responsiblePersons;
+
+       /**
+        * @var array
+        */
+       protected $defaultRecipients;
+
+       /**
+        * @var array
+        */
+       protected $preselectedRecipients;
+
+       /**
+        * @var array
+        */
+       protected $allRecipients;
+
+       /**
+        * @param int $uid
+        * @param array $record
+        * @return StageRecord
+        */
+       static public function get($uid, array $record = NULL) {
+               if (empty($record)) {
+                       $record = static::fetch('sys_workspace_stage', $uid);
+               }
+               return WorkspaceRecord::get($record['parentid'])->getStage($uid);
+       }
+
+       /**
+        * @param WorkspaceRecord $workspace
+        * @param int $uid
+        * @param array $record
+        * @return StageRecord
+        */
+       static public function build(WorkspaceRecord $workspace, $uid, array $record = NULL) {
+               if (empty($record)) {
+                       $record = static::fetch('sys_workspace_stage', $uid);
+               }
+               return new StageRecord($workspace, $record);
+       }
+
+       /**
+        * @param WorkspaceRecord $workspace
+        * @param array $record
+        */
+       public function __construct(WorkspaceRecord $workspace, array $record) {
+               parent::__construct($record);
+               $this->workspace = $workspace;
+       }
+
+       /**
+        * @return WorkspaceRecord
+        */
+       public function getWorkspace() {
+               return $this->workspace;
+       }
+
+       /**
+        * @return NULL|StageRecord
+        */
+       public function getPrevious() {
+               return $this->getWorkspace()->getPreviousStage($this->getUid());
+       }
+
+       /**
+        * @return NULL|StageRecord
+        */
+       public function getNext() {
+               return $this->getWorkspace()->getNextStage($this->getUid());
+       }
+
+       /**
+        * @param StageRecord $stageRecord
+        * @return int
+        */
+       public function determineOrder(StageRecord $stageRecord) {
+               if ($this->getUid() === $stageRecord->getUid()) {
+                       return 0;
+               } elseif ($this->isEditStage() || $stageRecord->isExecuteStage() || $this->isPreviousTo($stageRecord)) {
+                       return -1;
+               } elseif ($this->isExecuteStage() || $stageRecord->isEditStage() || $this->isNextTo($stageRecord)) {
+                       return 1;
+               }
+               return 0;
+       }
+
+       /**
+        * Determines whether $this is in a previous stage compared to $stageRecord.
+        *
+        * @param StageRecord $stageRecord
+        * @return bool
+        */
+       public function isPreviousTo(StageRecord $stageRecord) {
+               $current = $stageRecord;
+               while ($previous = $current->getPrevious()) {
+                       if ($this->getUid() === $previous->getUid()) {
+                               return TRUE;
+                       }
+                       $current = $previous;
+               }
+               return FALSE;
+       }
+
+       /**
+        * Determines whether $this is in a later stage compared to $stageRecord.
+        *
+        * @param StageRecord $stageRecord
+        * @return bool
+        */
+       public function isNextTo(StageRecord $stageRecord) {
+               $current = $stageRecord;
+               while ($next = $current->getNext()) {
+                       if ($this->getUid() === $next->getUid()) {
+                               return TRUE;
+                       }
+                       $current = $next;
+               }
+               return FALSE;
+       }
+
+       /**
+        * @return string
+        */
+       public function getDefaultComment() {
+               $defaultComment = '';
+               if (isset($this->record['default_mailcomment'])) {
+                       $defaultComment = $this->record['default_mailcomment'];
+               }
+               return $defaultComment;
+       }
+
+       /**
+        * @param bool $internal
+        */
+       public function setInternal($internal = TRUE) {
+               $this->internal = (bool)$internal;
+       }
+
+       /**
+        * @return bool
+        */
+       public function isInternal() {
+               return $this->internal;
+       }
+
+       /**
+        * @return bool
+        */
+       public function isEditStage() {
+               return ($this->getUid() === StagesService::STAGE_EDIT_ID);
+       }
+
+       /**
+        * @return bool
+        */
+       public function isPublishStage() {
+               return ($this->getUid() === StagesService::STAGE_PUBLISH_ID);
+       }
+
+       /**
+        * @return bool
+        */
+       public function isExecuteStage() {
+               return ($this->getUid() === StagesService::STAGE_PUBLISH_EXECUTE_ID);
+       }
+
+       /**
+        * @return bool
+        */
+       public function isDialogEnabled() {
+               return (((int)$this->record['allow_notificaton_settings'] & 1) > 0);
+       }
+
+       /**
+        * @return bool
+        */
+       public function isPreselectionChangeable() {
+               return (((int)$this->record['allow_notificaton_settings'] & 2) > 0);
+       }
+
+       /**
+        * @return bool
+        */
+       public function areOwnersPreselected() {
+               return (((int)$this->record['notification_preselection'] & 1) > 0);
+       }
+
+       /**
+        * @return bool
+        */
+       public function areMembersPreselected() {
+               return (((int)$this->record['notification_preselection'] & 2) > 0);
+       }
+
+       /**
+        * @return bool
+        */
+       public function areEditorsPreselected() {
+               return (((int)$this->record['notification_preselection'] & 4) > 0);
+       }
+
+       /**
+        * @return bool
+        */
+       public function areResponsiblePersonsPreselected() {
+               return (((int)$this->record['notification_preselection'] & 8) > 0);
+       }
+
+       /**
+        * @return bool
+        */
+       public function hasPreselection() {
+               return (
+                       $this->areOwnersPreselected()
+                       || $this->areMembersPreselected()
+                       || $this->areEditorsPreselected()
+                       || $this->areResponsiblePersonsPreselected()
+               );
+       }
+
+       /**
+        * @return array
+        */
+       public function getResponsiblePersons() {
+               if (!isset($this->responsiblePersons)) {
+                       $this->responsiblePersons = array();
+                       if (!empty($this->record['responsible_persons'])) {
+                               $this->responsiblePersons = $this->getStagesService()->resolveBackendUserIds($this->record['responsible_persons']);
+                       }
+               }
+               return $this->responsiblePersons;
+       }
+
+       /**
+        * @return array
+        */
+       public function getDefaultRecipients() {
+               if (!isset($this->defaultRecipients)) {
+                       $this->defaultRecipients = $this->getStagesService()->resolveBackendUserIds($this->record['notification_defaults']);
+               }
+               return $this->defaultRecipients;
+       }
+
+       /**
+        * Gets all recipients (backend user ids).
+        *
+        * @return array
+        */
+       public function getAllRecipients() {
+               if (!isset($this->allRecipients)) {
+                       $allRecipients = $this->getDefaultRecipients();
+
+                       if ($this->isInternal() || $this->areOwnersPreselected()) {
+                               $allRecipients = array_merge($allRecipients, $this->getWorkspace()->getOwners());
+                       }
+                       if ($this->isInternal() || $this->areMembersPreselected()) {
+                               $allRecipients = array_merge($allRecipients, $this->getWorkspace()->getMembers());
+                       }
+                       if (!$this->isInternal()) {
+                               $allRecipients = array_merge($allRecipients, $this->getResponsiblePersons());
+                       }
+
+                       $this->allRecipients = array_unique($allRecipients);
+               }
+
+               return $this->allRecipients;
+       }
+
+       /**
+        * @return int[]
+        */
+       public function getPreselectedRecipients() {
+               if (!isset($this->preselectedRecipients)) {
+                       $preselectedRecipients = $this->getDefaultRecipients();
+
+                       if ($this->areOwnersPreselected()) {
+                               $preselectedRecipients = array_merge($preselectedRecipients, $this->getWorkspace()->getOwners());
+                       }
+                       if ($this->areMembersPreselected()) {
+                               $preselectedRecipients = array_merge($preselectedRecipients, $this->getWorkspace()->getMembers());
+                       }
+                       if ($this->areResponsiblePersonsPreselected()) {
+                               $preselectedRecipients = array_merge($preselectedRecipients, $this->getResponsiblePersons());
+                       }
+
+                       $this->preselectedRecipients = array_unique($preselectedRecipients);
+               }
+
+               return $this->preselectedRecipients;
+       }
+
+       /**
+        * @return bool
+        */
+       public function isAllowed() {
+               return (
+                       $this->isEditStage()
+                       || static::getBackendUser()->workspaceCheckStageForCurrent($this->getUid())
+                       || $this->isExecuteStage() && static::getBackendUser()->workspacePublishAccess($this->workspace->getUid())
+               );
+       }
+
+}
diff --git a/typo3/sysext/workspaces/Classes/Domain/Record/WorkspaceRecord.php b/typo3/sysext/workspaces/Classes/Domain/Record/WorkspaceRecord.php
new file mode 100644 (file)
index 0000000..b951776
--- /dev/null
@@ -0,0 +1,209 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Domain\Record;
+
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Workspaces\Service\StagesService;
+
+/**
+ * Combined record class
+ */
+class WorkspaceRecord extends AbstractRecord{
+
+       /**
+        * @var array
+        */
+       protected $internalStages = array(
+               StagesService::STAGE_EDIT_ID => array(
+                       'name' => 'edit',
+                       'label' => 'LLL:EXT:lang/locallang_mod_user_ws.xlf:stage_editing'
+               ),
+               StagesService::STAGE_PUBLISH_ID => array(
+                       'name' => 'publish',
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_mod.xlf:stage_ready_to_publish'
+               ),
+               StagesService::STAGE_PUBLISH_EXECUTE_ID => array(
+                       'name' => 'execute',
+                       'label' => 'LLL:EXT:lang/locallang_mod_user_ws.xlf:stage_publish'
+               ),
+       );
+
+       /**
+        * @var array
+        */
+       protected $internalStageFieldNames = array(
+               'notification_defaults',
+               'notification_preselection',
+               'allow_notificaton_settings'
+       );
+
+       /**
+        * @var array
+        */
+       protected $owners;
+
+       /**
+        * @var array
+        */
+       protected $members;
+
+       /**
+        * @var StageRecord[]
+        */
+       protected $stages;
+
+       /**
+        * @param int $uid
+        * @param array $record
+        * @return WorkspaceRecord
+        */
+       static public function get($uid, array $record = NULL) {
+               if (empty($uid)) {
+                       $record = array();
+               } elseif (empty($record)) {
+                       $record = static::fetch('sys_workspace', $uid);
+               }
+               return new static($record);
+       }
+
+       /**
+        * @return array
+        */
+       public function getOwners() {
+               if (!isset($this->owners)) {
+                       $this->owners = $this->getStagesService()->resolveBackendUserIds($this->record['adminusers']);
+               }
+               return $this->owners;
+       }
+
+       /**
+        * @return array
+        */
+       public function getMembers() {
+               if (!isset($this->members)) {
+                       $this->members = $this->getStagesService()->resolveBackendUserIds($this->record['members']);
+               }
+               return $this->members;
+       }
+
+       /**
+        * @return StageRecord[]
+        */
+       public function getStages() {
+               if (!isset($this->stages)) {
+                       $this->stages = array();
+                       $this->addStage($this->createInternalStage(StagesService::STAGE_EDIT_ID));
+
+                       $records = self::getDatabaseConnection()->exec_SELECTgetRows(
+                               '*', 'sys_workspace_stage',
+                               'deleted=0 AND parentid=' . $this->getUid() . ' AND parenttable='
+                                       . self::getDatabaseConnection()->fullQuoteStr('sys_workspace', 'sys_workspace_stage'),
+                               '', 'sorting'
+                       );
+                       if (!empty($records)) {
+                               foreach ($records as $record) {
+                                       $this->addStage(StageRecord::build($this, $record['uid'], $record));
+                               }
+                       }
+
+                       $this->addStage($this->createInternalStage(StagesService::STAGE_PUBLISH_ID));
+                       $this->addStage($this->createInternalStage(StagesService::STAGE_PUBLISH_EXECUTE_ID));
+               }
+
+               return $this->stages;
+       }
+
+       /**
+        * @param int $stageId
+        * @return NULL|StageRecord
+        */
+       public function getStage($stageId) {
+               $stageId = (int)$stageId;
+               $this->getStages();
+               if (!isset($this->stages[$stageId])) {
+                       return NULL;
+               }
+               return $this->stages[$stageId];
+       }
+
+       /**
+        * @param int $stageId
+        * @return NULL|StageRecord
+        */
+       public function getPreviousStage($stageId) {
+               $stageId = (int)$stageId;
+               $stageIds = array_keys($this->getStages());
+               $stageIndex = array_search($stageId, $stageIds);
+
+               // catches "0" (edit stage) as well
+               if (empty($stageIndex)) {
+                       return NULL;
+               }
+
+               $previousStageId = $stageIds[$stageIndex - 1];
+               return $this->stages[$previousStageId];
+       }
+
+       /**
+        * @param int $stageId
+        * @return NULL|StageRecord
+        */
+       public function getNextStage($stageId) {
+               $stageId = (int)$stageId;
+               $stageIds = array_keys($this->getStages());
+               $stageIndex = array_search($stageId, $stageIds);
+
+               if ($stageIndex === FALSE || !isset($stageIds[$stageIndex + 1])) {
+                       return NULL;
+               }
+
+               $nextStageId = $stageIds[$stageIndex + 1];
+               return $this->stages[$nextStageId];
+       }
+
+       /**
+        * @param StageRecord $stage
+        */
+       protected function addStage(StageRecord $stage) {
+               $this->stages[$stage->getUid()] = $stage;
+       }
+
+       /**
+        * @param int $stageId
+        * @return StageRecord
+        * @throws \RuntimeException
+        */
+       protected function createInternalStage($stageId) {
+               $stageId = (int)$stageId;
+
+               if (!isset($this->internalStages[$stageId])) {
+                       throw new \RuntimeException('Invalid internal stage "' . $stageId . '"');
+               }
+
+               $record = array(
+                       'uid' => $stageId,
+                       'title' => static::getLanguageService()->sL($this->internalStages[$stageId]['label'])
+               );
+
+               $fieldNamePrefix = $this->internalStages[$stageId]['name'] . '_';
+               foreach ($this->internalStageFieldNames as $fieldName) {
+                       $record[$fieldName] = $this->record[$fieldNamePrefix . $fieldName];
+               }
+
+               $stage = StageRecord::build($this, $stageId, $record);
+               $stage->setInternal(TRUE);
+               return $stage;
+       }
+
+}
index 526812c..bf7e673 100644 (file)
@@ -17,6 +17,8 @@ namespace TYPO3\CMS\Workspaces\ExtDirect;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Workspaces\Service\StagesService;
+use TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord;
+use TYPO3\CMS\Workspaces\Domain\Record\StageRecord;
 
 /**
  * ExtDirect action handler
@@ -224,13 +226,14 @@ class ActionHandler extends AbstractHandler {
                $currentWorkspace = $this->setTemporaryWorkspace($elementRecord['t3ver_wsid']);
 
                if (is_array($elementRecord)) {
-                       $stageId = $elementRecord['t3ver_stage'];
-                       if ($this->getStageService()->isValid($stageId)) {
-                               $nextStage = $this->getStageService()->getNextStage($stageId);
-                               $result = $this->getSentToStageWindow($nextStage['uid']);
+                       $workspaceRecord = WorkspaceRecord::get($elementRecord['t3ver_wsid']);
+                       $nextStageRecord = $workspaceRecord->getNextStage($elementRecord['t3ver_stage']);
+                       if ($nextStageRecord !== NULL) {
+                               $this->stageService->getRecordService()->add($table, $uid);
+                               $result = $this->getSentToStageWindow($nextStageRecord);
                                $result['affects'] = array(
                                        'table' => $table,
-                                       'nextStage' => $nextStage['uid'],
+                                       'nextStage' => $nextStageRecord->getUid(),
                                        't3ver_oid' => $t3ver_oid,
                                        'uid' => $uid
                                );
@@ -257,15 +260,18 @@ class ActionHandler extends AbstractHandler {
                $currentWorkspace = $this->setTemporaryWorkspace($elementRecord['t3ver_wsid']);
 
                if (is_array($elementRecord)) {
-                       $stageId = $elementRecord['t3ver_stage'];
-                       if ($this->getStageService()->isValid($stageId)) {
-                               if ($stageId !== StagesService::STAGE_EDIT_ID) {
-                                       $prevStage = $this->getStageService()->getPrevStage($stageId);
-                                       $result = $this->getSentToStageWindow($prevStage['uid']);
+                       $workspaceRecord = WorkspaceRecord::get($elementRecord['t3ver_wsid']);
+                       $stageRecord = $workspaceRecord->getStage($elementRecord['t3ver_stage']);
+
+                       if ($stageRecord !== NULL) {
+                               if (!$stageRecord->isEditStage()) {
+                                       $this->stageService->getRecordService()->add($table, $uid);
+                                       $previousStageRecord = $stageRecord->getPrevious();
+                                       $result = $this->getSentToStageWindow($previousStageRecord);
                                        $result['affects'] = array(
                                                'table' => $table,
                                                'uid' => $uid,
-                                               'nextStage' => $prevStage['uid']
+                                               'nextStage' => $previousStageRecord->getUid()
                                        );
                                } else {
                                        // element is already in edit stage, there is no prev stage - return an error message
@@ -286,9 +292,17 @@ class ActionHandler extends AbstractHandler {
         * Gets the dialog window to be displayed before a record can be sent to a specific stage.
         *
         * @param int $nextStageId
+        * @param array|\stdClass[] $elements
         * @return array
         */
-       public function sendToSpecificStageWindow($nextStageId) {
+       public function sendToSpecificStageWindow($nextStageId, array $elements) {
+               foreach ($elements as $element) {
+                       $this->stageService->getRecordService()->add(
+                               $element->table,
+                               $element->uid
+                       );
+               }
+
                $result = $this->getSentToStageWindow($nextStageId);
                $result['affects'] = array(
                        'nextStage' => $nextStageId
@@ -299,20 +313,27 @@ class ActionHandler extends AbstractHandler {
        /**
         * Gets a merged variant of recipient defined by uid and custom ones.
         *
-        * @param array list of recipients
-        * @param string given user string of additional recipients
-        * @param int stage id
+        * @param array $uidOfRecipients list of recipients
+        * @param string $additionalRecipients given user string of additional recipients
+        * @param int $stageId stage id
         * @return array
+        * @throws \InvalidArgumentException
         */
        public function getRecipientList(array $uidOfRecipients, $additionalRecipients, $stageId) {
-               $finalRecipients = array();
-               if (!$this->getStageService()->isValid($stageId)) {
+               $stageRecord = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($stageId);
+
+               if ($stageRecord === NULL) {
                        throw new \InvalidArgumentException($GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:error.stageId.integer'));
-               } else {
-                       $stageId = (int)$stageId;
                }
+
                $recipients = array();
+               $finalRecipients = array();
+               $backendUserIds = $stageRecord->getAllRecipients();
                foreach ($uidOfRecipients as $userUid) {
+                       // Ensure that only configured backend users are considered
+                       if (!in_array($userUid, $backendUserIds)) {
+                               continue;
+                       }
                        $beUserRecord = BackendUtility::getRecord('be_users', (int)$userUid);
                        if (is_array($beUserRecord) && $beUserRecord['email'] !== '') {
                                $uc = $beUserRecord['uc'] ? unserialize($beUserRecord['uc']) : array();
@@ -322,22 +343,26 @@ class ActionHandler extends AbstractHandler {
                                );
                        }
                }
-               // the notification mode can be configured in the workspace stage record
-               $notification_mode = (int)$this->getStageService()->getNotificationMode($stageId);
-               if ($notification_mode === StagesService::MODE_NOTIFY_ALL || $notification_mode === StagesService::MODE_NOTIFY_ALL_STRICT) {
-                       // get the default recipients from the stage configuration
-                       // the default recipients needs to be added in some cases of the notification_mode
-                       $default_recipients = $this->getStageService()->getResponsibleBeUser($stageId, TRUE);
-                       foreach ($default_recipients as $default_recipient_uid => $default_recipient_record) {
-                               if (!isset($recipients[$default_recipient_record['email']])) {
-                                       $uc = $default_recipient_record['uc'] ? unserialize($default_recipient_record['uc']) : array();
-                                       $recipients[$default_recipient_record['email']] = array(
-                                               'email' => $default_recipient_record['email'],
-                                               'lang' => isset($uc['lang']) ? $uc['lang'] : $default_recipient_record['lang']
+
+               if ($stageRecord->hasPreselection() && !$stageRecord->isPreselectionChangeable()) {
+                       $preselectedBackendUsers = $this->getStageService()->getBackendUsers(
+                               implode(',', $this->stageService->getPreselectedRecipients($stageRecord))
+                       );
+
+                       foreach ($preselectedBackendUsers as $preselectedBackendUser) {
+                               if (empty($preselectedBackendUser['email']) || !GeneralUtility::validEmail($preselectedBackendUser['email'])) {
+                                       continue;
+                               }
+                               if (!isset($recipients[$preselectedBackendUser['email']])) {
+                                       $uc = (!empty($preselectedBackendUser['uc']) ? unserialize($preselectedBackendUser['uc']) : array());
+                                       $recipients[$preselectedBackendUser['email']] = array(
+                                               'email' => $preselectedBackendUser['email'],
+                                               'lang' => (isset($uc['lang']) ? $uc['lang'] : $preselectedBackendUser['lang'])
                                        );
                                }
                        }
                }
+
                if ($additionalRecipients !== '') {
                        $emails = GeneralUtility::trimExplode(LF, $additionalRecipients, TRUE);
                        $additionalRecipients = array();
@@ -601,43 +626,26 @@ class ActionHandler extends AbstractHandler {
        /**
         * Gets the dialog window to be displayed before a record can be sent to a stage.
         *
-        * @param $nextStageId
+        * @param StageRecord|int $nextStageId
         * @return array
         */
-       protected function getSentToStageWindow($nextStageId) {
-               $workspaceRec = BackendUtility::getRecord('sys_workspace', $this->getStageService()->getWorkspaceId());
-               $showNotificationFields = FALSE;
-               $stageTitle = $this->getStageService()->getStageTitle($nextStageId);
+       protected function getSentToStageWindow($nextStage) {
+               if (!$nextStage instanceof StageRecord) {
+                       $nextStage = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($nextStage);
+               }
+
                $result = array(
                        'title' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:actionSendToStage'),
                        'items' => array(
                                array(
                                        'xtype' => 'panel',
                                        'bodyStyle' => 'margin-bottom: 7px; border: none;',
-                                       'html' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.itemsWillBeSentTo') . ' ' . $stageTitle
+                                       'html' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.itemsWillBeSentTo') . ' ' . $nextStage->getTitle()
                                )
                        )
                );
-               switch ($nextStageId) {
-                       case StagesService::STAGE_PUBLISH_EXECUTE_ID:
 
-                       case StagesService::STAGE_PUBLISH_ID:
-                               if (!empty($workspaceRec['publish_allow_notificaton_settings'])) {
-                                       $showNotificationFields = TRUE;
-                               }
-                               break;
-                       case StagesService::STAGE_EDIT_ID:
-                               if (!empty($workspaceRec['edit_allow_notificaton_settings'])) {
-                                       $showNotificationFields = TRUE;
-                               }
-                               break;
-                       default:
-                               $allow_notificaton_settings = $this->getStageService()->getPropertyOfCurrentWorkspaceStage($nextStageId, 'allow_notificaton_settings');
-                               if (!empty($allow_notificaton_settings)) {
-                                       $showNotificationFields = TRUE;
-                               }
-               }
-               if ($showNotificationFields == TRUE) {
+               if ($nextStage->isDialogEnabled()) {
                        $result['items'][] = array(
                                'fieldLabel' => $GLOBALS['LANG']->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:window.sendToNextStageWindow.sendMailTo'),
                                'xtype' => 'checkboxgroup',
@@ -646,7 +654,7 @@ class ActionHandler extends AbstractHandler {
                                'style' => 'max-height: 200px',
                                'autoScroll' => TRUE,
                                'items' => array(
-                                       $this->getReceipientsOfStage($nextStageId)
+                                       $this->getReceipientsOfStage($nextStage->getUid())
                                )
                        );
                        $result['items'][] = array(
@@ -661,52 +669,45 @@ class ActionHandler extends AbstractHandler {
                        'name' => 'comments',
                        'xtype' => 'textarea',
                        'width' => 250,
-                       'value' => $this->getDefaultCommentOfStage($nextStageId)
+                       'value' => ($nextStage->isInternal() ? '' : $nextStage->getDefaultComment())
                );
+
                return $result;
        }
 
        /**
         * Gets all assigned recipients of a particular stage.
         *
-        * @param int $stage
+        * @param StageRecord|int $stageRecord
         * @return array
         */
-       protected function getReceipientsOfStage($stage) {
+       protected function getReceipientsOfStage($stageRecord) {
+               if (!$stageRecord instanceof StageRecord) {
+                       $stageRecord = WorkspaceRecord::get($this->getCurrentWorkspace())->getStage($stageRecord);
+               }
+
                $result = array();
-               $recipients = $this->getStageService()->getResponsibleBeUser($stage);
-               $default_recipients = $this->getStageService()->getResponsibleBeUser($stage, TRUE);
-               foreach ($recipients as $id => $user) {
-                       if (GeneralUtility::validEmail($user['email'])) {
-                               $checked = FALSE;
-                               $disabled = FALSE;
-                               $name = $user['realName'] ? $user['realName'] : $user['username'];
-                               // the notification mode can be configured in the workspace stage record
-                               $notification_mode = (int)$this->getStageService()->getNotificationMode($stage);
-                               if ($notification_mode === StagesService::MODE_NOTIFY_SOMEONE) {
-                                       // all responsible users are checked per default, as in versions before
-                                       $checked = TRUE;
-                               } elseif ($notification_mode === StagesService::MODE_NOTIFY_ALL) {
-                                       // the default users are checked only
-                                       if (!empty($default_recipients[$id])) {
-                                               $checked = TRUE;
-                                               $disabled = TRUE;
-                                       } else {
-                                               $checked = FALSE;
-                                       }
-                               } elseif ($notification_mode === StagesService::MODE_NOTIFY_ALL_STRICT) {
-                                       // all responsible users are checked
-                                       $checked = TRUE;
-                                       $disabled = TRUE;
-                               }
-                               $result[] = array(
-                                       'boxLabel' => sprintf('%s (%s)', $name, $user['email']),
-                                       'name' => 'receipients-' . $id,
-                                       'checked' => $checked,
-                                       'disabled' => $disabled
-                               );
+               $allRecipients = $this->getStageService()->getResponsibleBeUser($stageRecord);
+               $preselectedRecipients = $this->stageService->getPreselectedRecipients($stageRecord);
+               $isPreselectionChangeable = $stageRecord->isPreselectionChangeable();
+
+               foreach ($allRecipients as $backendUserId => $backendUser) {
+                       if (empty($backendUser['email']) || !GeneralUtility::validEmail($backendUser['email'])) {
+                               continue;
                        }
+
+                       $name = (!empty($backendUser['realName']) ? $backendUser['realName'] : $backendUser['username']);
+                       $checked = in_array($backendUserId, $preselectedRecipients);
+                       $disabled = ($checked && !$isPreselectionChangeable);
+
+                       $result[] = array(
+                               'boxLabel' => sprintf('%s (%s)', $name, $backendUser['email']),
+                               'name' => 'receipients-' . $backendUserId,
+                               'checked' => $checked,
+                               'disabled' => $disabled
+                       );
                }
+
                return $result;
        }
 
index b90617f..d506943 100644 (file)
@@ -18,6 +18,7 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
 
 /**
  * ExtDirect server
@@ -297,4 +298,11 @@ class ExtDirectServer extends AbstractHandler {
                return $this->stagesService;
        }
 
+       /**
+        * @return \TYPO3\CMS\Extbase\Object\ObjectManager
+        */
+       protected function getObjectManager() {
+               return GeneralUtility::makeInstance(ObjectManager::class);
+       }
+
 }
diff --git a/typo3/sysext/workspaces/Classes/Service/RecordService.php b/typo3/sysext/workspaces/Classes/Service/RecordService.php
new file mode 100644 (file)
index 0000000..c7b3913
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+namespace TYPO3\CMS\Workspaces\Service;
+
+/**
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Workspaces\Domain\Model\DatabaseRecord;
+
+/**
+ * Service for records
+ */
+class RecordService implements \TYPO3\CMS\Core\SingletonInterface {
+
+       /**
+        * @var DatabaseRecord[]
+        */
+       protected $records = array();
+
+       /**
+        * @param string $tableName
+        * @param int $id
+        */
+       public function add($tableName, $id) {
+               $databaseRecord = DatabaseRecord::create($tableName, $id);
+               if (!isset($this->records[$databaseRecord->getIdentifier()])) {
+                       $this->records[$databaseRecord->getIdentifier()] = $databaseRecord;
+               }
+       }
+
+       /**
+        * @return array
+        */
+       public function getIdsPerTable() {
+               $idsPerTable = array();
+               foreach ($this->records as $databaseRecord) {
+                       if (!isset($idsPerTable[$databaseRecord->getTable()])) {
+                               $idsPerTable[$databaseRecord->getTable()] = array();
+                       }
+                       $idsPerTable[$databaseRecord->getTable()][] = $databaseRecord->getUid();
+               }
+               return $idsPerTable;
+       }
+
+       /**
+        * @return array
+        */
+       public function getCreateUserIds() {
+               $createUserIds = array();
+               foreach ($this->getIdsPerTable() as $tableName => $ids) {
+                       if (empty($GLOBALS['TCA'][$tableName]['ctrl']['cruser_id'])) {
+                               continue;
+                       }
+                       $createUserIdFieldName = $GLOBALS['TCA'][$tableName]['ctrl']['cruser_id'];
+                       $records = $this->getDatabaseConnection()->exec_SELECTgetRows(
+                               $createUserIdFieldName, $tableName,
+                               'uid IN (' . implode(',', $ids) . ')',
+                               $createUserIdFieldName,
+                               '', '',
+                               $createUserIdFieldName
+                       );
+                       if (!empty($records)) {
+                               $createUserIds = array_merge($createUserIds, array_keys($records));
+                       }
+               }
+               return array_unique($createUserIds);
+       }
+
+       /**
+        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
+}
index d064c19..439999c 100644 (file)
@@ -17,11 +17,13 @@ namespace TYPO3\CMS\Workspaces\Service;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord;
+use TYPO3\CMS\Workspaces\Domain\Record\StageRecord;
 
 /**
  * Stages service
  */
-class StagesService {
+class StagesService implements \TYPO3\CMS\Core\SingletonInterface {
 
        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
@@ -41,6 +43,11 @@ class StagesService {
        private $pathToLocallang = 'LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf';
 
        /**
+        * @var RecordService
+        */
+       protected $recordService;
+
+       /**
         * Local cache to reduce number of database queries for stages, groups, etc.
         *
         * @var array
@@ -173,35 +180,12 @@ class StagesService {
         * @return array id and title of the stages
         */
        public function getStagesForWS() {
-               $stages = array();
                if (isset($this->workspaceStageCache[$this->getWorkspaceId()])) {
                        $stages = $this->workspaceStageCache[$this->getWorkspaceId()];
+               } elseif ($this->getWorkspaceId() === 0) {
+                       $stages = array();
                } 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.xlf:stage_editing') . '"'
-                       );
-                       $workspaceRec = BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
-                       if ($workspaceRec['custom_stages'] > 0) {
-                               // Get all stage records for this workspace
-                               $where = 'parentid=' . $this->getWorkspaceId() . ' AND parenttable='
-                                       . $GLOBALS['TYPO3_DB']->fullQuoteStr('sys_workspace', self::TABLE_STAGE) . ' AND deleted=0';
-                               $workspaceStageRecs = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*', self::TABLE_STAGE, $where, '', 'sorting', '', 'uid');
-                               foreach ($workspaceStageRecs as $stage) {
-                                       $stage['title'] = $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $stage['title'] . '"';
-                                       $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.xlf:stage_ready_to_publish') . '"'
-                       );
-                       $stages[] = array(
-                               'uid' => self::STAGE_PUBLISH_EXECUTE_ID,
-                               'title' => $GLOBALS['LANG']->sL($this->pathToLocallang . ':publish_execute_action_option')
-                       );
+                       $stages = $this->prepareStagesArray($this->getWorkspaceRecord()->getStages());
                        $this->workspaceStageCache[$this->getWorkspaceId()] = $stages;
                }
                return $stages;
@@ -213,39 +197,58 @@ class StagesService {
         * @return array id and title of stages
         */
        public function getStagesForWSUser() {
-               $stagesForWSUserData = array();
+               if ($GLOBALS['BE_USER']->isAdmin()) {
+                       return $this->getStagesForWS();
+               }
+
+               /** @var $allowedStages StageRecord[] */
                $allowedStages = array();
-               $orderedAllowedStages = array();
-               $workspaceStageRecs = $this->getStagesForWS();
-               if (is_array($workspaceStageRecs) && !empty($workspaceStageRecs)) {
-                       if ($GLOBALS['BE_USER']->isAdmin()) {
-                               $orderedAllowedStages = $workspaceStageRecs;
+               $stageRecords = $this->getWorkspaceRecord()->getStages();
+
+               // Only use stages that are allowed for current backend user
+               foreach ($stageRecords as $stageRecord) {
+                       if ($stageRecord->isAllowed()) {
+                               $allowedStages[$stageRecord->getUid()] = $stageRecord;
+                       }
+               }
+
+               // Add previous and next stages (even if they are not allowed!)
+               foreach ($allowedStages as $allowedStage) {
+                       $previousStage = $allowedStage->getPrevious();
+                       $nextStage = $allowedStage->getNext();
+                       if ($previousStage !== NULL && !isset($allowedStages[$previousStage->getUid()])) {
+                               $allowedStages[$previousStage->getUid()] = $previousStage;
+                       }
+                       if ($nextStage !== NULL && !isset($allowedStages[$nextStage->getUid()])) {
+                               $allowedStages[$nextStage->getUid()] = $nextStage;
+                       }
+               }
+
+               uasort($allowedStages, function(StageRecord $first, StageRecord $second) { return $first->determineOrder($second); });
+               return $this->prepareStagesArray($allowedStages);
+       }
+
+       /**
+        * Prepares simplified stages array to be used in ExtJs components.
+        *
+        * @param StageRecord[] $stageRecords
+        * @return array
+        */
+       protected function prepareStagesArray(array $stageRecords) {
+               $stagesArray = array();
+               foreach ($stageRecords as $stageRecord) {
+                       $stage = array(
+                               'uid' => $stageRecord->getUid(),
+                               'label' => $stageRecord->getTitle(),
+                       );
+                       if (!$stageRecord->isExecuteStage()) {
+                               $stage['title'] = $GLOBALS['LANG']->sL(($this->pathToLocallang . ':actionSendToStage')) . ' "' . $stageRecord->getTitle() . '"';
                        } else {
-                               foreach ($workspaceStageRecs as $workspaceStageRec) {
-                                       if ($workspaceStageRec['uid'] === self::STAGE_EDIT_ID) {
-                                               $allowedStages[self::STAGE_EDIT_ID] = $workspaceStageRec;
-                                               $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
-                                       } elseif ($this->isStageAllowedForUser($workspaceStageRec['uid'])) {
-                                               $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
-                                       } elseif ($workspaceStageRec['uid'] == self::STAGE_PUBLISH_EXECUTE_ID && $GLOBALS['BE_USER']->workspacePublishAccess($this->getWorkspaceId())) {
-                                               $allowedStages[] = $workspaceStageRec;
-                                               $stagesForWSUserData[$workspaceStageRec['uid']] = $workspaceStageRec;
-                                       }
-                               }
-                               foreach ($stagesForWSUserData as $allowedStage) {
-                                       $nextStage = $this->getNextStage($allowedStage['uid']);
-                                       $prevStage = $this->getPrevStage($allowedStage['uid']);
-                                       if (isset($prevStage['uid'])) {
-                                               $allowedStages[$prevStage['uid']] = $prevStage;
-                                       }
-                                       if (isset($nextStage['uid'])) {
-                                               $allowedStages[$nextStage['uid']] = $nextStage;
-                                       }
-                               }
-                               $orderedAllowedStages = array_values($allowedStages);
+                               $stage['title'] = $GLOBALS['LANG']->sL($this->pathToLocallang . ':publish_execute_action_option');
                        }
+                       $stagesArray[] = $stage;
                }
-               return $orderedAllowedStages;
+               return $stagesArray;
        }
 
        /**
@@ -406,95 +409,129 @@ class StagesService {
        }
 
        /**
-        * Get array of all responsilbe be_users for a stage
+        * Gets all backend user records that are considered to be responsible
+        * for a particular stage or workspace.
         *
-        * @param int $stageId Stage id
+        * @param StageRecord|int $stageRecord Stage
         * @param bool $selectDefaultUserField If field notification_defaults should be selected instead of responsible users
         * @return array be_users with e-mail and name
         */
-       public function getResponsibleBeUser($stageId, $selectDefaultUserField = FALSE) {
-               $workspaceRec = BackendUtility::getRecord('sys_workspace', $this->getWorkspaceId());
+       public function getResponsibleBeUser($stageRecord, $selectDefaultUserField = FALSE) {
+               if (!$stageRecord instanceof StageRecord) {
+                       $stageRecord = $this->getWorkspaceRecord()->getStage($stageRecord);
+               }
+
                $recipientArray = array();
-               switch ($stageId) {
-                       case self::STAGE_PUBLISH_EXECUTE_ID:
 
-                       case self::STAGE_PUBLISH_ID:
-                               if (!$selectDefaultUserField) {
-                                       $userList = $this->getResponsibleUser($workspaceRec['adminusers'] . ',' . $workspaceRec['members']);
-                               } else {
-                                       $notification_default_user = $workspaceRec['publish_notification_defaults'];
-                                       $userList = $this->getResponsibleUser($notification_default_user);
-                               }
-                               break;
-                       case self::STAGE_EDIT_ID:
-                               if (!$selectDefaultUserField) {
-                                       $userList = $this->getResponsibleUser($workspaceRec['adminusers'] . ',' . $workspaceRec['members']);
-                               } else {
-                                       $notification_default_user = $workspaceRec['edit_notification_defaults'];
-                                       $userList = $this->getResponsibleUser($notification_default_user);
-                               }
-                               break;
-                       default:
-                               if (!$selectDefaultUserField) {
-                                       $responsible_persons = $this->getPropertyOfCurrentWorkspaceStage($stageId, 'responsible_persons');
-                                       $userList = $this->getResponsibleUser($responsible_persons);
-                               } else {
-                                       $notification_default_user = $this->getPropertyOfCurrentWorkspaceStage($stageId, 'notification_defaults');
-                                       $userList = $this->getResponsibleUser($notification_default_user);
-                               }
-               }
-               if (!empty($userList)) {
-                       $userRecords = BackendUtility::getUserNames(
-                               'username, uid, email, realName',
-                               'AND uid IN (' . $userList . ')' . BackendUtility::BEenableFields('be_users')
-                       );
+               if (!$selectDefaultUserField) {
+                       $backendUserIds = $stageRecord->getAllRecipients();
+               } else {
+                       $backendUserIds = $stageRecord->getDefaultRecipients();
                }
-               if (!empty($userRecords) && is_array($userRecords)) {
-                       foreach ($userRecords as $userUid => $userRecord) {
-                               $recipientArray[$userUid] = $userRecord;
-                       }
+
+               $userList = implode(',', $backendUserIds);
+               $userRecords = $this->getBackendUsers($userList);
+               foreach ($userRecords as $userUid => $userRecord) {
+                       $recipientArray[$userUid] = $userRecord;
                }
                return $recipientArray;
        }
 
        /**
-        * Get uids of all responsilbe persons for a stage
+        * Gets backend user ids from a mixed list of backend users
+        * and backend users groups. This is used for notifying persons
+        * responsible for a particular stage or workspace.
         *
         * @param string $stageRespValue Responsible_person value from stage record
-        * @return string Uid list of responsible be_users
+        * @return string List of backend user ids
         */
        public function getResponsibleUser($stageRespValue) {
-               $stageValuesArray = GeneralUtility::trimExplode(',', $stageRespValue, TRUE);
-               $beuserUidArray = array();
-               $begroupUidArray = array();
+               return implode(',', $this->resolveBackendUserIds($stageRespValue));
+       }
+
+       /**
+        * Resolves backend user ids from a mixed list of backend users
+        * and backend user groups (e.g. "be_users_1,be_groups_3,be_users_4,...")
+        *
+        * @param string $backendUserGroupList
+        * @return array
+        */
+       public function resolveBackendUserIds($backendUserGroupList) {
+               $elements = GeneralUtility::trimExplode(',', $backendUserGroupList, TRUE);
+               $backendUserIds = array();
+               $backendGroupIds = array();
 
-               foreach ($stageValuesArray as $uidvalue) {
-                       if (strstr($uidvalue, 'be_users') !== FALSE) {
+               foreach ($elements as $element) {
+                       if (strpos($element, 'be_users_') === 0) {
                                // 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);
-                       } elseif ((int)$uidvalue) {
-                               $beuserUidArray[] = (int)$uidvalue;
+                               $backendUserIds[] = str_replace('be_users_', '', $element);
+                       } elseif (strpos($element, 'be_groups_') === 0) {
+                               $backendGroupIds[] = str_replace('be_groups_', '', $element);
+                       } elseif ((int)$element) {
+                               $backendUserIds[] = (int)$element;
                        }
                }
 
-               if (!empty($begroupUidArray)) {
+               if (!empty($backendGroupIds)) {
                        $allBeUserArray = BackendUtility::getUserNames();
-                       $begroupUidList = implode(',', $begroupUidArray);
+                       $backendGroupList = implode(',', $backendGroupIds);
                        $this->userGroups = array();
-                       $begroupUidArray = $this->fetchGroups($begroupUidList);
-                       foreach ($begroupUidArray as $groupkey => $groupData) {
-                               foreach ($allBeUserArray as $useruid => $userdata) {
-                                       if (GeneralUtility::inList($userdata['usergroup_cached_list'], $groupData['uid'])) {
-                                               $beuserUidArray[] = $useruid;
+                       $backendGroups = $this->fetchGroups($backendGroupList);
+                       foreach ($backendGroups as $backendGroup) {
+                               foreach ($allBeUserArray as $backendUserId => $backendUser) {
+                                       if (GeneralUtility::inList($backendUser['usergroup_cached_list'], $backendGroup['uid'])) {
+                                               $backendUserIds[] = $backendUserId;
                                        }
                                }
                        }
                }
 
-               array_unique($beuserUidArray);
-               return implode(',', $beuserUidArray);
+               return array_unique($backendUserIds);
+       }
+
+       /**
+        * Gets backend user records from a given list of ids.
+        *
+        * @param string $backendUserList
+        * @return array
+        */
+       public function getBackendUsers($backendUserList) {
+               if (empty($backendUserList)) {
+                       return array();
+               }
+
+               $backendUserList = $this->getDatabaseConnection()->cleanIntList($backendUserList);
+               $backendUsers = BackendUtility::getUserNames(
+                       'username, uid, email, realName',
+                       'AND uid IN (' . $backendUserList . ')' . BackendUtility::BEenableFields('be_users')
+               );
+
+               if (empty($backendUsers)) {
+                       $backendUsers = array();
+               }
+               return $backendUsers;
+       }
+
+       /**
+        * @param StageRecord $stageRecord
+        * @return array
+        */
+       public function getPreselectedRecipients(StageRecord $stageRecord) {
+               if ($stageRecord->areEditorsPreselected()) {
+                       return array_merge(
+                               $stageRecord->getPreselectedRecipients(),
+                               $this->getRecordService()->getCreateUserIds()
+                       );
+               } else {
+                       return $stageRecord->getPreselectedRecipients();
+               }
+       }
+
+       /**
+        * @return WorkspaceRecord
+        */
+       protected function getWorkspaceRecord() {
+               return WorkspaceRecord::get($this->getWorkspaceId());
        }
 
        /**
@@ -734,10 +771,27 @@ class StagesService {
        }
 
        /**
+        * @return RecordService
+        */
+       public function getRecordService() {
+               if (!isset($this->recordService)) {
+                       $this->recordService = GeneralUtility::makeInstance(RecordService::class);
+               }
+               return $this->recordService;
+       }
+
+       /**
         * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
         */
        protected function getBackendUser() {
                return $GLOBALS['BE_USER'];
        }
 
+       /**
+        * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+        */
+       protected function getDatabaseConnection() {
+               return $GLOBALS['TYPO3_DB'];
+       }
+
 }
index 947ca47..70ff4e4 100644 (file)
@@ -179,6 +179,7 @@ return array(
                        ),
                        'default' => 0
                ),
+               // @deprecated not used anymore
                'edit_notification_mode' => array(
                        'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.edit_notification_mode',
                        'config' => array(
@@ -191,8 +192,8 @@ return array(
                        )
                ),
                'edit_notification_defaults' => array(
-                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.edit_notification_defaults',
-                       'displayCond' => 'FIELD:edit_notification_mode:IN:0,1',
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace_stage.notification_defaults',
+                       'displayCond' => 'FIELD:edit_allow_notificaton_settings:BIT:1',
                        'config' => array(
                                'type' => 'group',
                                'internal_type' => 'db',
@@ -210,12 +211,31 @@ return array(
                        )
                ),
                'edit_allow_notificaton_settings' => array(
-                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.edit_allow_notificaton_settings',
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog',
                        'config' => array(
                                'type' => 'check',
-                               'default' => 1
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.showDialog', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.changeablePreselection', ''),
+                               ),
+                               'default' => 3,
+                               'cols' => 2,
+                       )
+               ),
+               'edit_notification_preselection' => array(
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection',
+                       'config' => array(
+                               'type' => 'check',
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.owners', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.members', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.editors', ''),
+                               ),
+                               'default' => 2,
+                               'cols' => 3,
                        )
                ),
+               // @deprecated not used anymore
                'publish_notification_mode' => array(
                        'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.publish_notification_mode',
                        'config' => array(
@@ -228,8 +248,8 @@ return array(
                        )
                ),
                'publish_notification_defaults' => array(
-                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.publish_notification_defaults',
-                       'displayCond' => 'FIELD:publish_notification_mode:IN:0,1',
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace_stage.notification_defaults',
+                       'displayCond' => 'FIELD:publish_allow_notificaton_settings:BIT:1',
                        'config' => array(
                                'type' => 'group',
                                'internal_type' => 'db',
@@ -247,17 +267,96 @@ return array(
                        )
                ),
                'publish_allow_notificaton_settings' => array(
-                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.publish_allow_notificaton_settings',
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog',
+                       'config' => array(
+                               'type' => 'check',
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.showDialog', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.changeablePreselection', ''),
+                               ),
+                               'default' => 3,
+                               'cols' => 2,
+                       )
+               ),
+               'publish_notification_preselection' => array(
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection',
+                       'config' => array(
+                               'type' => 'check',
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.owners', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.members', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.editors', ''),
+                               ),
+                               'default' => 1,
+                               'cols' => 3,
+                       )
+               ),
+               'execute_notification_defaults' => array(
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace_stage.notification_defaults',
+                       'displayCond' => 'FIELD:execute_allow_notificaton_settings:BIT:1',
+                       'config' => array(
+                               'type' => 'group',
+                               'internal_type' => 'db',
+                               'allowed' => 'be_users,be_groups',
+                               'prepend_tname' => 1,
+                               'size' => '3',
+                               'maxitems' => '100',
+                               'autoSizeMax' => 20,
+                               'show_thumbs' => '1',
+                               'wizards' => array(
+                                       'suggest' => array(
+                                               'type' => 'suggest'
+                                       )
+                               )
+                       )
+               ),
+               'execute_allow_notificaton_settings' => array(
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog',
                        'config' => array(
                                'type' => 'check',
-                               'default' => 1
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.showDialog', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.changeablePreselection', ''),
+                               ),
+                               'default' => 3,
+                               'cols' => 2,
+                       )
+               ),
+               'execute_notification_preselection' => array(
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection',
+                       'config' => array(
+                               'type' => 'check',
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.owners', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.members', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.editors', ''),
+                               ),
+                               'default' => 3,
+                               'cols' => 3,
                        )
                )
        ),
+       'palettes' => array(
+               'stage.edit' => array(
+                       'canNotCollapse' => TRUE,
+                       'showitem' => 'edit_allow_notificaton_settings, edit_notification_preselection,',
+               ),
+               'stage.publish' => array(
+                       'canNotCollapse' => TRUE,
+                       'showitem' => 'publish_allow_notificaton_settings, publish_notification_preselection,',
+               ),
+               'stage.execute' => array(
+                       'canNotCollapse' => TRUE,
+                       'showitem' => 'execute_allow_notificaton_settings, execute_notification_preselection,',
+               )
+       ),
        'types' => array(
                '0' => array('showitem' => 'title,description,
                        --div--;LLL:EXT:lang/locallang_tca.xlf:sys_filemounts.tabs.users,adminusers,members,
-                       --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:tabs.notification_settings,stagechg_notification,edit_notification_mode,edit_notification_defaults,edit_allow_notificaton_settings,publish_notification_mode,publish_notification_defaults,publish_allow_notificaton_settings,
+                       --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:tabs.notification_settings, stagechg_notification,
+                               --palette--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.palette.stage.edit;stage.edit, edit_notification_defaults,
+                               --palette--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.palette.stage.publish;stage.publish, publish_notification_defaults,
+                               --palette--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xml:sys_workspace.palette.stage.execute;stage.execute, execute_notification_defaults,
                        --div--;LLL:EXT:lang/locallang_tca.xlf:sys_filemounts.tabs.mountpoints,db_mountpoints,file_mountpoints,
                        --div--;LLL:EXT:lang/locallang_tca.xlf:sys_filemounts.tabs.publishing,publish_time,unpublish_time,
                        --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_filemounts.tabs.staging,custom_stages,
index fd4813c..ebc9c76 100644 (file)
@@ -78,7 +78,7 @@ return array(
                ),
                'notification_defaults' => array(
                        'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace_stage.notification_defaults',
-                       'displayCond' => 'FIELD:notification_mode:IN:0,1',
+                       'displayCond' => 'FIELD:allow_notificaton_settings:BIT:1',
                        'config' => array(
                                'type' => 'group',
                                'internal_type' => 'db',
@@ -96,16 +96,41 @@ return array(
                        )
                ),
                'allow_notificaton_settings' => array(
-                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace_stage.allow_notificaton_settings',
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog',
                        'config' => array(
                                'type' => 'check',
-                               'default' => 1
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.showDialog', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.settingsDialog.changeablePreselection', ''),
+                               ),
+                               'default' => 3,
+                               'cols' => 2,
+                       )
+               ),
+               'notification_preselection' => array(
+                       'label' => 'LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection',
+                       'config' => array(
+                               'type' => 'check',
+                               'items' => array(
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.owners', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.members', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.editors', ''),
+                                       array('LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:sys_workspace.preselection.responsiblePersons', ''),
+                               ),
+                               'default' => 8,
+                               'cols' => 4,
                        )
                )
        ),
+       'palettes' => array(
+               'stage' => array(
+                       'canNotCollapse' => TRUE,
+                       'showitem' => 'allow_notificaton_settings, notification_preselection,',
+               )
+       ),
        'types' => array(
                '0' => array('showitem' => '
                        --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:tabs.general,title,responsible_persons,
-                       --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:tabs.notification_settings,notification_mode,notification_defaults,allow_notificaton_settings,default_mailcomment')
+                       --div--;LLL:EXT:workspaces/Resources/Private/Language/locallang_db.xlf:tabs.notification_settings,--palette--;;stage, notification_defaults, default_mailcomment')
        )
 );
index cead932..0f6dcb2 100644 (file)
@@ -57,6 +57,9 @@
                        <trans-unit id="tabs.general">
                                <source>General</source>
                        </trans-unit>
+                       <trans-unit id="sys_workspace.execute_notification_defaults">
+                               <source>Publishing execute stage: default notification mail recipients</source>
+                       </trans-unit>
                        <trans-unit id="sys_workspace_stage.notification_mode">
                                <source>Recipient suggestion checkboxes</source>
                        </trans-unit>
                        <trans-unit id="sys_workspace_stage.allow_notificaton_settings">
                                <source>Allow notification settings during stage change</source>
                        </trans-unit>
+                       <trans-unit id="sys_workspace.palette.stage.edit">
+                               <source>Stage "editing":</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.palette.stage.publish">
+                               <source>Stage "ready to publish":</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.palette.stage.execute">
+                               <source>Stage "publishing execute":</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.settingsDialog">
+                               <source>Settings dialog</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.settingsDialog.showDialog">
+                               <source>show dialog</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.settingsDialog.changeablePreselection">
+                               <source>changeable preselection</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.preselection">
+                               <source>Preselection</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.preselection.owners">
+                               <source>owners</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.preselection.members">
+                               <source>members</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.preselection.editors">
+                               <source>editors</source>
+                       </trans-unit>
+                       <trans-unit id="sys_workspace.preselection.responsiblePersons">
+                               <source>responsible persons</source>
+                       </trans-unit>
                </body>
        </file>
 </xliff>
index 25eccdb..13e2ecd 100644 (file)
@@ -172,7 +172,13 @@ TYPO3.Workspaces.Actions = {
                });
        },
        sendToSpecificStageWindow: function(selection, nextStage) {
-               TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageWindow(nextStage, function(response) {
+               var elements = [];
+
+               Ext.each(selection, function(row) {
+                       elements.push({table: row.json.table, uid: row.json.uid})
+               });
+
+               TYPO3.Workspaces.ExtDirectActions.sendToSpecificStageWindow(nextStage, elements, function(response) {
                        TYPO3.Workspaces.Actions.currentSendToMode = 'specific';
                        TYPO3.Workspaces.Actions.sendToStageWindow(response, selection);
                });
index c97bf90..ebc980b 100644 (file)
@@ -24,10 +24,16 @@ CREATE TABLE sys_workspace (
        stagechg_notification tinyint(3) DEFAULT '0' NOT NULL,
        edit_notification_mode tinyint(3) DEFAULT '0' NOT NULL,
        edit_notification_defaults varchar(255) DEFAULT '' NOT NULL,
+       edit_notification_preselection tinyint(3) DEFAULT '3' NOT NULL,
        edit_allow_notificaton_settings tinyint(3) DEFAULT '0' NOT NULL,
        publish_notification_mode tinyint(3) DEFAULT '0' NOT NULL,
        publish_notification_defaults varchar(255) DEFAULT '' NOT NULL,
+       publish_notification_preselection tinyint(3) DEFAULT '3' NOT NULL,
        publish_allow_notificaton_settings tinyint(3) DEFAULT '0' NOT NULL,
+       execute_notification_mode tinyint(3) DEFAULT '0' NOT NULL,
+       execute_notification_defaults varchar(255) DEFAULT '' NOT NULL,
+       execute_notification_preselection tinyint(3) DEFAULT '3' NOT NULL,
+       execute_allow_notificaton_settings tinyint(3) DEFAULT '0' NOT NULL,
 
        PRIMARY KEY (uid),
        KEY parent (pid)
@@ -51,6 +57,7 @@ CREATE TABLE sys_workspace_stage (
        notification_mode tinyint(3) DEFAULT '0' NOT NULL,
        notification_defaults varchar(255) DEFAULT '' NOT NULL,
        allow_notificaton_settings tinyint(3) DEFAULT '0' NOT NULL,
+       notification_preselection tinyint(3) DEFAULT '8' NOT NULL,
 
        PRIMARY KEY (uid),
        KEY parent (pid)