749 lines
23 KiB
JavaScript
749 lines
23 KiB
JavaScript
function _extends() {
|
|
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i];
|
|
for (var key in source) {
|
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
};
|
|
return _extends.apply(this, arguments);
|
|
}
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap util/sanitizer.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
// js-docs-end allow-list
|
|
|
|
const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']);
|
|
|
|
/**
|
|
* A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
|
|
* contexts.
|
|
*
|
|
* Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38
|
|
*/
|
|
// eslint-disable-next-line unicorn/better-regex
|
|
const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i;
|
|
const allowedAttribute = (attribute, allowedAttributeList) => {
|
|
const attributeName = attribute.nodeName.toLowerCase();
|
|
if (allowedAttributeList.includes(attributeName)) {
|
|
if (uriAttributes.has(attributeName)) {
|
|
return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if a regular expression validates the attribute.
|
|
return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName));
|
|
};
|
|
function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) {
|
|
if (!unsafeHtml.length) {
|
|
return unsafeHtml;
|
|
}
|
|
if (sanitizeFunction && typeof sanitizeFunction === 'function') {
|
|
return sanitizeFunction(unsafeHtml);
|
|
}
|
|
const domParser = new window.DOMParser();
|
|
const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');
|
|
const elements = [].concat(...createdDocument.body.querySelectorAll('*'));
|
|
for (const element of elements) {
|
|
const elementName = element.nodeName.toLowerCase();
|
|
if (!Object.keys(allowList).includes(elementName)) {
|
|
element.remove();
|
|
continue;
|
|
}
|
|
const attributeList = [].concat(...element.attributes);
|
|
const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []);
|
|
for (const attribute of attributeList) {
|
|
if (!allowedAttribute(attribute, allowedAttributes)) {
|
|
element.removeAttribute(attribute.nodeName);
|
|
}
|
|
}
|
|
}
|
|
return createdDocument.body.innerHTML;
|
|
}
|
|
|
|
const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
|
|
const DATA_ATTRIBUTE_PATTERN = /^data-[\w-]*$/i;
|
|
const DefaultAllowlist = {
|
|
// Global attributes allowed on any supplied element below.
|
|
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN, DATA_ATTRIBUTE_PATTERN],
|
|
a: ['target', 'href', 'title', 'rel'],
|
|
area: [],
|
|
b: [],
|
|
br: [],
|
|
col: [],
|
|
code: [],
|
|
div: [],
|
|
em: [],
|
|
hr: [],
|
|
h1: [],
|
|
h2: [],
|
|
h3: [],
|
|
h4: [],
|
|
h5: [],
|
|
h6: [],
|
|
i: [],
|
|
img: ['src', 'srcset', 'alt', 'title', 'width', 'height'],
|
|
li: [],
|
|
ol: [],
|
|
p: [],
|
|
pre: [],
|
|
s: [],
|
|
small: [],
|
|
span: [],
|
|
sub: [],
|
|
sup: [],
|
|
strong: [],
|
|
u: [],
|
|
ul: [],
|
|
button: ['type'],
|
|
input: ['accept', 'alt', 'autocomplete', 'autofocus', 'capture', 'checked', 'dirname', 'disabled', 'height', 'list', 'max', 'maxlength', 'min', 'minlength', 'multiple', 'type', 'name', 'pattern', 'placeholder', 'readonly', 'required', 'size', 'src', 'step', 'value', 'width', 'inputmode'],
|
|
select: ['name'],
|
|
textarea: ['name'],
|
|
option: ['value', 'selected']
|
|
};
|
|
|
|
// Only define the Joomla namespace if not defined.
|
|
window.Joomla = window.Joomla || {};
|
|
window.Joomla.Modal = window.Joomla.Modal || {
|
|
/**
|
|
* *****************************************************************
|
|
* Modals should implement
|
|
* *****************************************************************
|
|
*
|
|
* getCurrent Type Function Should return the modal element
|
|
* setCurrent Type Function Should set the modal element
|
|
* current Type {node} The modal element
|
|
*
|
|
* USAGE (assuming that exampleId is the modal id)
|
|
* To get the current modal element:
|
|
* Joomla.Modal.getCurrent(); // Returns node element, eg: document.getElementById('exampleId')
|
|
* To set the current modal element:
|
|
* Joomla.Modal.setCurrent(document.getElementById('exampleId'));
|
|
*
|
|
* *************************************************************
|
|
* Joomla's UI modal uses `element.close();` to close the modal
|
|
* and `element.open();` to open the modal
|
|
* If you are using another modal make sure the same
|
|
* functionality is bound to the modal element
|
|
* @see media/legacy/bootstrap.init.js
|
|
* *************************************************************
|
|
*/
|
|
current: '',
|
|
setCurrent: element => {
|
|
window.Joomla.Modal.current = element;
|
|
},
|
|
getCurrent: () => window.Joomla.Modal.current
|
|
};
|
|
(Joomla => {
|
|
|
|
/**
|
|
* Method to Extend Objects
|
|
*
|
|
* @param {Object} destination
|
|
* @param {Object} source
|
|
*
|
|
* @return Object
|
|
*/
|
|
Joomla.extend = (destination, source) => {
|
|
let newDestination = destination;
|
|
/**
|
|
* Technically null is an object, but trying to treat the destination as one in this
|
|
* context will error out.
|
|
* So emulate jQuery.extend(), and treat a destination null as an empty object.
|
|
*/
|
|
if (destination === null) {
|
|
newDestination = {};
|
|
}
|
|
[].slice.call(Object.keys(source)).forEach(key => {
|
|
newDestination[key] = source[key];
|
|
});
|
|
return destination;
|
|
};
|
|
|
|
/**
|
|
* Joomla options storage
|
|
*
|
|
* @type {{}}
|
|
*
|
|
* @since 3.7.0
|
|
*/
|
|
Joomla.optionsStorage = Joomla.optionsStorage || null;
|
|
|
|
/**
|
|
* Get script(s) options
|
|
*
|
|
* @param {String} key Name in Storage
|
|
* @param {mixed} def Default value if nothing found
|
|
*
|
|
* @return {mixed}
|
|
*
|
|
* @since 3.7.0
|
|
*/
|
|
Joomla.getOptions = (key, def) => {
|
|
// Load options if they not exists
|
|
if (!Joomla.optionsStorage) {
|
|
Joomla.loadOptions();
|
|
}
|
|
return Joomla.optionsStorage[key] !== undefined ? Joomla.optionsStorage[key] : def;
|
|
};
|
|
|
|
/**
|
|
* Load new options from given options object or from Element
|
|
*
|
|
* @param {Object|undefined} options The options object to load.
|
|
* Eg {"com_foobar" : {"option1": 1, "option2": 2}}
|
|
*
|
|
* @since 3.7.0
|
|
*/
|
|
Joomla.loadOptions = options => {
|
|
// Load form the script container
|
|
if (!options) {
|
|
const elements = [].slice.call(document.querySelectorAll('.joomla-script-options.new'));
|
|
let counter = 0;
|
|
elements.forEach(element => {
|
|
const str = element.text || element.textContent;
|
|
const option = JSON.parse(str);
|
|
if (option) {
|
|
Joomla.loadOptions(option);
|
|
counter += 1;
|
|
}
|
|
element.className = element.className.replace(' new', ' loaded');
|
|
});
|
|
if (counter) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Initial loading
|
|
if (!Joomla.optionsStorage) {
|
|
Joomla.optionsStorage = options || {};
|
|
} else if (options) {
|
|
// Merge with existing
|
|
[].slice.call(Object.keys(options)).forEach(key => {
|
|
/**
|
|
* If both existing and new options are objects, merge them with Joomla.extend().
|
|
* But test for new option being null, as null is an object, but we want to allow
|
|
* clearing of options with ...
|
|
*
|
|
* Joomla.loadOptions({'joomla.jtext': null});
|
|
*/
|
|
if (options[key] !== null && typeof Joomla.optionsStorage[key] === 'object' && typeof options[key] === 'object') {
|
|
Joomla.optionsStorage[key] = Joomla.extend(Joomla.optionsStorage[key], options[key]);
|
|
} else {
|
|
Joomla.optionsStorage[key] = options[key];
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Custom behavior for JavaScript I18N in Joomla! 1.6
|
|
*
|
|
* @type {{}}
|
|
*
|
|
* Allows you to call Joomla.Text._() to get a translated JavaScript string
|
|
* pushed in with Text::script() in Joomla.
|
|
*/
|
|
Joomla.Text = {
|
|
strings: {},
|
|
/**
|
|
* Translates a string into the current language.
|
|
*
|
|
* @param {String} key The string to translate
|
|
* @param {String} def Default string
|
|
*
|
|
* @returns {String}
|
|
*/
|
|
_: (key, def) => {
|
|
let newKey = key;
|
|
let newDef = def;
|
|
// Check for new strings in the optionsStorage, and load them
|
|
const newStrings = Joomla.getOptions('joomla.jtext');
|
|
if (newStrings) {
|
|
Joomla.Text.load(newStrings);
|
|
|
|
// Clean up the optionsStorage from useless data
|
|
Joomla.loadOptions({
|
|
'joomla.jtext': null
|
|
});
|
|
}
|
|
newDef = newDef === undefined ? newKey : newDef;
|
|
newKey = newKey.toUpperCase();
|
|
return Joomla.Text.strings[newKey] !== undefined ? Joomla.Text.strings[newKey] : newDef;
|
|
},
|
|
/**
|
|
* Load new strings in to Joomla.Text
|
|
*
|
|
* @param {Object} object Object with new strings
|
|
* @returns {Joomla.Text}
|
|
*/
|
|
load: object => {
|
|
[].slice.call(Object.keys(object)).forEach(key => {
|
|
Joomla.Text.strings[key.toUpperCase()] = object[key];
|
|
});
|
|
return Joomla.Text;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* For B/C we still support Joomla.JText
|
|
*
|
|
* @type {{}}
|
|
*
|
|
* @deprecated 4.0 will be removed in 6.0
|
|
* Example: Joomla.Text._('...');
|
|
* Joomla.Text.load(...);
|
|
*/
|
|
Joomla.JText = Joomla.Text;
|
|
|
|
/**
|
|
* Generic submit form
|
|
*
|
|
* @param {String} task The given task
|
|
* @param {node} form The form element
|
|
* @param {bool} validate The form element
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
Joomla.submitform = (task, form, validate) => {
|
|
let newForm = form;
|
|
const newTask = task;
|
|
if (!newForm) {
|
|
newForm = document.getElementById('adminForm');
|
|
}
|
|
if (newTask) {
|
|
newForm.task.value = newTask;
|
|
}
|
|
|
|
// Toggle HTML5 validation
|
|
newForm.noValidate = !validate;
|
|
if (!validate) {
|
|
newForm.setAttribute('novalidate', '');
|
|
} else if (newForm.hasAttribute('novalidate')) {
|
|
newForm.removeAttribute('novalidate');
|
|
}
|
|
|
|
// Submit the form.
|
|
// Create the input type="submit"
|
|
const button = document.createElement('input');
|
|
button.classList.add('hidden');
|
|
button.type = 'submit';
|
|
|
|
// Append it and click it
|
|
newForm.appendChild(button).click();
|
|
|
|
// If "submit" was prevented, make sure we don't get a build up of buttons
|
|
newForm.removeChild(button);
|
|
};
|
|
|
|
/**
|
|
* Default function. Can be overridden by the component to add custom logic
|
|
*
|
|
* @param {String} task The given task
|
|
* @param {String} formSelector The form selector eg '#adminForm'
|
|
* @param {bool} validate The form element
|
|
*
|
|
* @returns {void}
|
|
*/
|
|
Joomla.submitbutton = (task, formSelector, validate) => {
|
|
let form = document.querySelector(formSelector || 'form.form-validate');
|
|
let newValidate = validate;
|
|
if (typeof formSelector === 'string' && form === null) {
|
|
form = document.querySelector(`#${formSelector}`);
|
|
}
|
|
if (form) {
|
|
if (newValidate === undefined || newValidate === null) {
|
|
const pressbutton = task.split('.');
|
|
let cancelTask = form.getAttribute('data-cancel-task');
|
|
if (!cancelTask) {
|
|
cancelTask = `${pressbutton[0]}.cancel`;
|
|
}
|
|
newValidate = task !== cancelTask;
|
|
}
|
|
if (!newValidate || document.formvalidator.isValid(form)) {
|
|
Joomla.submitform(task, form);
|
|
}
|
|
} else {
|
|
Joomla.submitform(task);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* USED IN: all list forms.
|
|
*
|
|
* Toggles the check state of a group of boxes
|
|
*
|
|
* Checkboxes must have an id attribute in the form cb0, cb1...
|
|
*
|
|
* @param {mixed} checkbox The number of box to 'check', for a checkbox element
|
|
* @param {string} stub An alternative field name
|
|
*
|
|
* @return {boolean}
|
|
*/
|
|
Joomla.checkAll = (checkbox, stub) => {
|
|
if (!checkbox.form) {
|
|
return false;
|
|
}
|
|
const currentStab = stub || 'cb';
|
|
const elements = [].slice.call(checkbox.form.elements);
|
|
let state = 0;
|
|
elements.forEach(element => {
|
|
if (element.type === checkbox.type && element.id.indexOf(currentStab) === 0) {
|
|
element.checked = checkbox.checked;
|
|
state += element.checked ? 1 : 0;
|
|
}
|
|
});
|
|
if (checkbox.form.boxchecked) {
|
|
checkbox.form.boxchecked.value = state;
|
|
checkbox.form.boxchecked.dispatchEvent(new CustomEvent('change', {
|
|
bubbles: true,
|
|
cancelable: true
|
|
}));
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* USED IN: administrator/components/com_cache/views/cache/tmpl/default.php
|
|
* administrator/components/com_installer/views/discover/tmpl/default_item.php
|
|
* administrator/components/com_installer/views/update/tmpl/default_item.php
|
|
* administrator/components/com_languages/helpers/html/languages.php
|
|
* libraries/joomla/html/html/grid.php
|
|
*
|
|
* @param {boolean} isitchecked Flag for checked
|
|
* @param {node} form The form
|
|
*
|
|
* @return {void}
|
|
*/
|
|
Joomla.isChecked = (isitchecked, form) => {
|
|
let newForm = form;
|
|
if (typeof newForm === 'undefined') {
|
|
newForm = document.getElementById('adminForm');
|
|
} else if (typeof form === 'string') {
|
|
newForm = document.getElementById(form);
|
|
}
|
|
newForm.boxchecked.value = isitchecked ? parseInt(newForm.boxchecked.value, 10) + 1 : parseInt(newForm.boxchecked.value, 10) - 1;
|
|
newForm.boxchecked.dispatchEvent(new CustomEvent('change', {
|
|
bubbles: true,
|
|
cancelable: true
|
|
}));
|
|
|
|
// If we don't have a checkall-toggle, done.
|
|
if (!newForm.elements['checkall-toggle']) {
|
|
return;
|
|
}
|
|
|
|
// Toggle main toggle checkbox depending on checkbox selection
|
|
let c = true;
|
|
let i;
|
|
let e;
|
|
let n;
|
|
|
|
// eslint-disable-next-line no-plusplus
|
|
for (i = 0, n = newForm.elements.length; i < n; i++) {
|
|
e = newForm.elements[i];
|
|
if (e.type === 'checkbox' && e.name !== 'checkall-toggle' && !e.checked) {
|
|
c = false;
|
|
break;
|
|
}
|
|
}
|
|
newForm.elements['checkall-toggle'].checked = c;
|
|
};
|
|
|
|
/**
|
|
* USED IN: libraries/joomla/html/html/grid.php
|
|
* In other words, on any reorderable table
|
|
*
|
|
* @param {string} order The order value
|
|
* @param {string} dir The direction
|
|
* @param {string} task The task
|
|
* @param {node} form The form
|
|
*
|
|
* return {void}
|
|
*/
|
|
Joomla.tableOrdering = (order, dir, task, form) => {
|
|
let newForm = form;
|
|
if (typeof newForm === 'undefined') {
|
|
newForm = document.getElementById('adminForm');
|
|
} else if (typeof form === 'string') {
|
|
newForm = document.getElementById(form);
|
|
}
|
|
newForm.filter_order.value = order;
|
|
newForm.filter_order_Dir.value = dir;
|
|
Joomla.submitform(task, newForm);
|
|
};
|
|
|
|
/**
|
|
* USED IN: all over :)
|
|
*
|
|
* @param {string} id The id
|
|
* @param {string} task The task
|
|
* @param {string} form The optional form
|
|
*
|
|
* @return {boolean}
|
|
*/
|
|
Joomla.listItemTask = (id, task, form = null) => {
|
|
let newForm = form;
|
|
if (form !== null) {
|
|
newForm = document.getElementById(form);
|
|
} else {
|
|
newForm = document.adminForm;
|
|
}
|
|
const cb = newForm[id];
|
|
let i = 0;
|
|
let cbx;
|
|
if (!cb) {
|
|
return false;
|
|
}
|
|
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
cbx = newForm[`cb${i}`];
|
|
if (!cbx) {
|
|
break;
|
|
}
|
|
cbx.checked = false;
|
|
i += 1;
|
|
}
|
|
cb.checked = true;
|
|
newForm.boxchecked.value = 1;
|
|
Joomla.submitform(task, newForm);
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Method to replace all request tokens on the page with a new one.
|
|
*
|
|
* @param {String} newToken The token
|
|
*
|
|
* Used in Joomla Installation
|
|
*/
|
|
Joomla.replaceTokens = newToken => {
|
|
if (!/^[0-9A-F]{32}$/i.test(newToken)) {
|
|
return;
|
|
}
|
|
const elements = [].slice.call(document.getElementsByTagName('input'));
|
|
elements.forEach(element => {
|
|
if (element.type === 'hidden' && element.value === '1' && element.name.length === 32) {
|
|
element.name = newToken;
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Method to perform AJAX request
|
|
*
|
|
* @param {Object} options Request options:
|
|
* {
|
|
* url: 'index.php', Request URL
|
|
* method: 'GET', Request method GET (default), POST
|
|
* data: null, Data to be sent, see
|
|
* https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/send
|
|
* perform: true, Perform the request immediately
|
|
* or return XMLHttpRequest instance and perform it later
|
|
* headers: null, Object of custom headers, eg {'X-Foo': 'Bar', 'X-Bar': 'Foo'}
|
|
* promise: false Whether return a Promise instance.
|
|
* When true then next options is ignored: perform, onSuccess, onError, onComplete
|
|
*
|
|
* onBefore: (xhr) => {} // Callback on before the request
|
|
* onSuccess: (response, xhr) => {}, // Callback on the request success
|
|
* onError: (xhr) => {}, // Callback on the request error
|
|
* onComplete: (xhr) => {}, // Callback on the request completed, with/without error
|
|
* }
|
|
*
|
|
* @return XMLHttpRequest|Boolean
|
|
*
|
|
* @example
|
|
*
|
|
* Joomla.request({
|
|
* url: 'index.php?option=com_example&view=example',
|
|
* onSuccess: (response, xhr) => {
|
|
* JSON.parse(response);
|
|
* }
|
|
* })
|
|
*
|
|
* @see https://developer.mozilla.org/docs/Web/API/XMLHttpRequest
|
|
*/
|
|
Joomla.request = options => {
|
|
// Prepare the options
|
|
const newOptions = Joomla.extend({
|
|
url: '',
|
|
method: 'GET',
|
|
data: null,
|
|
perform: true,
|
|
promise: false
|
|
}, options);
|
|
|
|
// Setup XMLHttpRequest instance
|
|
const createRequest = (onSuccess, onError) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open(newOptions.method, newOptions.url, true);
|
|
|
|
// Set the headers
|
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
xhr.setRequestHeader('X-Ajax-Engine', 'Joomla!');
|
|
if (newOptions.method !== 'GET') {
|
|
const token = Joomla.getOptions('csrf.token', '');
|
|
|
|
// Use the CSRF only on the site's domain
|
|
if (token && (!newOptions.url.startsWith('http:') && !newOptions.url.startsWith('https:') || newOptions.url.startsWith(window.location.origin))) {
|
|
xhr.setRequestHeader('X-CSRF-Token', token);
|
|
}
|
|
if (typeof newOptions.data === 'string' && (!newOptions.headers || !newOptions.headers['Content-Type'])) {
|
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
|
}
|
|
}
|
|
|
|
// Custom headers
|
|
if (newOptions.headers) {
|
|
[].slice.call(Object.keys(newOptions.headers)).forEach(key => {
|
|
// Allow request without Content-Type
|
|
// eslint-disable-next-line no-empty
|
|
if (key === 'Content-Type' && newOptions.headers['Content-Type'] === 'false') ; else {
|
|
xhr.setRequestHeader(key, newOptions.headers[key]);
|
|
}
|
|
});
|
|
}
|
|
xhr.onreadystatechange = () => {
|
|
// Request not finished
|
|
if (xhr.readyState !== 4) {
|
|
return;
|
|
}
|
|
|
|
// Request finished and response is ready
|
|
if (xhr.status === 200) {
|
|
if (newOptions.promise) {
|
|
// A Promise accepts only one argument
|
|
onSuccess.call(window, xhr);
|
|
} else {
|
|
onSuccess.call(window, xhr.responseText, xhr);
|
|
}
|
|
} else {
|
|
onError.call(window, xhr);
|
|
}
|
|
if (newOptions.onComplete && !newOptions.promise) {
|
|
newOptions.onComplete.call(window, xhr);
|
|
}
|
|
};
|
|
|
|
// Do request
|
|
if (newOptions.perform) {
|
|
if (newOptions.onBefore && newOptions.onBefore.call(window, xhr) === false) {
|
|
// Request interrupted
|
|
if (newOptions.promise) {
|
|
onSuccess.call(window, xhr);
|
|
}
|
|
return xhr;
|
|
}
|
|
xhr.send(newOptions.data);
|
|
}
|
|
return xhr;
|
|
};
|
|
|
|
// Return a Promise
|
|
if (newOptions.promise) {
|
|
return new Promise((resolve, reject) => {
|
|
newOptions.perform = true;
|
|
createRequest(resolve, reject);
|
|
});
|
|
}
|
|
|
|
// Return a Request
|
|
try {
|
|
return createRequest(newOptions.onSuccess || (() => {}), newOptions.onError || (() => {}));
|
|
} catch (error) {
|
|
// eslint-disable-next-line no-unused-expressions,no-console
|
|
console.error(error);
|
|
return false;
|
|
}
|
|
};
|
|
let lastRequestPromise;
|
|
|
|
/**
|
|
* Joomla Request queue.
|
|
*
|
|
* A FIFO queue of requests to execute serially. Used to prevent simultaneous execution of
|
|
* multiple requests against the server which could trigger its Denial of Service protection.
|
|
*
|
|
* @param {object} options Options for Joomla.request()
|
|
* @returns {Promise}
|
|
*/
|
|
Joomla.enqueueRequest = options => {
|
|
if (!options.promise) {
|
|
throw new Error('Joomla.enqueueRequest supports only Joomla.request as Promise');
|
|
}
|
|
if (!lastRequestPromise) {
|
|
lastRequestPromise = Joomla.request(options);
|
|
} else {
|
|
lastRequestPromise = lastRequestPromise.then(() => Joomla.request(options));
|
|
}
|
|
return lastRequestPromise;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param {string} unsafeHtml The html for sanitization
|
|
* @param {object} allowList The list of HTMLElements with an array of allowed attributes
|
|
* @param {function} sanitizeFn A custom sanitization function
|
|
*
|
|
* @return string
|
|
*/
|
|
Joomla.sanitizeHtml = (unsafeHtml, allowList, sanitizeFn) => {
|
|
const allowed = allowList === undefined || allowList === null ? DefaultAllowlist : _extends({}, DefaultAllowlist, allowList);
|
|
return sanitizeHtml(unsafeHtml, allowed, sanitizeFn);
|
|
};
|
|
|
|
/**
|
|
* Treat AJAX errors.
|
|
* Used by some javascripts such as sendtestmail.js and permissions.js
|
|
*
|
|
* @param {object} xhr XHR object.
|
|
* @param {string} textStatus Type of error that occurred.
|
|
* @param {string} error Textual portion of the HTTP status.
|
|
*
|
|
* @return {object} JavaScript object containing the system error message.
|
|
*
|
|
* @since 3.6.0
|
|
*/
|
|
Joomla.ajaxErrorsMessages = (xhr, textStatus) => {
|
|
const msg = {};
|
|
if (textStatus === 'parsererror') {
|
|
// For jQuery jqXHR
|
|
const buf = [];
|
|
|
|
// Html entity encode.
|
|
let encodedJson = xhr.responseText.trim();
|
|
|
|
// eslint-disable-next-line no-plusplus
|
|
for (let i = encodedJson.length - 1; i >= 0; i--) {
|
|
buf.unshift(['&#', encodedJson[i].charCodeAt(), ';'].join(''));
|
|
}
|
|
encodedJson = buf.join('');
|
|
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_PARSE').replace('%s', encodedJson)];
|
|
} else if (textStatus === 'nocontent') {
|
|
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_NO_CONTENT')];
|
|
} else if (textStatus === 'timeout') {
|
|
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_TIMEOUT')];
|
|
} else if (textStatus === 'abort') {
|
|
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_CONNECTION_ABORT')];
|
|
} else if (xhr.responseJSON && xhr.responseJSON.message) {
|
|
// For vanilla XHR
|
|
msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.responseJSON.message}</em>`];
|
|
} else if (xhr.statusText) {
|
|
msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.statusText}</em>`];
|
|
} else {
|
|
msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)];
|
|
}
|
|
return msg;
|
|
};
|
|
})(Joomla);
|