Commit a3b3e600 authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Christian Kuhn
Browse files

[TASK] Use ModuleData API in SchedulerModuleController

The ModuleData API, introduced in #96895,
is now used in the LinkValidatorController.

Additionally, collapsing tasks in the special
"No task group" group is now possible again.

Resolves: #96946
Related: #96895
Related: #96574
Releases: main
Change-Id: Ifa7a475ab18a12495748d774ea2a80da852609b7
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73579

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent d585226c
......@@ -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() {
......
......@@ -19,6 +19,7 @@ namespace TYPO3\CMS\Scheduler\Controller;
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 @@ class SchedulerModuleController
// 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 @@ class SchedulerModuleController
// 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 @@ class SchedulerModuleController
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 @@ class SchedulerModuleController
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 @@ class SchedulerModuleController
}
// Render list if no other action kicked in.
return $this->renderListTasksView($view);
return $this->renderListTasksView($view, $moduleData);
}
/**
......@@ -468,6 +462,7 @@ class SchedulerModuleController
$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 @@ class SchedulerModuleController
} 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 @@ class SchedulerModuleController
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 @@ class SchedulerModuleController
/**
* 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 @@ class SchedulerModuleController
'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 @@ class SchedulerModuleController
'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'),
......
......@@ -17,5 +17,8 @@ return [
'target' => SchedulerModuleController::class . '::handleRequest',
],
],
'moduleData' => [
'subModule' => 'scheduler',
],
],
];
......@@ -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
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment