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 index 0000000000000000000000000000000000000000..6d44b39c2627f4bb572eda8da4c46d9779c40274 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-35245-ReworkWorkspaceNotificationSettings.rst @@ -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 index 0000000000000000000000000000000000000000..d79f781cf53af86e495e185b702dba918e8ce037 --- /dev/null +++ b/typo3/sysext/install/Classes/Updates/WorkspacesNotificationSettingsUpdate.php @@ -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; + } + +} diff --git a/typo3/sysext/install/ext_localconf.php b/typo3/sysext/install/ext_localconf.php index 157a657db723830c246ac2696d5f46054e70504e..dfc33610866cd663f37ff20d16c19b0168d7c03d 100644 --- a/typo3/sysext/install/ext_localconf.php +++ b/typo3/sysext/install/ext_localconf.php @@ -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( diff --git a/typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php b/typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php index 142b6e9e1d6d7eb752710d88af9bd62103cb6526..372ebc9fac3a2a5ffc410861c1a9ce13cd694a36 100644 --- a/typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php +++ b/typo3/sysext/workspaces/Classes/Domain/Model/DatabaseRecord.php @@ -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 index 0000000000000000000000000000000000000000..7768aae57cc7576576145d15dc5b4345569f1a62 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Domain/Record/AbstractRecord.php @@ -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 index 0000000000000000000000000000000000000000..b198cbad8e4f44dde67847d9685361a52a449b51 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Domain/Record/StageRecord.php @@ -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 index 0000000000000000000000000000000000000000..b951776cb48314ed0d34c15503fa43fd920ec5f1 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Domain/Record/WorkspaceRecord.php @@ -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; + } + +} diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php b/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php index 526812c32513b84d7951eb20b0e3f51d124fefda..bf7e6730896e28b25b1500ea847c980ce774386f 100644 --- a/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php +++ b/typo3/sysext/workspaces/Classes/ExtDirect/ActionHandler.php @@ -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; } diff --git a/typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php b/typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php index b90617fc15c96ba29615ef34f869d40e383d455c..d50694310cab4f7ac6f2fbefe17079cbce887745 100644 --- a/typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php +++ b/typo3/sysext/workspaces/Classes/ExtDirect/ExtDirectServer.php @@ -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 index 0000000000000000000000000000000000000000..c7b39133939994b4a9952c3b0d007965875d93c2 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Service/RecordService.php @@ -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']; + } + +} diff --git a/typo3/sysext/workspaces/Classes/Service/StagesService.php b/typo3/sysext/workspaces/Classes/Service/StagesService.php index d064c19139fdf577bccddb4e642b2cbfce31b3d8..439999c4071cacff2335aa9515239f3a7e504981 100644 --- a/typo3/sysext/workspaces/Classes/Service/StagesService.php +++ b/typo3/sysext/workspaces/Classes/Service/StagesService.php @@ -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 @@ -40,6 +42,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. * @@ -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()); } /** @@ -733,6 +770,16 @@ 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 */ @@ -740,4 +787,11 @@ class StagesService { return $GLOBALS['BE_USER']; } + /** + * @return \TYPO3\CMS\Core\Database\DatabaseConnection + */ + protected function getDatabaseConnection() { + return $GLOBALS['TYPO3_DB']; + } + } diff --git a/typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php index 947ca4700d28eede16f592c8854ef9651a2fa941..70ff4e409756587683f60ff17036b3fccb69ca8f 100644 --- a/typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php +++ b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace.php @@ -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, diff --git a/typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php index fd4813c0a268e9e1d78ef78ee270206a8c2cebff..ebc9c768c2057d2480aa26c8aaafd48c494b07f8 100644 --- a/typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php +++ b/typo3/sysext/workspaces/Configuration/TCA/sys_workspace_stage.php @@ -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') ) ); diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf b/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf index cead9329d9f74875087eb07433f115c788262d33..0f6dcb266111ac3285294ad60305c5ba57aa7390 100644 --- a/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf +++ b/typo3/sysext/workspaces/Resources/Private/Language/locallang_db.xlf @@ -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> @@ -66,6 +69,39 @@ <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> diff --git a/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js b/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js index 25eccdba405e28d02b6af890cd8228e55b66cf18..13e2ecdbd9352991c862ba19fa72c1f2a7a2f652 100644 --- a/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js +++ b/typo3/sysext/workspaces/Resources/Public/JavaScript/actions.js @@ -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); }); diff --git a/typo3/sysext/workspaces/ext_tables.sql b/typo3/sysext/workspaces/ext_tables.sql index c97bf906683141fd383d5f02e2218aed3b1fa4ee..ebc980bb82235f28c905e766309bed2a2525e920 100644 --- a/typo3/sysext/workspaces/ext_tables.sql +++ b/typo3/sysext/workspaces/ext_tables.sql @@ -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)