Commit 6a882fff authored by Oliver Hader's avatar Oliver Hader Committed by Oliver Hader
Browse files

[TASK] Avoid inline JavaScript in Scheduler

Schedule task "Table Garbage Collection" uses inline JavaScript
to declare default values for different database tables. Besides
that proper prototype functions instead of closures assigned to
properties are used - which require proper scoping with `bind()`.

Resolves: #95989
Releases: master
Change-Id: I78ff7903716b52c6d625e41237dd6c02c6b9af54
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72185


Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Tested-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Reviewed-by: Oliver Hader's avatarOliver Hader <oliver.hader@typo3.org>
parent 16daefbe
......@@ -24,7 +24,6 @@ import PersistentStorage = require('TYPO3/CMS/Backend/Storage/Persistent');
interface TableNumberMapping {
[s: string]: number;
}
declare let defaultNumberOfDays: TableNumberMapping;
/**
* Module: TYPO3/CMS/Scheduler/Scheduler
......@@ -40,6 +39,14 @@ class Scheduler {
});
}
private static resolveDefaultNumberOfDays(): TableNumberMapping|null {
const element = document.getElementById('task_tableGarbageCollection_numberOfDays');
if (element === null || typeof element.dataset.defaultNumberOfDays === 'undefined') {
return null;
}
return JSON.parse(element.dataset.defaultNumberOfDays) as TableNumberMapping
}
/**
* Store task group collapse state in UC
*/
......@@ -74,7 +81,7 @@ class Scheduler {
* This method reacts on changes to the task class
* It switches on or off the relevant extra fields
*/
public actOnChangedTaskClass = (theSelector: JQuery): void => {
public actOnChangedTaskClass(theSelector: JQuery): void {
let taskClass: string = theSelector.val();
taskClass = taskClass.toLowerCase().replace(/\\/g, '-');
......@@ -87,14 +94,14 @@ class Scheduler {
/**
* This method reacts on changes to the type of a task, i.e. single or recurring
*/
public actOnChangedTaskType = (evt: JQueryEventObject): void => {
public actOnChangedTaskType(evt: JQueryEventObject): void {
this.toggleFieldsByTaskType($(evt.currentTarget).val());
}
/**
* This method reacts on field changes of all table field for table garbage collection task
*/
public actOnChangeSchedulerTableGarbageCollectionAllTables = (theCheckbox: JQuery): void => {
public actOnChangeSchedulerTableGarbageCollectionAllTables(theCheckbox: JQuery): void {
let $numberOfDays = $('#task_tableGarbageCollection_numberOfDays');
let $taskTableGarbageCollectionTable = $('#task_tableGarbageCollection_table');
if (theCheckbox.prop('checked')) {
......@@ -105,7 +112,8 @@ class Scheduler {
let numberOfDays = parseInt($numberOfDays.val(), 10);
if (numberOfDays < 1) {
let selectedTable = $taskTableGarbageCollectionTable.val();
if (typeof(defaultNumberOfDays[selectedTable]) !== 'undefined') {
const defaultNumberOfDays = Scheduler.resolveDefaultNumberOfDays();
if (defaultNumberOfDays !== null) {
numberOfDays = defaultNumberOfDays[selectedTable];
}
}
......@@ -121,9 +129,10 @@ class Scheduler {
* This methods set the 'number of days' field to the default expire period
* of the selected table
*/
public actOnChangeSchedulerTableGarbageCollectionTable = (theSelector: JQuery): void => {
public actOnChangeSchedulerTableGarbageCollectionTable(theSelector: JQuery): void {
let $numberOfDays = $('#task_tableGarbageCollection_numberOfDays');
if (defaultNumberOfDays[theSelector.val()] > 0) {
const defaultNumberOfDays = Scheduler.resolveDefaultNumberOfDays();
if (defaultNumberOfDays !== null && defaultNumberOfDays[theSelector.val()] > 0) {
$numberOfDays.prop('disabled', false);
$numberOfDays.val(defaultNumberOfDays[theSelector.val()]);
} else {
......@@ -135,7 +144,7 @@ class Scheduler {
/**
* Toggle the relevant form fields by task type
*/
public toggleFieldsByTaskType = (taskType: number): void => {
public toggleFieldsByTaskType(taskType: number): void {
// Single task option = 1, Recurring task option = 2
taskType = parseInt(taskType + '', 10);
$('#task_end_col').toggle(taskType === 2);
......@@ -145,12 +154,12 @@ class Scheduler {
/**
* Registers listeners
*/
public initializeEvents = (): void => {
public initializeEvents(): void {
$('#task_class').on('change', (evt: JQueryEventObject): void => {
this.actOnChangedTaskClass($(evt.currentTarget));
});
$('#task_type').on('change', this.actOnChangedTaskType);
$('#task_type').on('change', this.actOnChangedTaskType.bind(this));
$('#task_tableGarbageCollection_allTables').on('change', (evt: JQueryEventObject): void => {
this.actOnChangeSchedulerTableGarbageCollectionAllTables($(evt.currentTarget));
......@@ -183,18 +192,18 @@ class Scheduler {
});
});
new RegularEvent('show.bs.collapse', this.toggleCollapseIcon).bindTo(document);
new RegularEvent('hide.bs.collapse', this.toggleCollapseIcon).bindTo(document);
new RegularEvent('multiRecordSelection:action:go', this.executeTasks).bindTo(document);
new RegularEvent('multiRecordSelection:action:go_cron', this.executeTasks).bindTo(document);
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);
window.addEventListener('message', this.listenOnElementBrowser.bind(this));
}
/**
* Initialize default states
*/
public initializeDefaultStates = (): void => {
public initializeDefaultStates(): void {
let $taskType = $('#task_type');
if ($taskType.length) {
this.toggleFieldsByTaskType($taskType.val());
......@@ -206,7 +215,7 @@ class Scheduler {
}
}
private listenOnElementBrowser = (e: MessageEvent): void => {
private listenOnElementBrowser(e: MessageEvent): void {
if (!MessageUtility.verifyOrigin(e.origin)) {
throw 'Denied message sent by ' + e.origin;
}
......@@ -226,7 +235,7 @@ class Scheduler {
}
}
private toggleCollapseIcon (e: Event): void {
private toggleCollapseIcon(e: Event): void {
const isCollapsed: boolean = e.type === 'hide.bs.collapse';
const collapseIcon: HTMLElement = document.querySelector('.t3js-toggle-table[data-bs-target="#' + (e.target as HTMLElement).id + '"] .collapseIcon');
if (collapseIcon !== null) {
......@@ -239,7 +248,7 @@ class Scheduler {
Scheduler.storeCollapseState($(e.target).data('table'), isCollapsed);
}
private executeTasks (e: CustomEvent): void {
private executeTasks(e: CustomEvent): void {
const form: HTMLFormElement = document.querySelector('#tx_scheduler_form');
if (form === null) {
return;
......@@ -268,7 +277,7 @@ class Scheduler {
form.submit();
}
};
}
}
export = new Scheduler();
......@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Scheduler\Task;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
......@@ -130,9 +131,6 @@ class TableGarbageCollectionAdditionalFieldProvider extends AbstractAdditionalFi
// Add table drop down html
$fieldHtml[] = '<select class="form-select" name="' . $fieldName . '"' . $disabled . ' id="' . $fieldId . '">' . implode(LF, $options) . '</select>';
// Add js array for default 'number of days' values
$fieldHtml[] = '<script>/*<![CDATA[*/<!--';
$fieldHtml[] = 'var defaultNumberOfDays = ' . json_encode($this->defaultNumberOfDays) . ';';
$fieldHtml[] = '// -->/*]]>*/</script>';
$fieldConfiguration = [
'code' => implode(LF, $fieldHtml),
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.table',
......@@ -167,12 +165,21 @@ class TableGarbageCollectionAdditionalFieldProvider extends AbstractAdditionalFi
}
}
}
if ($task && $task->allTables === true) {
$disabled = ' disabled="disabled"';
}
$fieldName = 'tx_scheduler[scheduler_tableGarbageCollection_numberOfDays]';
$fieldId = 'task_tableGarbageCollection_numberOfDays';
$fieldHtml = '<input class="form-control" type="text" name="' . $fieldName . '" id="' . $fieldId . '"' . $disabled . ' value="' . (int)$taskInfo['scheduler_tableGarbageCollection_numberOfDays'] . '" size="4">';
$attrs = [
'class' => 'form-control',
'type' => 'text',
'name' => $fieldName,
'id' => $fieldId,
'size' => '4',
'value' => (string)(int)$taskInfo['scheduler_tableGarbageCollection_numberOfDays'],
'data-default-number-of-days' => GeneralUtility::jsonEncodeForHtmlAttribute($this->defaultNumberOfDays, false),
];
if ($task && $task->allTables === true) {
$attrs['disabled'] = 'disabled';
}
$fieldHtml = '<input ' . GeneralUtility::implodeAttributes($attrs, true) . '>';
$fieldConfiguration = [
'code' => $fieldHtml,
'label' => 'LLL:EXT:scheduler/Resources/Private/Language/locallang.xlf:label.tableGarbageCollection.numberOfDays',
......
......@@ -10,4 +10,4 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","TYPO3/CMS/Backend/DocumentSaveActions","TYPO3/CMS/Core/Event/RegularEvent","TYPO3/CMS/Backend/Modal","TYPO3/CMS/Backend/Icons","TYPO3/CMS/Backend/Utility/MessageUtility","TYPO3/CMS/Backend/Storage/Persistent","tablesort"],(function(e,t,a,l,s,n,o,i,d){"use strict";a=__importDefault(a),s=__importDefault(s);class r{constructor(){this.actOnChangedTaskClass=e=>{let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),a.default(".extraFields").hide(),a.default(".extra_fields_"+t).show()},this.actOnChangedTaskType=e=>{this.toggleFieldsByTaskType(a.default(e.currentTarget).val())},this.actOnChangeSchedulerTableGarbageCollectionAllTables=e=>{let t=a.default("#task_tableGarbageCollection_numberOfDays"),l=a.default("#task_tableGarbageCollection_table");if(e.prop("checked"))l.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=l.val();void 0!==defaultNumberOfDays[t]&&(e=defaultNumberOfDays[t])}l.prop("disabled",!1),e>0&&t.prop("disabled",!1)}},this.actOnChangeSchedulerTableGarbageCollectionTable=e=>{let t=a.default("#task_tableGarbageCollection_numberOfDays");defaultNumberOfDays[e.val()]>0?(t.prop("disabled",!1),t.val(defaultNumberOfDays[e.val()])):(t.prop("disabled",!0),t.val(0))},this.toggleFieldsByTaskType=e=>{e=parseInt(e+"",10),a.default("#task_end_col").toggle(2===e),a.default("#task_frequency_row").toggle(2===e)},this.initializeEvents=()=>{a.default("#task_class").on("change",e=>{this.actOnChangedTaskClass(a.default(e.currentTarget))}),a.default("#task_type").on("change",this.actOnChangedTaskType),a.default("#task_tableGarbageCollection_allTables").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables(a.default(e.currentTarget))}),a.default("#task_tableGarbageCollection_table").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionTable(a.default(e.currentTarget))}),a.default("[data-update-task-frequency]").on("change",e=>{const t=a.default(e.currentTarget);a.default("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")});const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),a.default(document).on("click",".t3js-element-browser",e=>{e.preventDefault();const t=e.currentTarget;n.advanced({type:n.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:n.sizes.large})}),new s.default("show.bs.collapse",this.toggleCollapseIcon).bindTo(document),new s.default("hide.bs.collapse",this.toggleCollapseIcon).bindTo(document),new s.default("multiRecordSelection:action:go",this.executeTasks).bindTo(document),new s.default("multiRecordSelection:action:go_cron",this.executeTasks).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser)},this.initializeDefaultStates=()=>{let e=a.default("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=a.default("#task_class");t.length&&(this.actOnChangedTaskClass(t),r.updateElementBrowserTriggers())},this.listenOnElementBrowser=e=>{if(!i.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]}},this.initializeEvents(),this.initializeDefaultStates(),l.getInstance().addPreSubmitCallback(()=>{let e=a.default("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),a.default(".extraFields").appendTo(a.default("#extraFieldsHidden")),a.default(".extra_fields_"+e).appendTo(a.default("#extraFieldsSection"))})}static updateElementBrowserTriggers(){document.querySelectorAll(".t3js-element-browser").forEach(e=>{const t=document.getElementById(e.dataset.triggerFor);e.dataset.params=t.name+"|||pages"})}static storeCollapseState(e,t){let l={};d.isset("moduleData.scheduler")&&(l=d.get("moduleData.scheduler"));const s={};s[e]=t?1:0,a.default.extend(l,s),d.set("moduleData.scheduler",l)}toggleCollapseIcon(e){const t="hide.bs.collapse"===e.type,l=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==l&&o.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",o.sizes.small).done(e=>{l.innerHTML=e}),r.storeCollapseState(a.default(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){const l=document.createElement("input");if(l.setAttribute("type","hidden"),l.setAttribute("name","tx_scheduler[execute]"),l.setAttribute("value",a.join(",")),t.append(l),"multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","go_cron"),e.setAttribute("value","1"),t.append(e)}t.submit()}}}return new r}));
\ No newline at end of file
var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","jquery","TYPO3/CMS/Backend/DocumentSaveActions","TYPO3/CMS/Core/Event/RegularEvent","TYPO3/CMS/Backend/Modal","TYPO3/CMS/Backend/Icons","TYPO3/CMS/Backend/Utility/MessageUtility","TYPO3/CMS/Backend/Storage/Persistent","tablesort"],(function(e,t,a,l,s,n,o,d,r){"use strict";a=__importDefault(a),s=__importDefault(s);class i{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 l={};r.isset("moduleData.scheduler")&&(l=r.get("moduleData.scheduler"));const s={};s[e]=t?1:0,a.default.extend(l,s),r.set("moduleData.scheduler",l)}constructor(){this.initializeEvents(),this.initializeDefaultStates(),l.getInstance().addPreSubmitCallback(()=>{let e=a.default("#task_class").val();e=e.toLowerCase().replace(/\\/g,"-"),a.default(".extraFields").appendTo(a.default("#extraFieldsHidden")),a.default(".extra_fields_"+e).appendTo(a.default("#extraFieldsSection"))})}actOnChangedTaskClass(e){let t=e.val();t=t.toLowerCase().replace(/\\/g,"-"),a.default(".extraFields").hide(),a.default(".extra_fields_"+t).show()}actOnChangedTaskType(e){this.toggleFieldsByTaskType(a.default(e.currentTarget).val())}actOnChangeSchedulerTableGarbageCollectionAllTables(e){let t=a.default("#task_tableGarbageCollection_numberOfDays"),l=a.default("#task_tableGarbageCollection_table");if(e.prop("checked"))l.prop("disabled",!0),t.prop("disabled",!0);else{let e=parseInt(t.val(),10);if(e<1){let t=l.val();const a=i.resolveDefaultNumberOfDays();null!==a&&(e=a[t])}l.prop("disabled",!1),e>0&&t.prop("disabled",!1)}}actOnChangeSchedulerTableGarbageCollectionTable(e){let t=a.default("#task_tableGarbageCollection_numberOfDays");const l=i.resolveDefaultNumberOfDays();null!==l&&l[e.val()]>0?(t.prop("disabled",!1),t.val(l[e.val()])):(t.prop("disabled",!0),t.val(0))}toggleFieldsByTaskType(e){e=parseInt(e+"",10),a.default("#task_end_col").toggle(2===e),a.default("#task_frequency_row").toggle(2===e)}initializeEvents(){a.default("#task_class").on("change",e=>{this.actOnChangedTaskClass(a.default(e.currentTarget))}),a.default("#task_type").on("change",this.actOnChangedTaskType.bind(this)),a.default("#task_tableGarbageCollection_allTables").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionAllTables(a.default(e.currentTarget))}),a.default("#task_tableGarbageCollection_table").on("change",e=>{this.actOnChangeSchedulerTableGarbageCollectionTable(a.default(e.currentTarget))}),a.default("[data-update-task-frequency]").on("change",e=>{const t=a.default(e.currentTarget);a.default("#task_frequency").val(t.val()),t.val(t.attr("value")).trigger("blur")});const e=document.querySelector("table.taskGroup-table");null!==e&&new Tablesort(e),a.default(document).on("click",".t3js-element-browser",e=>{e.preventDefault();const t=e.currentTarget;n.advanced({type:n.types.iframe,content:t.href+"&mode="+t.dataset.mode+"&bparams="+t.dataset.params,size:n.sizes.large})}),new s.default("show.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new s.default("hide.bs.collapse",this.toggleCollapseIcon.bind(this)).bindTo(document),new s.default("multiRecordSelection:action:go",this.executeTasks.bind(this)).bindTo(document),new s.default("multiRecordSelection:action:go_cron",this.executeTasks.bind(this)).bindTo(document),window.addEventListener("message",this.listenOnElementBrowser.bind(this))}initializeDefaultStates(){let e=a.default("#task_type");e.length&&this.toggleFieldsByTaskType(e.val());let t=a.default("#task_class");t.length&&(this.actOnChangedTaskClass(t),i.updateElementBrowserTriggers())}listenOnElementBrowser(e){if(!d.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,l=document.querySelector('.t3js-toggle-table[data-bs-target="#'+e.target.id+'"] .collapseIcon');null!==l&&o.getIcon(t?"actions-view-list-expand":"actions-view-list-collapse",o.sizes.small).done(e=>{l.innerHTML=e}),i.storeCollapseState(a.default(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){const l=document.createElement("input");if(l.setAttribute("type","hidden"),l.setAttribute("name","tx_scheduler[execute]"),l.setAttribute("value",a.join(",")),t.append(l),"multiRecordSelection:action:go_cron"===e.type){const e=document.createElement("input");e.setAttribute("type","hidden"),e.setAttribute("name","go_cron"),e.setAttribute("value","1"),t.append(e)}t.submit()}}}return new i}));
\ 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