diff --git a/Build/Sources/TypeScript/scheduler/scheduler.ts b/Build/Sources/TypeScript/scheduler/scheduler.ts index 3a8df2d1e7fc..fe02862964b6 100644 --- a/Build/Sources/TypeScript/scheduler/scheduler.ts +++ b/Build/Sources/TypeScript/scheduler/scheduler.ts @@ -54,15 +54,15 @@ class Scheduler { private static storeCollapseState(table: string, isCollapsed: boolean): void { let storedModuleData = {}; - if (PersistentStorage.isset('moduleData.scheduler')) { - storedModuleData = PersistentStorage.get('moduleData.scheduler'); + if (PersistentStorage.isset('moduleData.system_txschedulerM1')) { + storedModuleData = PersistentStorage.get('moduleData.system_txschedulerM1'); } const collapseConfig: any = {}; collapseConfig[table] = isCollapsed ? 1 : 0; $.extend(storedModuleData, collapseConfig); - PersistentStorage.set('moduleData.scheduler', storedModuleData); + PersistentStorage.set('moduleData.system_txschedulerM1', storedModuleData); } constructor() { diff --git a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php index 4630aab682e0..6ecfbe1ae9dc 100644 --- a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php +++ b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php @@ -19,6 +19,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Module\ModuleData; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Template\Components\ButtonBar; use TYPO3\CMS\Backend\Template\ModuleTemplate; @@ -89,18 +90,11 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac // See if action from main module drop down is given, else fetch from user data and update if needed. $backendUser = $this->getBackendUser(); - $storedModuleData = $backendUser->getModuleData('scheduler'); - $requestedSubModule = $queryParams['subModule'] ?? $storedModuleData['subModule'] ?? 'scheduler'; - if (!empty($requestedSubModule) && !in_array($requestedSubModule, ['scheduler', 'info', 'check'], true)) { - // Reset to 'scheduler list view' if stored moduleData or GET was invalid. - $requestedSubModule = 'scheduler'; + $moduleData = $request->getAttribute('moduleData'); + if ($moduleData->clean('subModule', ['scheduler', 'info', 'check'])) { + $backendUser->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray()); } - if (!isset($storedModuleData['subModule']) || $storedModuleData['subModule'] !== $requestedSubModule) { - $storedModuleData['subModule'] = $requestedSubModule; - $backendUser->pushModuleData('scheduler', $storedModuleData); - } - // Don't further fiddle with backend user module data from here on. - unset($storedModuleData); + $requestedSubModule = (string)$moduleData->get('subModule'); // 'info' and 'check' submodules have no other action and can be rendered directly. if ($requestedSubModule === 'info') { @@ -113,26 +107,26 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac // Simple actions from list view. if (!empty($parsedBody['action']['toggleHidden'])) { $this->toggleDisabledFlag($view, (int)$parsedBody['action']['toggleHidden']); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if (!empty($queryParams['action']['delete'])) { // @todo: This should be POST only, but modals on button type="submit" don't trigger and buttons in doc header can't do that, either. // Compare with 'toggleHidden' solution above which has no modal. $this->deleteTask($view, (int)$queryParams['action']['delete']); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if (!empty($queryParams['action']['stop'])) { // @todo: Same as above. $this->stopTask($view, (int)$queryParams['action']['stop']); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if (!empty($parsedBody['execute'])) { $this->executeTasks($view, (string)$parsedBody['execute']); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if (!empty($parsedBody['scheduleCron'])) { $this->scheduleCrons($view, (string)$parsedBody['scheduleCron']); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if (($parsedBody['action'] ?? '') === Action::ADD @@ -148,7 +142,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac return $this->renderAddTaskFormView($view, $request); } if ($parsedBody['CMD'] === 'saveclose') { - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if ($parsedBody['CMD'] === 'save') { return $this->renderEditTaskFormView($view, $request, $newTaskUid); @@ -168,7 +162,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac return $this->renderAddTaskFormView($view, $request); } if ($parsedBody['CMD'] === 'saveclose') { - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if ($parsedBody['CMD'] === 'save') { return $this->renderEditTaskFormView($view, $request); @@ -184,7 +178,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac } // Render list if no other action kicked in. - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } /** @@ -468,6 +462,7 @@ protected function renderEditTaskFormView(ModuleTemplate $view, ServerRequestInt $languageService = $this->getLanguageService(); $registeredClasses = $this->getRegisteredClasses(); $parsedBody = $request->getParsedBody()['tx_scheduler'] ?? []; + $moduleData = $request->getAttribute('moduleData'); $taskUid = (int)($taskUid ?? $request->getQueryParams()['uid'] ?? $parsedBody['uid'] ?? 0); if (empty($taskUid)) { throw new \RuntimeException('No valid task uid given to edit task', 1641720929); @@ -478,13 +473,13 @@ protected function renderEditTaskFormView(ModuleTemplate $view, ServerRequestInt } catch (\OutOfBoundsException $e) { // Task not found - removed meanwhile? $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.taskNotFound'), $taskUid), AbstractMessage::ERROR); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } if (!empty($taskRecord['serialized_executions'])) { // If there's a registered execution, the task should not be edited. May happen if a cron started the task meanwhile. $this->addMessage($view, $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.maynotEditRunningTask'), AbstractMessage::ERROR); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } $task = null; @@ -500,7 +495,7 @@ protected function renderEditTaskFormView(ModuleTemplate $view, ServerRequestInt if ($isInvalidTask || !isset($registeredClasses[$class]) || !$this->scheduler->isValidTaskObject($task)) { // The task object is not valid anymore. Add flash message and go back to list view. $this->addMessage($view, sprintf($languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:msg.invalidTaskClassEdit'), $class), AbstractMessage::ERROR); - return $this->renderListTasksView($view); + return $this->renderListTasksView($view, $moduleData); } $taskExecution = $task->getExecution(); @@ -631,11 +626,10 @@ protected function scheduleCrons(ModuleTemplate $view, string $taskUids): void /** * Assemble display of list of scheduled tasks */ - protected function renderListTasksView(ModuleTemplate $view): ResponseInterface + protected function renderListTasksView(ModuleTemplate $view, ModuleData $moduleData): ResponseInterface { $languageService = $this->getLanguageService(); $registeredClasses = $this->getRegisteredClasses(); - $schedulerModuleData = $this->getBackendUser()->getModuleData('scheduler') ?? []; // Get all registered tasks $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_scheduler_task'); @@ -727,7 +721,7 @@ protected function renderListTasksView(ModuleTemplate $view): ResponseInterface 'tasks' => [], 'groupName' => $row['taskGroupName'], 'groupDescription' => $row['taskGroupDescription'], - 'taskGroupCollapsed' => (bool)($schedulerModuleData['task-group-' . $row['taskGroupId']] ?? false), + 'taskGroupCollapsed' => (bool)($moduleData->get('task-group-' . ($row['taskGroupId'] ?? 0), false)), ]; } $taskGroupsWithTasks[(int)$row['task_group']]['tasks'][] = $taskData; @@ -737,7 +731,7 @@ protected function renderListTasksView(ModuleTemplate $view): ResponseInterface 'tasks' => $taskGroupsWithTasks, 'now' => $this->context->getAspect('date')->get('timestamp'), 'errorClasses' => $errorClasses, - 'errorClassesCollapsed' => (bool)($schedulerModuleData['task-group-missing'] ?? false), + 'errorClassesCollapsed' => (bool)($moduleData->get('task-group-missing', false)), ]); $view->setTitle( $languageService->sL('LLL:EXT:scheduler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'), diff --git a/typo3/sysext/scheduler/Configuration/Backend/Modules.php b/typo3/sysext/scheduler/Configuration/Backend/Modules.php index 3909f49afcf2..016abc5e90f1 100644 --- a/typo3/sysext/scheduler/Configuration/Backend/Modules.php +++ b/typo3/sysext/scheduler/Configuration/Backend/Modules.php @@ -17,5 +17,8 @@ 'target' => SchedulerModuleController::class . '::handleRequest', ], ], + 'moduleData' => [ + 'subModule' => 'scheduler', + ], ], ]; diff --git a/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js b/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js index 13b6e8c8d5fd..4d25b0350831 100644 --- a/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js +++ b/typo3/sysext/scheduler/Resources/Public/JavaScript/scheduler.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -import $ from"jquery";import Tablesort from"tablesort";import DocumentSaveActions from"@typo3/backend/document-save-actions.js";import RegularEvent from"@typo3/core/event/regular-event.js";import Modal from"@typo3/backend/modal.js";import Icons from"@typo3/backend/icons.js";import{MessageUtility}from"@typo3/backend/utility/message-utility.js";import PersistentStorage from"@typo3/backend/storage/persistent.js";import DateTimePicker from"@typo3/backend/date-time-picker.js";class Scheduler{static updateElementBrowserTriggers(){document.querySelectorAll(".t3js-element-browser").forEach(e=>{const t=document.getElementById(e.dataset.triggerFor);e.dataset.params=t.name+"|||pages"})}static resolveDefaultNumberOfDays(){const e=document.getElementById("task_tableGarbageCollection_numberOfDays");return null===e||void 0===e.dataset.defaultNumberOfDays?null:JSON.parse(e.dataset.defaultNumberOfDays)}static storeCollapseState(e,t){let a={};PersistentStorage.isset("moduleData.scheduler")&&(a=PersistentStorage.get("moduleData.scheduler"));const l={};l[e]=t?1:0,$.extend(a,l),PersistentStorage.set("moduleData.scheduler",a)}constructor(){this.initializeEvents(),this.initializeDefaultStates(),DocumentSaveActions.getInstance().addPreSubmitCallback(()=>{let e=$("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),$(".extraFields").appendTo($("#extraFieldsHidden")),$(".extra_fields_"+e).appendTo($("#extraFieldsSection"))})}actOnChangedTaskClass(e){let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),$(".extraFields").hide(),$(".extra_fields_"+t).show()}actOnChangedTaskType(e){this.toggleFieldsByTaskType($(e.currentTarget).val())}actOnChangeSchedulerTableGarbageCollectionAllTables(e){let t=$("#task_tableGarbageCollection_numberOfDays"),a=$("#task_tableGarbageCollection_table");if(e.prop("checked"))a.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=a.val();const l=Scheduler.resolveDefaultNumberOfDays();null!==l&&(e=l[t])}a.prop("disabled",!1),e>0&&t.prop("disabled",!1)}}actOnChangeSchedulerTableGarbageCollectionTable(e){let t=$("#task_tableGarbageCollection_numberOfDays");const a=Scheduler.resolveDefaultNumberOfDays();null!==a&&a[e.val()]>0?(t.prop("disabled",!1),t.val(a[e.val()])):(t.prop("disabled",!0),t.val(0))}toggleFieldsByTaskType(e){e=parseInt(e+"",10),$("#task_end_col").toggle(2===e),$("#task_frequency_row").toggle(2===e)}initializeEvents(){$("#task_class").on("change",e=>{this.actOnChangedTaskClass($(e.currentTarget))}),$("#task_type").on("change",this.actOnChangedTaskType.bind(this)),$("#task_tableGarbageCollection_allTables").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables($(e.currentTarget))}),$("#task_tableGarbageCollection_table").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionTable($(e.currentTarget))}),$("[data-update-task-frequency]").on("change",e=>{const t=$(e.currentTarget);$("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")});const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),document.querySelectorAll("#tx_scheduler_form .t3js-datetimepicker").forEach(e=>DateTimePicker.initialize(e)),$(document).on("click",".t3js-element-browser",e=>{e.preventDefault();const t=e.currentTarget;Modal.advanced({type:Modal.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:Modal.sizes.large})}),new RegularEvent("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser.bind(this))}initializeDefaultStates(){let e=$("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=$("#task_class");t.length&&(this.actOnChangedTaskClass(t),Scheduler.updateElementBrowserTriggers())}listenOnElementBrowser(e){if(!MessageUtility.verifyOrigin(e.origin))throw"Denied message sent by "+e.origin;if("typo3:elementBrowser:elementAdded"===e.data.actionName){if(void 0===e.data.fieldName)throw"fieldName not defined in message";if(void 0===e.data.value)throw"value not defined in message";const t=e.data.value.split("_");document.querySelector('input[name="'+e.data.fieldName+'"]').value=t[1]}}toggleCollapseIcon(e){const t="hide.bs.collapse"===e.type,a=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==a&&Icons.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",Icons.sizes.small).then(e=>{a.innerHTML=e}),Scheduler.storeCollapseState($(e.target).data("table"),t)}executeTasks(e){const t=document.querySelector("#tx_scheduler_form");if(null===t)return;const a=[];if(e.detail.checkboxes.forEach(e=>{const t=e.closest("tr");null!==t&&t.dataset.taskId&&a.push(t.dataset.taskId)}),a.length){if("multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","scheduleCron"),e.setAttribute("value",a.join(",")),t.append(e)}else{const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","execute"),e.setAttribute("value",a.join(",")),t.append(e)}t.submit()}}}export default new Scheduler; \ No newline at end of file +import $ from"jquery";import Tablesort from"tablesort";import DocumentSaveActions from"@typo3/backend/document-save-actions.js";import RegularEvent from"@typo3/core/event/regular-event.js";import Modal from"@typo3/backend/modal.js";import Icons from"@typo3/backend/icons.js";import{MessageUtility}from"@typo3/backend/utility/message-utility.js";import PersistentStorage from"@typo3/backend/storage/persistent.js";import DateTimePicker from"@typo3/backend/date-time-picker.js";class Scheduler{static updateElementBrowserTriggers(){document.querySelectorAll(".t3js-element-browser").forEach(e=>{const t=document.getElementById(e.dataset.triggerFor);e.dataset.params=t.name+"|||pages"})}static resolveDefaultNumberOfDays(){const e=document.getElementById("task_tableGarbageCollection_numberOfDays");return null===e||void 0===e.dataset.defaultNumberOfDays?null:JSON.parse(e.dataset.defaultNumberOfDays)}static storeCollapseState(e,t){let a={};PersistentStorage.isset("moduleData.system_txschedulerM1")&&(a=PersistentStorage.get("moduleData.system_txschedulerM1"));const l={};l[e]=t?1:0,$.extend(a,l),PersistentStorage.set("moduleData.system_txschedulerM1",a)}constructor(){this.initializeEvents(),this.initializeDefaultStates(),DocumentSaveActions.getInstance().addPreSubmitCallback(()=>{let e=$("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),$(".extraFields").appendTo($("#extraFieldsHidden")),$(".extra_fields_"+e).appendTo($("#extraFieldsSection"))})}actOnChangedTaskClass(e){let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),$(".extraFields").hide(),$(".extra_fields_"+t).show()}actOnChangedTaskType(e){this.toggleFieldsByTaskType($(e.currentTarget).val())}actOnChangeSchedulerTableGarbageCollectionAllTables(e){let t=$("#task_tableGarbageCollection_numberOfDays"),a=$("#task_tableGarbageCollection_table");if(e.prop("checked"))a.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=a.val();const l=Scheduler.resolveDefaultNumberOfDays();null!==l&&(e=l[t])}a.prop("disabled",!1),e>0&&t.prop("disabled",!1)}}actOnChangeSchedulerTableGarbageCollectionTable(e){let t=$("#task_tableGarbageCollection_numberOfDays");const a=Scheduler.resolveDefaultNumberOfDays();null!==a&&a[e.val()]>0?(t.prop("disabled",!1),t.val(a[e.val()])):(t.prop("disabled",!0),t.val(0))}toggleFieldsByTaskType(e){e=parseInt(e+"",10),$("#task_end_col").toggle(2===e),$("#task_frequency_row").toggle(2===e)}initializeEvents(){$("#task_class").on("change",e=>{this.actOnChangedTaskClass($(e.currentTarget))}),$("#task_type").on("change",this.actOnChangedTaskType.bind(this)),$("#task_tableGarbageCollection_allTables").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables($(e.currentTarget))}),$("#task_tableGarbageCollection_table").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionTable($(e.currentTarget))}),$("[data-update-task-frequency]").on("change",e=>{const t=$(e.currentTarget);$("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")});const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),document.querySelectorAll("#tx_scheduler_form .t3js-datetimepicker").forEach(e=>DateTimePicker.initialize(e)),$(document).on("click",".t3js-element-browser",e=>{e.preventDefault();const t=e.currentTarget;Modal.advanced({type:Modal.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:Modal.sizes.large})}),new RegularEvent("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new RegularEvent("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser.bind(this))}initializeDefaultStates(){let e=$("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=$("#task_class");t.length&&(this.actOnChangedTaskClass(t),Scheduler.updateElementBrowserTriggers())}listenOnElementBrowser(e){if(!MessageUtility.verifyOrigin(e.origin))throw"Denied message sent by "+e.origin;if("typo3:elementBrowser:elementAdded"===e.data.actionName){if(void 0===e.data.fieldName)throw"fieldName not defined in message";if(void 0===e.data.value)throw"value not defined in message";const t=e.data.value.split("_");document.querySelector('input[name="'+e.data.fieldName+'"]').value=t[1]}}toggleCollapseIcon(e){const t="hide.bs.collapse"===e.type,a=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==a&&Icons.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",Icons.sizes.small).then(e=>{a.innerHTML=e}),Scheduler.storeCollapseState($(e.target).data("table"),t)}executeTasks(e){const t=document.querySelector("#tx_scheduler_form");if(null===t)return;const a=[];if(e.detail.checkboxes.forEach(e=>{const t=e.closest("tr");null!==t&&t.dataset.taskId&&a.push(t.dataset.taskId)}),a.length){if("multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","scheduleCron"),e.setAttribute("value",a.join(",")),t.append(e)}else{const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","execute"),e.setAttribute("value",a.join(",")),t.append(e)}t.submit()}}}export default new Scheduler; \ No newline at end of file