1(function () { 2 'use strict'; 3 4 function _extends() { 5 return _extends = Object.assign ? Object.assign.bind() : function (n) { 6 for (var e = 1; e < arguments.length; e++) { 7 var t = arguments[e]; 8 for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); 9 } 10 return n; 11 }, _extends.apply(null, arguments); 12 } 13 14 /** 15 * -------------------------------------------------------------------------- 16 * Bootstrap util/sanitizer.js 17 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 18 * -------------------------------------------------------------------------- 19 */ 20 21 // js-docs-end allow-list 22 23 const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); 24 25 /** 26 * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation 27 * contexts. 28 * 29 * Shout-out to Angular https://github.com/angular/angular/blob/15.2.8/packages/core/src/sanitization/url_sanitizer.ts#L38 30 */ 31 const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i; 32 const allowedAttribute = (attribute, allowedAttributeList) => { 33 const attributeName = attribute.nodeName.toLowerCase(); 34 if (allowedAttributeList.includes(attributeName)) { 35 if (uriAttributes.has(attributeName)) { 36 return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue)); 37 } 38 return true; 39 } 40 41 // Check if a regular expression validates the attribute. 42 return allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp).some(regex => regex.test(attributeName)); 43 }; 44 function sanitizeHtml(unsafeHtml, allowList, sanitizeFunction) { 45 if (!unsafeHtml.length) { 46 return unsafeHtml; 47 } 48 if (sanitizeFunction && typeof sanitizeFunction === 'function') { 49 return sanitizeFunction(unsafeHtml); 50 } 51 const domParser = new window.DOMParser(); 52 const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); 53 const elements = [].concat(...createdDocument.body.querySelectorAll('*')); 54 for (const element of elements) { 55 const elementName = element.nodeName.toLowerCase(); 56 if (!Object.keys(allowList).includes(elementName)) { 57 element.remove(); 58 continue; 59 } 60 const attributeList = [].concat(...element.attributes); 61 const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); 62 for (const attribute of attributeList) { 63 if (!allowedAttribute(attribute, allowedAttributes)) { 64 element.removeAttribute(attribute.nodeName); 65 } 66 } 67 } 68 return createdDocument.body.innerHTML; 69 } 70 71 /** 72 * @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org> 73 * @license GNU General Public License version 2 or later; see LICENSE.txt 74 */ 75 76 const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; 77 const DATA_ATTRIBUTE_PATTERN = /^data-[\w-]*$/i; 78 const DefaultAllowlist = { 79 // Global attributes allowed on any supplied element below. 80 '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN, DATA_ATTRIBUTE_PATTERN], 81 a: ['target', 'href', 'title', 'rel'], 82 area: [], 83 b: [], 84 br: [], 85 col: [], 86 code: [], 87 div: [], 88 em: [], 89 hr: [], 90 h1: [], 91 h2: [], 92 h3: [], 93 h4: [], 94 h5: [], 95 h6: [], 96 i: [], 97 img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], 98 li: [], 99 ol: [], 100 p: [], 101 pre: [], 102 s: [], 103 small: [], 104 span: [], 105 sub: [], 106 sup: [], 107 strong: [], 108 u: [], 109 ul: [], 110 button: ['type'], 111 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'], 112 select: ['name'], 113 textarea: ['name'], 114 option: ['value', 'selected'], 115 details: ['open'], 116 summary: [] 117 }; 118 119 // Only define the Joomla namespace if not defined. 120 window.Joomla = window.Joomla || {}; 121 122 // Only define editors if not defined 123 Joomla.editors = Joomla.editors || {}; 124 125 // An object to hold each editor instance on page, only define if not defined. 126 Joomla.editors.instances = Joomla.editors.instances || { 127 /** 128 * ***************************************************************** 129 * All Editors MUST register, per instance, the following callbacks: 130 * ***************************************************************** 131 * 132 * getValue Type Function Should return the complete data from the editor
133 * Example: () => { return this.element.value; } 134 * setValue Type Function Should replace the complete data of the editor 135 * Example: (text) => { return this.element.value = text; } 136 * getSelection Type Function Should return the selected text from the editor 137 * Example: function () { return this.selectedText; } 138 * disable Type Function Toggles the editor into disabled mode. When the editor is 139 * active then everything should be usable. When inactive the 140 * editor should be unusable AND disabled for form validation 141 * Example: (bool) => { return this.disable = value; } 142 * replaceSelection Type Function Should replace the selected text of the editor 143 * If nothing selected, will insert the data at the cursor 144 * Example: 145 * (text) => { 146 * return insertAtCursor(this.element, text); 147 * } 148 * 149 * USAGE (assuming that jform_articletext is the textarea id) 150 * { 151 * To get the current editor value: 152 * Joomla.editors.instances['jform_articletext'].getValue(); 153 * To set the current editor value: 154 * Joomla.editors.instances['jform_articletext'].setValue('Joomla! rocks'); 155 * To replace(selection) or insert a value at the current editor cursor (replaces the J3 156 * jInsertEditorText API): 157 * replaceSelection: 158 * Joomla.editors.instances['jform_articletext'].replaceSelection('Joomla! rocks') 159 * } 160 * 161 * *********************************************************
162 * ANY INTERACTION WITH THE EDITORS SHOULD USE THE ABOVE API 163 * ********************************************************* 164 */ 165 }; 166 Joomla.Modal = Joomla.Modal || { 167 /** 168 * ***************************************************************** 169 * Modals should implement 170 * ***************************************************************** 171 * 172 * getCurrent Type Function Should return the modal element 173 * setCurrent Type Function Should set the modal element 174 * current Type {node} The modal element 175 * 176 * USAGE (assuming that exampleId is the modal id) 177 * To get the current modal element: 178 * Joomla.Modal.getCurrent(); // Returns node element, eg: document.getElementById('exampleId') 179 * To set the current modal element: 180 * Joomla.Modal.setCurrent(document.getElementById('exampleId')); 181 * 182 * ************************************************************* 183 * Joomla's UI modal uses `element.close();` to close the modal 184 * and `element.open();` to open the modal 185 * If you are using another modal make sure the same 186 * functionality is bound to the modal element 187 * @see media/legacy/bootstrap.init.js 188 * ************************************************************* 189 */ 190 current: '', 191 setCurrent: element => { 192 Joomla.Modal.current = element; 193 }, 194 getCurrent: () => Joomla.Modal.current 195 }; 196 197 /** 198 * Method to Extend Objects 199 * 200 * @param {Object} destination 201 * @param {Object} source 202 * 203 * @return Object 204 */ 205 Joomla.extend = (destination, source) => { 206 let newDestination = destination; 207 /** 208 * Technically null is an object, but trying to treat the destination as one in this 209 * context will error out. 210 * So emulate jQuery.extend(), and treat a destination null as an empty object. 211 */ 212 if (destination === null) { 213 newDestination = {}; 214 } 215 Object.keys(source).forEach(key => { 216 newDestination[key] = source[key]; 217 }); 218 return destination; 219 }; 220 221 /** 222 * Joomla options storage 223 * 224 * @type {{}} 225 * 226 * @since 3.7.0 227 */ 228 Joomla.optionsStorage = Joomla.optionsStorage || null; 229 230 /** 231 * Get script(s) options 232 * 233 * @param {String} key Name in Storage 234 * @param {mixed} def Default value if nothing found 235 * 236 * @return {mixed} 237 * 238 * @since 3.7.0 239 */ 240 Joomla.getOptions = (key, def) => { 241 // Load options if they not exists 242 if (!Joomla.optionsStorage) { 243 Joomla.loadOptions(); 244 } 245 return Joomla.optionsStorage[key] !== undefined ? Joomla.optionsStorage[key] : def; 246 }; 247 248 /** 249 * Load new options from given options object or from Element 250 * 251 * @param {Object|undefined} options The options object to load. 252 * Eg {"com_foobar" : {"option1": 1, "option2": 2}} 253 * 254 * @since 3.7.0 255 */ 256 Joomla.loadOptions = options => { 257 // Load form the script container 258 if (!options) { 259 let counter = 0; 260 document.querySelectorAll('.joomla-script-options.new').forEach(element => { 261 const str = element.text || element.textContent; 262 const option = JSON.parse(str); 263 if (option) { 264 Joomla.loadOptions(option); 265 counter += 1; 266 } 267 element.className = element.className.replace(' new', ' loaded'); 268 }); 269 if (counter) { 270 return; 271 } 272 } 273 274 // Initial loading 275 if (!Joomla.optionsStorage) { 276 Joomla.optionsStorage = options || {}; 277 } else if (options) { 278 // Merge with existing 279 Object.keys(options).forEach(key => { 280 /** 281 * If both existing and new options are objects, merge them with Joomla.extend(). 282 * But test for new option being null, as null is an object, but we want to allow 283 * clearing of options with ... 284 * 285 * Joomla.loadOptions({'joomla.jtext': null}); 286 */ 287 if (options[key] !== null && typeof Joomla.optionsStorage[key] === 'object' && typeof options[key] === 'object') { 288 Joomla.optionsStorage[key] = Joomla.extend(Joomla.optionsStorage[key], options[key]); 289 } else { 290 Joomla.optionsStorage[key] = options[key]; 291 } 292 }); 293 } 294 }; 295 296 /** 297 * Custom behavior for JavaScript I18N in Joomla! 1.6 298 * 299 * @type {{}} 300 * 301 * Allows you to call Joomla.Text._() to get a translated JavaScript string 302 * pushed in with Text::script() in Joomla. 303 */ 304 Joomla.Text = { 305 strings: {}, 306 /** 307 * Translates a string into the current language. 308 * 309 * @param {String} key The string to translate 310 * @param {String} def Default string 311 * 312 * @returns {String} 313 */ 314 _: (key, def) => { 315 let newKey = key; 316 let newDef = def; 317 // Check for new strings in the optionsStorage, and load them
318 const newStrings = Joomla.getOptions('joomla.jtext'); 319 if (newStrings) { 320 Joomla.Text.load(newStrings); 321 322 // Clean up the optionsStorage from useless data 323 Joomla.loadOptions({ 324 'joomla.jtext': null 325 }); 326 } 327 newDef = newDef === undefined ? newKey : newDef; 328 newKey = newKey.toUpperCase(); 329 return Joomla.Text.strings[newKey] !== undefined ? Joomla.Text.strings[newKey] : newDef; 330 }, 331 /** 332 * Load new strings in to Joomla.Text 333 * 334 * @param {Object} object Object with new strings 335 * @returns {Joomla.Text} 336 */ 337 load: object => { 338 Object.keys(object).forEach(key => { 339 Joomla.Text.strings[key.toUpperCase()] = object[key]; 340 }); 341 return Joomla.Text; 342 } 343 }; 344 345 /** 346 * For B/C we still support Joomla.JText 347 * 348 * @type {{}} 349 * 350 * @deprecated 4.0 will be removed in 6.0 351 * Example: Joomla.Text._('...'); 352 * Joomla.Text.load(...); 353 */ 354 Joomla.JText = Joomla.Text; 355 356 /** 357 * Generic submit form 358 * 359 * @param {String} task The given task 360 * @param {node} form The form element 361 * @param {bool} validate The form element 362 * 363 * @returns {void} 364 */ 365 Joomla.submitform = (task, form, validate) => { 366 let newForm = form; 367 const newTask = task; 368 if (!newForm) { 369 newForm = document.getElementById('adminForm'); 370 } 371 if (newTask) { 372 newForm.task.value = newTask; 373 } 374 375 // Toggle HTML5 validation 376 newForm.noValidate = !validate; 377 if (!validate) { 378 newForm.setAttribute('novalidate', ''); 379 } else if (newForm.hasAttribute('novalidate')) { 380 newForm.removeAttribute('novalidate'); 381 } 382 383 // Submit the form. 384 // Create the input type="submit" 385 const button = document.createElement('input'); 386 button.classList.add('hidden'); 387 button.type = 'submit'; 388 389 // Append it and click it 390 newForm.appendChild(button).click(); 391 392 // If "submit" was prevented, make sure we don't get a build up of buttons 393 newForm.removeChild(button); 394 }; 395 396 /** 397 * Default function. Can be overridden by the component to add custom logic 398 * 399 * @param {String} task The given task 400 * @param {String} formSelector The form selector eg '#adminForm' 401 * @param {bool} validate The form element 402 * 403 * @returns {void} 404 */ 405 Joomla.submitbutton = (task, formSelector, validate) => { 406 let form = document.querySelector(formSelector || 'form.form-validate'); 407 let newValidate = validate; 408 if (typeof formSelector === 'string' && form === null) { 409 form = document.querySelector(`#${formSelector}`); 410 } 411 if (form) { 412 if (newValidate === undefined || newValidate === null) { 413 const pressbutton = task.split('.'); 414 let cancelTask = form.getAttribute('data-cancel-task'); 415 if (!cancelTask) { 416 cancelTask = `${pressbutton[0]}.cancel`; 417 } 418 newValidate = task !== cancelTask; 419 } 420 if (!newValidate || document.formvalidator.isValid(form)) { 421 Joomla.submitform(task, form); 422 } 423 } else { 424 Joomla.submitform(task); 425 } 426 }; 427 428 /** 429 * USED IN: all list forms. 430 * 431 * Toggles the check state of a group of boxes 432 * 433 * Checkboxes must have an id attribute in the form cb0, cb1... 434 * 435 * @param {mixed} checkbox The number of box to 'check', for a checkbox element 436 * @param {string} stub An alternative field name 437 * 438 * @return {boolean} 439 */ 440 Joomla.checkAll = (checkbox, stub) => { 441 if (!checkbox.form) { 442 return false; 443 } 444 const currentStab = stub || 'cb'; 445 const elements = [].slice.call(checkbox.form.elements); 446 let state = 0; 447 elements.forEach(element => { 448 if (element.type === checkbox.type && element.id.indexOf(currentStab) === 0) { 449 element.checked = checkbox.checked; 450 state += element.checked ? 1 : 0; 451 } 452 }); 453 if (checkbox.form.boxchecked) { 454 checkbox.form.boxchecked.value = state; 455 checkbox.form.boxchecked.dispatchEvent(new CustomEvent('change', { 456 bubbles: true, 457 cancelable: true 458 })); 459 } 460 return true; 461 }; 462 463 /** 464 * USED IN: administrator/components/com_cache/views/cache/tmpl/default.php 465 * administrator/components/com_installer/views/discover/tmpl/default_item.php 466 * administrator/components/com_installer/views/update/tmpl/default_item.php 467 * administrator/components/com_languages/helpers/html/languages.php
468 * libraries/joomla/html/html/grid.php 469 * 470 * @param {boolean} isitchecked Flag for checked 471 * @param {node} form The form 472 * 473 * @return {void} 474 */ 475 Joomla.isChecked = (isitchecked, form) => { 476 let newForm = form; 477 if (typeof newForm === 'undefined') { 478 newForm = document.getElementById('adminForm'); 479 } else if (typeof form === 'string') { 480 newForm = document.getElementById(form); 481 } 482 newForm.boxchecked.value = isitchecked ? parseInt(newForm.boxchecked.value, 10) + 1 : parseInt(newForm.boxchecked.value, 10) - 1; 483 newForm.boxchecked.dispatchEvent(new CustomEvent('change', { 484 bubbles: true, 485 cancelable: true 486 })); 487 488 // If we don't have a checkall-toggle, done. 489 if (!newForm.elements['checkall-toggle']) { 490 return; 491 } 492 493 // Toggle main toggle checkbox depending on checkbox selection 494 let c = true; 495 let i; 496 let e; 497 let n; 498 for (i = 0, n = newForm.elements.length; i < n; i++) { 499 e = newForm.elements[i]; 500 if (e.type === 'checkbox' && e.name !== 'checkall-toggle' && !e.checked) { 501 c = false; 502 break; 503 } 504 } 505 newForm.elements['checkall-toggle'].checked = c; 506 }; 507 508 /** 509 * USED IN: libraries/joomla/html/html/grid.php 510 * In other words, on any reorderable table 511 * 512 * @param {string} order The order value 513 * @param {string} dir The direction 514 * @param {string} task The task 515 * @param {node} form The form 516 * 517 * return {void} 518 */ 519 Joomla.tableOrdering = (order, dir, task, form) => { 520 let newForm = form; 521 if (typeof newForm === 'undefined') { 522 newForm = document.getElementById('adminForm'); 523 } else if (typeof form === 'string') { 524 newForm = document.getElementById(form); 525 } 526 newForm.filter_order.value = order; 527 newForm.filter_order_Dir.value = dir; 528 Joomla.submitform(task, newForm); 529 }; 530 531 /** 532 * USED IN: all over :) 533 * 534 * @param {string} id The id 535 * @param {string} task The task 536 * @param {string} form The optional form 537 * 538 * @return {boolean} 539 */ 540 Joomla.listItemTask = (id, task, form = null) => { 541 let newForm = form; 542 if (form !== null) { 543 newForm = document.getElementById(form); 544 } else { 545 newForm = document.adminForm; 546 } 547 const cb = newForm[id]; 548 let i = 0; 549 let cbx; 550 if (!cb) { 551 return false; 552 } 553 while (true) { 554 cbx = newForm[`cb${i}`]; 555 if (!cbx) { 556 break; 557 } 558 cbx.checked = false; 559 i += 1; 560 } 561 cb.checked = true; 562 newForm.boxchecked.value = 1; 563 Joomla.submitform(task, newForm); 564 return false; 565 }; 566 567 /** 568 * Method to replace all request tokens on the page with a new one. 569 * 570 * @param {String} newToken The token 571 * 572 * Used in Joomla Installation 573 */ 574 Joomla.replaceTokens = newToken => { 575 if (!/^[0-9A-F]{32}$/i.test(newToken)) { 576 return; 577 } 578 document.querySelectorAll('input[type="hidden"]').forEach(element => { 579 if (element.value === '1' && element.name.length === 32) { 580 element.name = newToken; 581 } 582 }); 583 }; 584 585 /** 586 * Method to perform AJAX request 587 * 588 * @param {Object} options Request options: 589 * { 590 * url: 'index.php', Request URL 591 * method: 'GET', Request method GET (default), POST 592 * data: null, Data to be sent, see 593 * https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/send 594 * perform: true, Perform the request immediately 595 * or return XMLHttpRequest instance and perform it later 596 * headers: null, Object of custom headers, eg {'X-Foo': 'Bar', 'X-Bar': 'Foo'} 597 * promise: false Whether return a Promise instance. 598 * When true then next options is ignored: perform, onSuccess, onError, onComplete 599 * 600 * onBefore: (xhr) => {} // Callback on before the request 601 * onSuccess: (response, xhr) => {}, // Callback on the request success 602 * onError: (xhr) => {}, // Callback on the request error 603 * onComplete: (xhr) => {}, // Callback on the request completed, with/without error 604 * } 605 * 606 * @return XMLHttpRequest|Boolean 607 * 608 * @example 609 * 610 * Joomla.request({ 611 * url: 'index.php?option=com_example&view=example', 612 * onSuccess: (response, xhr) => { 613 * JSON.parse(response); 614 * } 615 * }) 616 * 617 * @see https://developer.mozilla.org/docs/Web/API/XMLHttpRequest 618 */ 619 Joomla.request = options => { 620 // Prepare the options 621 const newOptions = Joomla.extend({ 622 url: '', 623 method: 'GET', 624 data: null, 625 perform: true, 626 promise: false 627 }, options); 628 629 // Setup XMLHttpRequest instance 630 const createRequest = (onSuccess, onError) => { 631 const xhr = new XMLHttpRequest(); 632 xhr.open(newOptions.method, newOptions.url, true); 633 634 // Set the headers 635 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 636 xhr.setRequestHeader('X-Ajax-Engine', 'Joomla!'); 637 if (newOptions.method !== 'GET') { 638 const token = Joomla.getOptions('csrf.token', ''); 639 640 // Use the CSRF only on the site's domain 641 if (token && (!newOptions.url.startsWith('http:') && !newOptions.url.startsWith('https:') || newOptions.url.startsWith(window.location.origin))) { 642 xhr.setRequestHeader('X-CSRF-Token', token); 643 } 644 if (typeof newOptions.data === 'string' && (!newOptions.headers || !newOptions.headers['Content-Type'])) { 645 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 646 } 647 } 648 649 // Custom headers 650 if (newOptions.headers) {
651 Object.keys(newOptions.headers).forEach(key => { 652 // Allow request without Content-Type 653 if (key === 'Content-Type' && newOptions.headers['Content-Type'] === 'false') ; else { 654 xhr.setRequestHeader(key, newOptions.headers[key]); 655 } 656 }); 657 } 658 xhr.onreadystatechange = () => { 659 // Request not finished 660 if (xhr.readyState !== 4) { 661 return; 662 } 663 664 // Request finished and response is ready 665 if (xhr.status === 200) { 666 if (newOptions.promise) { 667 // A Promise accepts only one argument 668 onSuccess.call(window, xhr); 669 } else { 670 onSuccess.call(window, xhr.responseText, xhr); 671 } 672 } else { 673 onError.call(window, xhr); 674 } 675 if (newOptions.onComplete && !newOptions.promise) { 676 newOptions.onComplete.call(window, xhr); 677 } 678 }; 679 680 // Do request 681 if (newOptions.perform) { 682 if (newOptions.onBefore && newOptions.onBefore.call(window, xhr) === false) { 683 // Request interrupted 684 if (newOptions.promise) { 685 onSuccess.call(window, xhr); 686 } 687 return xhr; 688 } 689 xhr.send(newOptions.data); 690 } 691 return xhr; 692 }; 693 694 // Return a Promise 695 if (newOptions.promise) { 696 return new Promise((resolve, reject) => { 697 newOptions.perform = true; 698 createRequest(resolve, reject); 699 }); 700 } 701 702 // Return a Request 703 try { 704 return createRequest(newOptions.onSuccess || (() => {}), newOptions.onError || (() => {})); 705 } catch (error) { 706 console.error(error); 707 return false; 708 } 709 }; 710 let lastRequestPromise; 711 712 /** 713 * Joomla Request queue. 714 * 715 * A FIFO queue of requests to execute serially. Used to prevent simultaneous execution of 716 * multiple requests against the server which could trigger its Denial of Service protection. 717 * 718 * @param {object} options Options for Joomla.request() 719 * @returns {Promise} 720 */ 721 Joomla.enqueueRequest = options => { 722 if (!options.promise) { 723 throw new Error('Joomla.enqueueRequest supports only Joomla.request as Promise'); 724 } 725 if (!lastRequestPromise) { 726 lastRequestPromise = Joomla.request(options); 727 } else { 728 lastRequestPromise = lastRequestPromise.then(() => Joomla.request(options)); 729 } 730 return lastRequestPromise; 731 }; 732 733 /** 734 * 735 * @param {string} unsafeHtml The html for sanitization 736 * @param {object} allowList The list of HTMLElements with an array of allowed attributes 737 * @param {function} sanitizeFn A custom sanitization function 738 * 739 * @return string 740 */ 741 Joomla.sanitizeHtml = (unsafeHtml, allowList, sanitizeFn) => { 742 const allowed = allowList === undefined || allowList === null ? DefaultAllowlist : _extends({}, DefaultAllowlist, allowList); 743 return sanitizeHtml(unsafeHtml, allowed, sanitizeFn); 744 }; 745 746 /** 747 * Treat AJAX errors. 748 * Used by some javascripts such as sendtestmail.js and permissions.js 749 * 750 * @param {object} xhr XHR object. 751 * @param {string} textStatus Type of error that occurred. 752 * @param {string} error Textual portion of the HTTP status. 753 * 754 * @return {object} JavaScript object containing the system error message. 755 * 756 * @since 3.6.0 757 */ 758 Joomla.ajaxErrorsMessages = (xhr, textStatus) => { 759 const msg = {}; 760 if (textStatus === 'parsererror') { 761 // For jQuery jqXHR 762 const buf = []; 763 764 // Html entity encode. 765 let encodedJson = xhr.responseText.trim(); 766 for (let i = encodedJson.length - 1; i >= 0; i--) { 767 buf.unshift(['&#', encodedJson[i].charCodeAt(), ';'].join('')); 768 } 769 encodedJson = buf.join(''); 770 msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_PARSE').replace('%s', encodedJson)]; 771 } else if (textStatus === 'nocontent') { 772 msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_NO_CONTENT')]; 773 } else if (textStatus === 'timeout') { 774 msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_TIMEOUT')]; 775 } else if (textStatus === 'abort') { 776 msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_CONNECTION_ABORT')]; 777 } else if (xhr.responseJSON && xhr.responseJSON.message) { 778 // For vanilla XHR 779 msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.responseJSON.message}</em>`]; 780 } else if (xhr.statusText) { 781 msg.error = [`${Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)} <em>${xhr.statusText}</em>`]; 782 } else { 783 msg.error = [Joomla.Text._('JLIB_JS_AJAX_ERROR_OTHER').replace('%s', xhr.status)]; 784 } 785 return msg; 786 }; 787 788})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.