1/* Source and licensing information for the line(s) below can be found at https://dannybarnes.com/misc/jquery-extend-3.4.0.js. */ 2(function(e){var n=e.fn.jquery.split('.'),t=parseInt(n[0]),i=parseInt(n[1]),r=parseInt(n[2]),f=(r.toString()!==n[2]);if((t>3)||(t===3&&i>4)||(t===3&&i===4&&r>0)||(t===3&&i===4&&r===0&&!f)){return};e.extend=e.fn.extend=function(){var o,r,f,t,s,a,n=arguments[0]||{},i=1,l=arguments.length,u=!1;if(typeof n==='boolean'){u=n;n=arguments[i]||{};i++};if(typeof n!=='object'&&!e.isFunction(n)){n={}};if(i===l){n=this;i--};for(;i<l;i++){if((o=arguments[i])!=null){for(r in o){t=o[r];if(r==='__proto__'||n===t){continue};if(u&&t&&(e.isPlainObject(t)||(s=e.isArray(t)))){f=n[r];if(s&&!e.isArray(f)){a=[]} 3else if(!s&&!e.isPlainObject(f)){a={}} 4else{a=f};s=!1;n[r]=e.extend(u,a,t)} 5else if(t!==undefined){n[r]=t}}}};return n}})(jQuery);; 6/* Source and licensing information for the above line(s) can be found at https://dannybarnes.com/misc/jquery-extend-3.4.0.js. */ 7;/*})'"*/ 8/** 9 * For jQuery versions less than 3.5.0, this replaces the jQuery.htmlPrefilter() 10 * function with one that fixes these security vulnerabilities while also 11 * retaining the pre-3.5.0 behavior where it's safe to do so. 12 * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022 13 * - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023 14 * 15 * Additionally, for jQuery versions that do not have a jQuery.htmlPrefilter() 16 * function (1.x prior to 1.12 and 2.x prior to 2.2), this adds it, and 17 * extends the functions that need to call it to do so. 18 * 19 * Drupal core's jQuery version is 1.4.4, but jQuery Update can provide a 20 * different version, so this covers all versions between 1.4.4 and 3.4.1. 21 * The GitHub links in the code comments below link to jQuery 1.5 code, because 22 * 1.4.4 isn't on GitHub, but the referenced code didn't change from 1.4.4 to 23 * 1.5. 24 */ 25 26(function (jQuery) { 27 28 // Parts of this backport differ by jQuery version. 29 var versionParts = jQuery.fn.jquery.split('.'); 30 var majorVersion = parseInt(versionParts[0]); 31 var minorVersion = parseInt(versionParts[1]); 32 33 // No backport is needed if we're already on jQuery 3.5 or higher. 34 if ( (majorVersion > 3) || (majorVersion === 3 && minorVersion >= 5) ) { 35 return; 36 } 37 38 // Prior to jQuery 3.5, jQuery converted XHTML-style self-closing tags to 39 // their XML equivalent: e.g., "<div />" to "<div></div>". This is 40 // problematic for several reasons, including that it's vulnerable to XSS 41 // attacks. However, since this was jQuery's behavior for many years, many 42 // Drupal modules and jQuery plugins may be relying on it. Therefore, we 43 // preserve that behavior, but for a limited set of tags only, that we believe 44 // to not be vulnerable. This is the set of HTML tags that satisfy all of the 45 // following conditions: 46 // - In DOMPurify's list of HTML tags. If an HTML tag isn't safe enough to 47 // appear in that list, then we don't want to mess with it here either. 48 // @see https://github.com/cure53/DOMPurify/blob/2.0.11/dist/purify.js#L128 49 // - A normal element (not a void, template, text, or foreign element). 50 // @see https://html.spec.whatwg.org/multipage/syntax.html#elements-2 51 // - An element that is still defined by the current HTML specification 52 // (not a deprecated element), because we do not want to rely on how 53 // browsers parse deprecated elements. 54 // @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element 55 // - Not 'html', 'head', or 'body', because this pseudo-XHTML expansion is 56 // designed for fragments, not entire documents. 57 // - Not 'colgroup', because due to an idiosyncrasy of jQuery's original 58 // regular expression, it didn't match on colgroup, and we don't want to 59 // introduce a behavior change for that. 60 var selfClosingTagsToReplace = [ 61 'a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 62 'blockquote', 'button', 'canvas', 'caption', 'cite', 'code', 'data', 63 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 64 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 65 'h4', 'h5', 'h6', 'header', 'hgroup', 'i', 'ins', 'kbd', 'label', 'legend', 66 'li', 'main', 'map', 'mark', 'menu', 'meter', 'nav', 'ol', 'optgroup', 67 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 68 'ruby', 's', 'samp', 'section', 'select', 'small', 'source', 'span', 69 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 70 'thead', 'time', 'tr', 'u', 'ul', 'var', 'video' 71 ]; 72 73 // Define regular expressions for <TAG/> and <TAG ATTRIBUTES/>. Doing this as 74 // two expressions makes it easier to target <a/> without also targeting 75 // every tag that starts with "a". 76 var xhtmlRegExpGroup = '(' + selfClosingTagsToReplace.join('|') + ')'; 77 var whitespace = '[\\x20\\t\\r\\n\\f]'; 78 var rxhtmlTagWithoutSpaceOrAttributes = new RegExp('<' + xhtmlRegExpGroup + '\\/>', 'gi'); 79 var rxhtmlTagWithSpaceAndMaybeAttributes = new RegExp('<' + xhtmlRegExpGroup + '(' + whitespace + '[^>]*)\\/>', 'gi'); 80 81 // jQuery 3.5 also fixed a vulnerability for when </select> appears within 82 // an <option> or <optgroup>, but it did that in local code that we can't 83 // backport directly. Instead, we filter such cases out. To do so, we need to 84 // determine when jQuery would otherwise invoke the vulnerable code, which it 85 // uses this regular expression to determine. The regular expression changed 86 // for version 3.0.0 and changed again for 3.4.0. 87 // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4958 88 // @see https://github.com/jquery/jquery/blob/3.0.0/dist/jquery.js#L4584 89 // @see https://github.com/jquery/jquery/blob/3.4.0/dist/jquery.js#L4712 90 var rtagName; 91 if (majorVersion < 3) { 92 rtagName = /<([\w:]+)/; 93 } 94 else if (minorVersion < 4) { 95 rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]+)/i; 96 } 97 else { 98 rtagName = /<([a-z][^\/\0>\x20\t\r\n\f]*)/i; 99 } 100 101 // The regular expression that jQuery uses to determine which self-closing 102 // tags to expand to open and close tags. This is vulnerable, because it 103 // matches all tag names except the few excluded ones. We only use this 104 // expression for determining vulnerability. The expression changed for 105 // version 3, but we only need to check for vulnerability in versions 1 and 2, 106 // so we use the expression from those versions. 107 // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4957 108 var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 109 110 jQuery.extend({ 111 htmlPrefilter: function (html) { 112 // This is how jQuery determines the first tag in the HTML. 113 // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5521 114 var tag = ( rtagName.exec( html ) || [ "", "" ] )[ 1 ].toLowerCase(); 115
116 // It is not valid HTML for <option> or <optgroup> to have <select> as 117 // either a descendant or sibling, and attempts to inject one can cause 118 // XSS on jQuery versions before 3.5. Since this is invalid HTML and a 119 // possible XSS attack, reject the entire string. 120 // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11023 121 if ((tag === 'option' || tag === 'optgroup') && html.match(/<\/?select/i)) { 122 html = ''; 123 } 124 125 // Retain jQuery's prior to 3.5 conversion of pseudo-XHTML, but for only 126 // the tags in the `selfClosingTagsToReplace` list defined above. 127 // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5518 128 // @see https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-11022 129 html = html.replace(rxhtmlTagWithoutSpaceOrAttributes, "<$1></$1>"); 130 html = html.replace(rxhtmlTagWithSpaceAndMaybeAttributes, "<$1$2></$1>"); 131 132 // Prior to jQuery 1.12 and 2.2, this function gets called (via code later 133 // in this file) in addition to, rather than instead of, the unsafe 134 // expansion of self-closing tags (including ones not in the list above). 135 // We can't prevent that unsafe expansion from running, so instead we 136 // check to make sure that it doesn't affect the DOM returned by the 137 // browser's parsing logic. If it does affect it, then it's vulnerable to 138 // XSS, so we reject the entire string. 139 if ( (majorVersion === 1 && minorVersion < 12) || (majorVersion === 2 && minorVersion < 2) ) { 140 var htmlRisky = html.replace(rxhtmlTag, "<$1></$2>"); 141 if (htmlRisky !== html) { 142 // Even though htmlRisky and html are different strings, they might 143 // represent the same HTML structure once parsed, in which case, 144 // htmlRisky is actually safe. We can ask the browser to parse both 145 // to find out, but the browser can't parse table fragments (e.g., a 146 // root-level "<td>"), so we need to wrap them. We just need this 147 // technique to work on all supported browsers; we don't need to 148 // copy from the specific jQuery version we're using. 149 // @see https://github.com/jquery/jquery/blob/3.5.1/dist/jquery.js#L4939 150 var wrapMap = { 151 thead: [ 1, "<table>", "</table>" ], 152 col: [ 2, "<table><colgroup>", "</colgroup></table>" ], 153 tr: [ 2, "<table><tbody>", "</tbody></table>" ], 154 td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], 155 }; 156 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 157 wrapMap.th = wrapMap.td; 158 159 // Function to wrap HTML into something that a browser can parse. 160 // @see https://github.com/jquery/jquery/blob/3.5.1/dist/jquery.js#L5032 161 var getWrappedHtml = function (html) { 162 var wrap = wrapMap[tag]; 163 if (wrap) { 164 html = wrap[1] + html + wrap[2]; 165 } 166 return html; 167 }; 168 169 // Function to return canonical HTML after parsing it. This parses 170 // only; it doesn't execute scripts. 171 // @see https://github.com/jquery/jquery-migrate/blob/3.3.0/src/jquery/manipulation.js#L5 172 var getParsedHtml = function (html) { 173 var doc = window.document.implementation.createHTMLDocument( "" ); 174 doc.body.innerHTML = html; 175 return doc.body ? doc.body.innerHTML : ''; 176 }; 177 178 // If the browser couldn't parse either one successfully, or if 179 // htmlRisky parses differently than html, then html is vulnerable, 180 // so reject it. 181 var htmlParsed = getParsedHtml(getWrappedHtml(html)); 182 var htmlRiskyParsed = getParsedHtml(getWrappedHtml(htmlRisky)); 183 if (htmlRiskyParsed === '' || htmlParsed === '' || (htmlRiskyParsed !== htmlParsed)) { 184 html = ''; 185 } 186 } 187 } 188 189 return html; 190 } 191 }); 192 193 // Prior to jQuery 1.12 and 2.2, jQuery.clean(), jQuery.buildFragment(), and 194 // jQuery.fn.html() did not call jQuery.htmlPrefilter(), so we add that. 195 if ( (majorVersion === 1 && minorVersion < 12) || (majorVersion === 2 && minorVersion < 2) ) { 196 // Filter the HTML coming into jQuery.fn.html(). 197 var fnOriginalHtml = jQuery.fn.html; 198 jQuery.fn.extend({ 199 // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5147 200 html: function (value) { 201 if (typeof value === "string") { 202 value = jQuery.htmlPrefilter(value); 203 } 204 // .html() can be called as a setter (with an argument) or as a getter 205 // (without an argument), so invoke fnOriginalHtml() the same way that 206 // we were invoked. 207 return fnOriginalHtml.apply(this, arguments.length ? [value] : []); 208 } 209 }); 210 211 // The regular expression that jQuery uses to determine if a string is HTML. 212 // Used by both clean() and buildFragment(). 213 // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L4960
214 var rhtml = /<|&#?\w+;/; 215 216 // Filter HTML coming into: 217 // - jQuery.clean() for versions prior to 1.9. 218 // - jQuery.buildFragment() for 1.9 and above. 219 // 220 // The looping constructs in the two functions might be essentially 221 // identical, but they're each expressed here in the way that most closely 222 // matches their original expression in jQuery, so that we filter all of 223 // the items and only the items that jQuery will treat as HTML strings. 224 if (majorVersion === 1 && minorVersion < 9) { 225 var originalClean = jQuery.clean; 226 jQuery.extend({ 227 // @see https://github.com/jquery/jquery/blob/1.5/jquery.js#L5493 228 'clean': function (elems, context, fragment, scripts) { 229 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 230 if ( typeof elem === "string" && rhtml.test( elem ) ) { 231 elems[i] = elem = jQuery.htmlPrefilter(elem); 232 } 233 } 234 return originalClean.call(this, elems, context, fragment, scripts); 235 } 236 }); 237 } 238 else { 239 var originalBuildFragment = jQuery.buildFragment; 240 jQuery.extend({ 241 // @see https://github.com/jquery/jquery/blob/1.9.0/jquery.js#L6419 242 'buildFragment': function (elems, context, scripts, selection) { 243 var l = elems.length; 244 for ( var i = 0; i < l; i++ ) { 245 var elem = elems[i]; 246 if (elem || elem === 0) { 247 if ( jQuery.type( elem ) !== "object" && rhtml.test( elem ) ) { 248 elems[i] = elem = jQuery.htmlPrefilter(elem); 249 } 250 } 251 } 252 return originalBuildFragment.call(this, elems, context, scripts, selection); 253 } 254 }); 255 } 256 } 257 258})(jQuery); 259 260;/*})'"*/ 261;/*})'"*/ 262/* Source and licensing information for the line(s) below can be found at https://dannybarnes.com/misc/jquery.once.js. */ 263(function(n){var e={},i=0;n.fn.once=function(r,o){if(typeof r!='string'){if(!(r in e)){e[r]=++i};if(!o){o=r};r='jquery-once-'+e[r]};var s=r+'-processed',t=this.not('.'+s).addClass(s);return n.isFunction(o)?t.each(o):t};n.fn.removeOnce=function(e,i){var r=e+'-processed',o=this.filter('.'+r).removeClass(r);return n.isFunction(i)?o.each(i):o}})(jQuery);; 264/* Source and licensing information for the above line(s) can be found at https://dannybarnes.com/misc/jquery.once.js. */ 265;/*})'"*/ 266 267var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {} }; 268 269// Allow other JavaScript libraries to use $. 270jQuery.noConflict(); 271 272(function ($) { 273 274/** 275 * Override jQuery.fn.init to guard against XSS attacks. 276 * 277 * See http://bugs.jquery.com/ticket/9521 278 */ 279var jquery_init = $.fn.init; 280$.fn.init = function (selector, context, rootjQuery) { 281 // If the string contains a "#" before a "<", treat it as invalid HTML. 282 if (selector && typeof selector === 'string') { 283 var hash_position = selector.indexOf('#'); 284 if (hash_position >= 0) { 285 var bracket_position = selector.indexOf('<'); 286 if (bracket_position > hash_position) { 287 throw 'Syntax error, unrecognized expression: ' + selector; 288 } 289 } 290 } 291 return jquery_init.call(this, selector, context, rootjQuery); 292}; 293$.fn.init.prototype = jquery_init.prototype; 294 295/** 296 * Pre-filter Ajax requests to guard against XSS attacks. 297 * 298 * See https://github.com/jquery/jquery/issues/2432 299 */ 300if ($.ajaxPrefilter) { 301 // For newer versions of jQuery, use an Ajax prefilter to prevent 302 // auto-executing script tags from untrusted domains. This is similar to the 303 // fix that is built in to jQuery 3.0 and higher. 304 $.ajaxPrefilter(function (s) { 305 if (s.crossDomain) { 306 s.contents.script = false; 307 } 308 }); 309} 310else if ($.httpData) { 311 // For the version of jQuery that ships with Drupal core, override 312 // jQuery.httpData to prevent auto-detecting "script" data types from 313 // untrusted domains. 314 var jquery_httpData = $.httpData; 315 $.httpData = function (xhr, type, s) { 316 // @todo Consider backporting code from newer jQuery versions to check for 317 // a cross-domain request here, rather than using Drupal.urlIsLocal() to 318 // block scripts from all URLs that are not on the same site. 319 if (!type && !Drupal.urlIsLocal(s.url)) { 320 var content_type = xhr.getResponseHeader('content-type') || ''; 321 if (content_type.indexOf('javascript') >= 0) { 322 // Default to a safe data type. 323 type = 'text'; 324 } 325 } 326 return jquery_httpData.call(this, xhr, type, s); 327 }; 328 $.httpData.prototype = jquery_httpData.prototype; 329} 330 331/** 332 * Attach all registered behaviors to a page element. 333 * 334 * Behaviors are event-triggered actions that attach to page elements, enhancing 335 * default non-JavaScript UIs. Behaviors are registered in the Drupal.behaviors 336 * object using the method 'attach' and optionally also 'detach' as follows: 337 * @code 338 * Drupal.behaviors.behaviorName = { 339 * attach: function (context, settings) { 340 * ... 341 * }, 342 * detach: function (context, settings, trigger) { 343 * ... 344 * } 345 * }; 346 * @endcode 347 * 348 * Drupal.attachBehaviors is added below to the jQuery ready event and so 349 * runs on initial page load. Developers implementing AHAH/Ajax in their 350 * solutions should also call this function after new page content has been 351 * loaded, feeding in an element to be processed, in order to attach all 352 * behaviors to the new content. 353 * 354 * Behaviors should use 355 * @code 356 * $(selector).once('behavior-name', function () { 357 * ... 358 * }); 359 * @endcode 360 * to ensure the behavior is attached only once to a given element. (Doing so 361 * enables the reprocessing of given elements, which may be needed on occasion 362 * despite the ability to limit behavior attachment to a particular element.) 363 * 364 * @param context 365 * An element to attach behaviors to. If none is given, the document element 366 * is used. 367 * @param settings
368 * An object containing settings for the current context. If none given, the 369 * global Drupal.settings object is used. 370 */ 371Drupal.attachBehaviors = function (context, settings) { 372 context = context || document; 373 settings = settings || Drupal.settings; 374 // Execute all of them. 375 $.each(Drupal.behaviors, function () { 376 if ($.isFunction(this.attach)) { 377 this.attach(context, settings); 378 } 379 }); 380}; 381 382/** 383 * Detach registered behaviors from a page element. 384 * 385 * Developers implementing AHAH/Ajax in their solutions should call this 386 * function before page content is about to be removed, feeding in an element 387 * to be processed, in order to allow special behaviors to detach from the 388 * content. 389 * 390 * Such implementations should look for the class name that was added in their 391 * corresponding Drupal.behaviors.behaviorName.attach implementation, i.e. 392 * behaviorName-processed, to ensure the behavior is detached only from 393 * previously processed elements. 394 * 395 * @param context 396 * An element to detach behaviors from. If none is given, the document element 397 * is used. 398 * @param settings 399 * An object containing settings for the current context. If none given, the 400 * global Drupal.settings object is used. 401 * @param trigger 402 * A string containing what's causing the behaviors to be detached. The 403 * possible triggers are: 404 * - unload: (default) The context element is being removed from the DOM. 405 * - move: The element is about to be moved within the DOM (for example, 406 * during a tabledrag row swap). After the move is completed, 407 * Drupal.attachBehaviors() is called, so that the behavior can undo 408 * whatever it did in response to the move. Many behaviors won't need to 409 * do anything simply in response to the element being moved, but because 410 * IFRAME elements reload their "src" when being moved within the DOM, 411 * behaviors bound to IFRAME elements (like WYSIWYG editors) may need to 412 * take some action. 413 * - serialize: When an Ajax form is submitted, this is called with the 414 * form as the context. This provides every behavior within the form an 415 * opportunity to ensure that the field elements have correct content 416 * in them before the form is serialized. The canonical use-case is so 417 * that WYSIWYG editors can update the hidden textarea to which they are 418 * bound. 419 * 420 * @see Drupal.attachBehaviors 421 */ 422Drupal.detachBehaviors = function (context, settings, trigger) { 423 context = context || document; 424 settings = settings || Drupal.settings; 425 trigger = trigger || 'unload'; 426 // Execute all of them. 427 $.each(Drupal.behaviors, function () { 428 if ($.isFunction(this.detach)) { 429 this.detach(context, settings, trigger); 430 } 431 }); 432}; 433 434/** 435 * Encode special characters in a plain-text string for display as HTML. 436 * 437 * @ingroup sanitization 438 */ 439Drupal.checkPlain = function (str) { 440 var character, regex, 441 replace = { '&': '&', "'": ''', '"': '"', '<': '<', '>': '>' }; 442 str = String(str); 443 for (character in replace) { 444 if (replace.hasOwnProperty(character)) { 445 regex = new RegExp(character, 'g'); 446 str = str.replace(regex, replace[character]); 447 } 448 } 449 return str; 450}; 451 452/** 453 * Replace placeholders with sanitized values in a string. 454 * 455 * @param str 456 * A string with placeholders. 457 * @param args 458 * An object of replacements pairs to make. Incidences of any key in this 459 * array are replaced with the corresponding value. Based on the first 460 * character of the key, the value is escaped and/or themed: 461 * - !variable: inserted as is 462 * - @variable: escape plain text to HTML (Drupal.checkPlain) 463 * - %variable: escape text and theme as a placeholder for user-submitted 464 * content (checkPlain + Drupal.theme('placeholder')) 465 * 466 * @see Drupal.t() 467 * @ingroup sanitization 468 */ 469Drupal.formatString = function(str, args) { 470 // Transform arguments before inserting them. 471 for (var key in args) { 472 if (args.hasOwnProperty(key)) { 473 switch (key.charAt(0)) { 474 // Escaped only. 475 case '@': 476 args[key] = Drupal.checkPlain(args[key]); 477 break; 478 // Pass-through. 479 case '!': 480 break; 481 // Escaped and placeholder. 482 default: 483 args[key] = Drupal.theme('placeholder', args[key]); 484 break; 485 } 486 } 487 } 488 489 return Drupal.stringReplace(str, args, null); 490}; 491 492/** 493 * Replace substring. 494 * 495 * The longest keys will be tried first. Once a substring has been replaced, 496 * its new value will not be searched again. 497 * 498 * @param {String} str 499 * A string with placeholders. 500 * @param {Object} args 501 * Key-value pairs. 502 * @param {Array|null} keys 503 * Array of keys from the "args". Internal use only. 504 * 505 * @return {String} 506 * Returns the replaced string. 507 */ 508Drupal.stringReplace = function (str, args, keys) { 509 if (str.length === 0) { 510 return str; 511 } 512 513 // If the array of keys is not passed then collect the keys from the args. 514 if (!$.isArray(keys)) { 515 keys = []; 516 for (var k in args) { 517 if (args.hasOwnProperty(k)) { 518 keys.push(k); 519 } 520 } 521 522 // Order the keys by the character length. The shortest one is the first. 523 keys.sort(function (a, b) { return a.length - b.length; }); 524 } 525 526 if (keys.length === 0) { 527 return str; 528 } 529 530 // Take next longest one from the end. 531 var key = keys.pop(); 532 var fragments = str.split(key); 533 534 if (keys.length) { 535 for (var i = 0; i < fragments.length; i++) { 536 // Process each fragment with a copy of remaining keys. 537 fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0)); 538 } 539 } 540 541 return fragments.join(args[key]); 542}; 543 544/** 545 * Translate strings to the page language or a given language. 546 * 547 * See the documentation of the server-side t() function for further details. 548 * 549 * @param str 550 * A string containing the English string to translate. 551 * @param args 552 * An object of replacements pairs to make after translation. Incidences 553 * of any key in this array are replaced with the corresponding value. 554 * See Drupal.formatString(). 555 * 556 * @param options 557 * - 'context' (defaults to the empty context): The context the source string 558 * belongs to. 559 * 560 * @return 561 * The translated string. 562 */ 563Drupal.t = function (str, args, options) { 564 options = options || {}; 565 options.context = options.context || ''; 566 567 // Fetch the localized version of the string. 568 if (Drupal.locale.strings && Drupal.locale.strings[options.context] && Drupal.locale.strings[options.context][str]) { 569 str = Drupal.locale.strings[options.context][str]; 570 } 571 572 if (args) { 573 str = Drupal.formatString(str, args); 574 } 575 return str; 576}; 577 578/** 579 * Format a string containing a count of items. 580 * 581 * This function ensures that the string is pluralized correctly. Since Drupal.t() is 582 * called by this function, make sure not to pass already-localized strings to it. 583 * 584 * See the documentation of the server-side format_plural() function for further details. 585 * 586 * @param count 587 * The item count to display. 588 * @param singular 589 * The string for the singular case. Please make sure it is clear this is 590 * singular, to ease translation (e.g. use "1 new comment" instead of "1 new"). 591 * Do not use @count in the singular string. 592 * @param plural 593 * The string for the plural case. Please make sure it is clear this is plural, 594 * to ease translation. Use @count in place of the item count, as in "@count 595 * new comments". 596 * @param args 597 * An object of replacements pairs to make after translation. Incidences 598 * of any key in this array are replaced with the corresponding value. 599 * See Drupal.formatString(). 600 * Note that you do not need to include @count in this array.
601 * This replacement is done automatically for the plural case. 602 * @param options 603 * The options to pass to the Drupal.t() function. 604 * @return 605 * A translated string. 606 */ 607Drupal.formatPlural = function (count, singular, plural, args, options) { 608 args = args || {}; 609 args['@count'] = count; 610 // Determine the index of the plural form. 611 var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1); 612 613 if (index == 0) { 614 return Drupal.t(singular, args, options); 615 } 616 else if (index == 1) { 617 return Drupal.t(plural, args, options); 618 } 619 else { 620 args['@count[' + index + ']'] = args['@count']; 621 delete args['@count']; 622 return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options); 623 } 624}; 625 626/** 627 * Returns the passed in URL as an absolute URL. 628 * 629 * @param url 630 * The URL string to be normalized to an absolute URL. 631 * 632 * @return 633 * The normalized, absolute URL. 634 * 635 * @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js 636 * @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript 637 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53 638 */ 639Drupal.absoluteUrl = function (url) { 640 var urlParsingNode = document.createElement('a'); 641 642 // Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8 643 // strings may throw an exception. 644 try { 645 url = decodeURIComponent(url); 646 } catch (e) {} 647 648 urlParsingNode.setAttribute('href', url); 649 650 // IE <= 7 normalizes the URL when assigned to the anchor node similar to 651 // the other browsers. 652 return urlParsingNode.cloneNode(false).href; 653}; 654 655/** 656 * Returns true if the URL is within Drupal's base path. 657 * 658 * @param url 659 * The URL string to be tested. 660 * 661 * @return 662 * Boolean true if local. 663 * 664 * @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58 665 */ 666Drupal.urlIsLocal = function (url) { 667 // Always use browser-derived absolute URLs in the comparison, to avoid 668 // attempts to break out of the base path using directory traversal. 669 var absoluteUrl = Drupal.absoluteUrl(url); 670 var protocol = location.protocol; 671 672 // Consider URLs that match this site's base URL but use HTTPS instead of HTTP 673 // as local as well. 674 if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) { 675 protocol = 'https:'; 676 } 677 var baseUrl = protocol + '//' + location.host + Drupal.settings.basePath.slice(0, -1); 678 679 // Decoding non-UTF-8 strings may throw an exception. 680 try { 681 absoluteUrl = decodeURIComponent(absoluteUrl); 682 } catch (e) {} 683 try { 684 baseUrl = decodeURIComponent(baseUrl); 685 } catch (e) {} 686 687 // The given URL matches the site's base URL, or has a path under the site's 688 // base URL. 689 return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0; 690}; 691 692/** 693 * Sanitizes a URL for use with jQuery.ajax(). 694 * 695 * @param url 696 * The URL string to be sanitized. 697 * 698 * @return 699 * The sanitized URL. 700 */ 701Drupal.sanitizeAjaxUrl = function (url) { 702 var regex = /\=\?(&|$)/; 703 while (url.match(regex)) { 704 url = url.replace(regex, ''); 705 } 706 return url; 707} 708 709/** 710 * Generate the themed representation of a Drupal object. 711 * 712 * All requests for themed output must go through this function. It examines 713 * the request and routes it to the appropriate theme function. If the current 714 * theme does not provide an override function, the generic theme function is 715 * called. 716 * 717 * For example, to retrieve the HTML for text that should be emphasized and 718 * displayed as a placeholder inside a sentence, call 719 * Drupal.theme('placeholder', text). 720 * 721 * @param func 722 * The name of the theme function to call. 723 * @param ... 724 * Additional arguments to pass along to the theme function. 725 * @return 726 * Any data the theme function returns. This could be a plain HTML string, 727 * but also a complex object. 728 */ 729Drupal.theme = function (func) { 730 var args = Array.prototype.slice.apply(arguments, [1]); 731 732 return (Drupal.theme[func] || Drupal.theme.prototype[func]).apply(this, args); 733}; 734 735/** 736 * Freeze the current body height (as minimum height). Used to prevent 737 * unnecessary upwards scrolling when doing DOM manipulations. 738 */ 739Drupal.freezeHeight = function () { 740 Drupal.unfreezeHeight(); 741 $('<div id="freeze-height"></div>').css({ 742 position: 'absolute', 743 top: '0px', 744 left: '0px', 745 width: '1px', 746 height: $('body').css('height') 747 }).appendTo('body'); 748}; 749 750/** 751 * Unfreeze the body height. 752 */ 753Drupal.unfreezeHeight = function () { 754 $('#freeze-height').remove(); 755}; 756 757/** 758 * Encodes a Drupal path for use in a URL. 759 * 760 * For aesthetic reasons slashes are not escaped. 761 */ 762Drupal.encodePath = function (item, uri) { 763 uri = uri || location.href; 764 return encodeURIComponent(item).replace(/%2F/g, '/'); 765}; 766 767/** 768 * Get the text selection in a textarea. 769 */ 770Drupal.getSelection = function (element) { 771 if (typeof element.selectionStart != 'number' && document.selection) { 772 // The current selection. 773 var range1 = document.selection.createRange(); 774 var range2 = range1.duplicate(); 775 // Select all text. 776 range2.moveToElementText(element); 777 // Now move 'dummy' end point to end point of original range.
778 range2.setEndPoint('EndToEnd', range1); 779 // Now we can calculate start and end points. 780 var start = range2.text.length - range1.text.length; 781 var end = start + range1.text.length; 782 return { 'start': start, 'end': end }; 783 } 784 return { 'start': element.selectionStart, 'end': element.selectionEnd }; 785}; 786 787/** 788 * Add a global variable which determines if the window is being unloaded. 789 * 790 * This is primarily used by Drupal.displayAjaxError(). 791 */ 792Drupal.beforeUnloadCalled = false; 793$(window).bind('beforeunload pagehide', function () { 794 Drupal.beforeUnloadCalled = true; 795}); 796 797/** 798 * Displays a JavaScript error from an Ajax response when appropriate to do so. 799 */ 800Drupal.displayAjaxError = function (message) { 801 // Skip displaying the message if the user deliberately aborted (for example, 802 // by reloading the page or navigating to a different page) while the Ajax 803 // request was still ongoing. See, for example, the discussion at 804 // http://stackoverflow.com/questions/699941/handle-ajax-error-when-a-user-clicks-refresh. 805 if (!Drupal.beforeUnloadCalled) { 806 alert(message); 807 } 808}; 809 810/** 811 * Build an error message from an Ajax response. 812 */ 813Drupal.ajaxError = function (xmlhttp, uri, customMessage) { 814 var statusCode, statusText, pathText, responseText, readyStateText, message; 815 if (xmlhttp.status) { 816 statusCode = "\n" + Drupal.t("An AJAX HTTP error occurred.") + "\n" + Drupal.t("HTTP Result Code: !status", {'!status': xmlhttp.status}); 817 } 818 else { 819 statusCode = "\n" + Drupal.t("An AJAX HTTP request terminated abnormally."); 820 } 821 statusCode += "\n" + Drupal.t("Debugging information follows."); 822 pathText = "\n" + Drupal.t("Path: !uri", {'!uri': uri} ); 823 statusText = ''; 824 // In some cases, when statusCode == 0, xmlhttp.statusText may not be defined. 825 // Unfortunately, testing for it with typeof, etc, doesn't seem to catch that 826 // and the test causes an exception. So we need to catch the exception here. 827 try { 828 statusText = "\n" + Drupal.t("StatusText: !statusText", {'!statusText': $.trim(xmlhttp.statusText)}); 829 } 830 catch (e) {} 831 832 responseText = ''; 833 // Again, we don't have a way to know for sure whether accessing 834 // xmlhttp.responseText is going to throw an exception. So we'll catch it. 835 try { 836 responseText = "\n" + Drupal.t("ResponseText: !responseText", {'!responseText': $.trim(xmlhttp.responseText) } ); 837 } catch (e) {} 838 839 // Make the responseText more readable by stripping HTML tags and newlines. 840 responseText = responseText.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi,""); 841 responseText = responseText.replace(/[\n]+\s+/g,"\n"); 842 843 // We don't need readyState except for status == 0. 844 readyStateText = xmlhttp.status == 0 ? ("\n" + Drupal.t("ReadyState: !readyState", {'!readyState': xmlhttp.readyState})) : ""; 845 846 // Additional message beyond what the xmlhttp object provides. 847 customMessage = customMessage ? ("\n" + Drupal.t("CustomMessage: !customMessage", {'!customMessage': customMessage})) : ""; 848 849 message = statusCode + pathText + statusText + customMessage + responseText + readyStateText; 850 return message; 851}; 852 853// Class indicating that JS is enabled; used for styling purpose. 854$('html').addClass('js'); 855 856$(function () { 857 if (Drupal.settings.setHasJsCookie === 1) { 858 // 'js enabled' cookie. 859 document.cookie = 'has_js=1; path=/; SameSite=Lax'; 860 } 861}); 862 863/** 864 * Additions to jQuery.support. 865 */ 866$(function () { 867 /** 868 * Boolean indicating whether or not position:fixed is supported. 869 */ 870 if (jQuery.support.positionFixed === undefined) { 871 var el = $('<div style="position:fixed; top:10px" />').appendTo(document.body); 872 jQuery.support.positionFixed = el[0].offsetTop === 10; 873 el.remove(); 874 } 875}); 876 877//Attach all behaviors. 878$(function () { 879 Drupal.attachBehaviors(document, Drupal.settings); 880}); 881 882/** 883 * The default themes. 884 */ 885Drupal.theme.prototype = { 886 887 /** 888 * Formats text for emphasized display in a placeholder inside a sentence. 889 * 890 * @param str 891 * The text to format (plain-text). 892 * @return 893 * The formatted text (html). 894 */ 895 placeholder: function (str) { 896 return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>'; 897 } 898}; 899 900})(jQuery); 901 902;/*})'"*/ 903;/*})'"*/ 904/** 905 * Workaround for deprecated $.browser which was removed in jQuery 1.9 906 * @see https://api.jquery.com/jquery.browser/ 907 */ 908(function ($) { 909 if ($.browser===undefined) { 910 $.browser={}; 911 $.browser.msie=false; 912 $.browser.version=0; 913 if (navigator.userAgent.match(/MSIE ([0-9]+)\./)) { 914 $.browser.msie=true; 915 $.browser.version=RegExp.$1; 916 } 917 } 918})(jQuery); 919 920;/*})'"*/ 921;/*})'"*/
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.