PageSourceSearch

https://crcc-ccetp.gc.ca/libraries/wet-boew/js/wet-boew.js?tksz1r

js crcc-ccetp.gc.ca collected 2026-09-24 19:22:26 UTC 603,506 bytes, 19,986 lines download raw bytes

vendor: 10,460 bytes, lines 1-254
1/*!
2 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
3 * wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4 * v4.0.75 - 2024-03-05
5 *
6 *//*! Modernizr (Custom Build) | MIT & BSD */
7/*! @license DOMPurify 2.4.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.4.4/LICENSE */
8
9(function (global, factory) {
10  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
11  typeof define === 'function' && define.amd ? define(factory) :
12  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DOMPurify = factory());
13})(this, (function () { 'use strict';
14
15  function _typeof(obj) {
16    "@babel/helpers - typeof";
17
18    return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
19      return typeof obj;
20    } : function (obj) {
21      return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
22    }, _typeof(obj);
23  }
24
25  function _setPrototypeOf(o, p) {
26    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
27      o.__proto__ = p;
28      return o;
29    };
30
31    return _setPrototypeOf(o, p);
32  }
33
34  function _isNativeReflectConstruct() {
35    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
36    if (Reflect.construct.sham) return false;
37    if (typeof Proxy === "function") return true;
38
39    try {
40      Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
41      return true;
42    } catch (e) {
43      return false;
44    }
45  }
46
47  function _construct(Parent, args, Class) {
48    if (_isNativeReflectConstruct()) {
49      _construct = Reflect.construct;
50    } else {
51      _construct = function _construct(Parent, args, Class) {
52        var a = [null];
53        a.push.apply(a, args);
54        var Constructor = Function.bind.apply(Parent, a);
55        var instance = new Constructor();
56        if (Class) _setPrototypeOf(instance, Class.prototype);
57        return instance;
58      };
59    }
60
61    return _construct.apply(null, arguments);
62  }
63
64  function _toConsumableArray(arr) {
65    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
66  }
67
68  function _arrayWithoutHoles(arr) {
69    if (Array.isArray(arr)) return _arrayLikeToArray(arr);
70  }
71
72  function _iterableToArray(iter) {
73    if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
74  }
75
76  function _unsupportedIterableToArray(o, minLen) {
77    if (!o) return;
78    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
79    var n = Object.prototype.toString.call(o).slice(8, -1);
80    if (n === "Object" && o.constructor) n = o.constructor.name;
81    if (n === "Map" || n === "Set") return Array.from(o);
82    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
83  }
84
85  function _arrayLikeToArray(arr, len) {
86    if (len == null || len > arr.length) len = arr.length;
87
88    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
89
90    return arr2;
91  }
92
93  function _nonIterableSpread() {
94    throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
95  }
96
97  var hasOwnProperty = Object.hasOwnProperty,
98      setPrototypeOf = Object.setPrototypeOf,
99      isFrozen = Object.isFrozen,
100      getPrototypeOf = Object.getPrototypeOf,
101      getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
102  var freeze = Object.freeze,
103      seal = Object.seal,
104      create = Object.create; // eslint-disable-line import/no-mutable-exports
105
106  var _ref = typeof Reflect !== 'undefined' && Reflect,
107      apply = _ref.apply,
108      construct = _ref.construct;
109
110  if (!apply) {
111    apply = function apply(fun, thisValue, args) {
112      return fun.apply(thisValue, args);
113    };
114  }
115
116  if (!freeze) {
117    freeze = function freeze(x) {
118      return x;
119    };
120  }
121
122  if (!seal) {
123    seal = function seal(x) {
124      return x;
125    };
126  }
127
128  if (!construct) {
129    construct = function construct(Func, args) {
130      return _construct(Func, _toConsumableArray(args));
131    };
132  }
133
134  var arrayForEach = unapply(Array.prototype.forEach);
135  var arrayPop = unapply(Array.prototype.pop);
136  var arrayPush = unapply(Array.prototype.push);
137  var stringToLowerCase = unapply(String.prototype.toLowerCase);
138  var stringToString = unapply(String.prototype.toString);
139  var stringMatch = unapply(String.prototype.match);
140  var stringReplace = unapply(String.prototype.replace);
141  var stringIndexOf = unapply(String.prototype.indexOf);
142  var stringTrim = unapply(String.prototype.trim);
143  var regExpTest = unapply(RegExp.prototype.test);
144  var typeErrorCreate = unconstruct(TypeError);
145  function unapply(func) {
146    return function (thisArg) {
147      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
148        args[_key - 1] = arguments[_key];
149      }
150
151      return apply(func, thisArg, args);
152    };
153  }
154  function unconstruct(func) {
155    return function () {
156      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
157        args[_key2] = arguments[_key2];
158      }
159
160      return construct(func, args);
161    };
162  }
163  /* Add properties to a lookup table */
164
165  function addToSet(set, array, transformCaseFunc) {
166    transformCaseFunc = transformCaseFunc ? transformCaseFunc : stringToLowerCase;
167
168    if (setPrototypeOf) {
169      // Make 'in' and truthy checks like Boolean(set.constructor)
170      // independent of any properties defined on Object.prototype.
171      // Prevent prototype setters from intercepting set as a this value.
172      setPrototypeOf(set, null);
173    }
174
175    var l = array.length;
176
177    while (l--) {
178      var element = array[l];
179
180      if (typeof element === 'string') {
181        var lcElement = transformCaseFunc(element);
182
183        if (lcElement !== element) {
184          // Config presets (e.g. tags.js, attrs.js) are immutable.
185          if (!isFrozen(array)) {
186            array[l] = lcElement;
187          }
188
189          element = lcElement;
190        }
191      }
192
193      set[element] = true;
194    }
195
196    return set;
197  }
198  /* Shallow clone an object */
199
200  function clone(object) {
201    var newObject = create(null);
202    var property;
203
204    for (property in object) {
205      if (apply(hasOwnProperty, object, [property]) === true) {
206        newObject[property] = object[property];
207      }
208    }
209
210    return newObject;
211  }
212  /* IE10 doesn't support __lookupGetter__ so lets'
213   * simulate it. It also automatically checks
214   * if the prop is function or getter and behaves
215   * accordingly. */
216
217  function lookupGetter(object, prop) {
218    while (object !== null) {
219      var desc = getOwnPropertyDescriptor(object, prop);
220
221      if (desc) {
222        if (desc.get) {
223          return unapply(desc.get);
224        }
225
226        if (typeof desc.value === 'function') {
227          return unapply(desc.value);
228        }
229      }
230
231      object = getPrototypeOf(object);
232    }
233
234    function fallbackValue(element) {
235      console.warn('fallback value for', element);
236      return null;
237    }
238
239    return fallbackValue;
240  }
241
242  var html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); // SVG
243
244  var svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
245  var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); // List of SVG elements that are disallowed by default.
246  // We still need to know them so that we can do namespace
247  // checks properly in case one wants to add them to
248  // allow-list.
249
250  var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
251  var mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover']); // Similarly to SVG, we want to know all MathML elements,
252  // even those that we disallow by default.
253
254  var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'n
254one']);
255  var text = freeze(['#text']);
256
257  var html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns', 'slot']);
258  var svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
259  var mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
260  var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
261
262  var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
263
264  var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
265  var TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
266  var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
267
268  var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
269
vendor: 5,388 bytes, lines 270-403
270  var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
271  );
272  var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
273  var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
274  );
275  var DOCTYPE_NAME = seal(/^html$/i);
276
277  var getGlobal = function getGlobal() {
278    return typeof window === 'undefined' ? null : window;
279  };
280  /**
281   * Creates a no-op policy for internal use only.
282   * Don't export this function outside this module!
283   * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory.
284   * @param {Document} document The document object (to determine policy name suffix)
285   * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types
286   * are not supported).
287   */
288
289
290  var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
291    if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
292      return null;
293    } // Allow the callers to control the unique policy name
294    // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
295    // Policy creation with duplicate names throws in Trusted Types.
296
297
298    var suffix = null;
299    var ATTR_NAME = 'data-tt-policy-suffix';
300
301    if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
302      suffix = document.currentScript.getAttribute(ATTR_NAME);
303    }
304
305    var policyName = 'dompurify' + (suffix ? '#' + suffix : '');
306
307    try {
308      return trustedTypes.createPolicy(policyName, {
309        createHTML: function createHTML(html) {
310          return html;
311        },
312        createScriptURL: function createScriptURL(scriptUrl) {
313          return scriptUrl;
314        }
315      });
316    } catch (_) {
317      // Policy creation failed (most likely another DOMPurify script has
318      // already run). Skip creating the policy, as this will only cause errors
319      // if TT are enforced.
320      console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
321      return null;
322    }
323  };
324
325  function createDOMPurify() {
326    var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
327
328    var DOMPurify = function DOMPurify(root) {
329      return createDOMPurify(root);
330    };
331    /**
332     * Version label, exposed for easier checks
333     * if DOMPurify is up to date or not
334     */
335
336
337    DOMPurify.version = '2.4.4';
338    /**
339     * Array of elements that DOMPurify removed during sanitation.
340     * Empty if nothing was removed.
341     */
342
343    DOMPurify.removed = [];
344
345    if (!window || !window.document || window.document.nodeType !== 9) {
346      // Not running in a browser, provide a factory function
347      // so that you can pass your own Window
348      DOMPurify.isSupported = false;
349      return DOMPurify;
350    }
351
352    var originalDocument = window.document;
353    var document = window.document;
354    var DocumentFragment = window.DocumentFragment,
355        HTMLTemplateElement = window.HTMLTemplateElement,
356        Node = window.Node,
357        Element = window.Element,
358        NodeFilter = window.NodeFilter,
359        _window$NamedNodeMap = window.NamedNodeMap,
360        NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
361        HTMLFormElement = window.HTMLFormElement,
362        DOMParser = window.DOMParser,
363        trustedTypes = window.trustedTypes;
364    var ElementPrototype = Element.prototype;
365    var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
366    var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
367    var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
368    var getParentNode = lookupGetter(ElementPrototype, 'parentNode'); // As per issue #47, the web-components registry is inherited by a
369    // new document created via createHTMLDocument. As per the spec
370    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
371    // a new empty registry is used when creating a template contents owner
372    // document, so we use that as our parent document to ensure nothing
373    // is inherited.
374
375    if (typeof HTMLTemplateElement === 'function') {
376      var template = document.createElement('template');
377
378      if (template.content && template.content.ownerDocument) {
379        document = template.content.ownerDocument;
380      }
381    }
382
383    var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
384
385    var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : '';
386    var _document = document,
387        implementation = _document.implementation,
388        createNodeIterator = _document.createNodeIterator,
389        createDocumentFragment = _document.createDocumentFragment,
390        getElementsByTagName = _document.getElementsByTagName;
391    var importNode = originalDocument.importNode;
392    var documentMode = {};
393
394    try {
395      documentMode = clone(document).documentMode ? document.documentMode : {};
396    } catch (_) {}
397
398    var hooks = {};
399    /**
400     * Expose whether this browser supports running the full DOMPurify.
401     */
402
403    DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9
vendor: 18,492 bytes, lines 403-882
403;
404    var MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
405        ERB_EXPR$1 = ERB_EXPR,
406        TMPLIT_EXPR$1 = TMPLIT_EXPR,
407        DATA_ATTR$1 = DATA_ATTR,
408        ARIA_ATTR$1 = ARIA_ATTR,
409        IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
410        ATTR_WHITESPACE$1 = ATTR_WHITESPACE;
411    var IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
412    /**
413     * We consider the elements and attributes below to be safe. Ideally
414     * don't add any new ones but feel free to remove unwanted ones.
415     */
416
417    /* allowed element names */
418
419    var ALLOWED_TAGS = null;
420    var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(svgFilters), _toConsumableArray(mathMl$1), _toConsumableArray(text)));
421    /* Allowed attribute names */
422
423    var ALLOWED_ATTR = null;
424    var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml)));
425    /*
426     * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
427     * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
428     * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
429     * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
430     */
431
432    var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
433      tagNameCheck: {
434        writable: true,
435        configurable: false,
436        enumerable: true,
437        value: null
438      },
439      attributeNameCheck: {
440        writable: true,
441        configurable: false,
442        enumerable: true,
443        value: null
444      },
445      allowCustomizedBuiltInElements: {
446        writable: true,
447        configurable: false,
448        enumerable: true,
449        value: false
450      }
451    }));
452    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
453
454    var FORBID_TAGS = null;
455    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
456
457    var FORBID_ATTR = null;
458    /* Decide if ARIA attributes are okay */
459
460    var ALLOW_ARIA_ATTR = true;
461    /* Decide if custom data attributes are okay */
462
463    var ALLOW_DATA_ATTR = true;
464    /* Decide if unknown protocols are okay */
465
466    var ALLOW_UNKNOWN_PROTOCOLS = false;
467    /* Decide if self-closing tags in attributes are allowed.
468     * Usually removed due to a mXSS issue in jQuery 3.0 */
469
470    var ALLOW_SELF_CLOSE_IN_ATTR = true;
471    /* Output should be safe for common template engines.
472     * This means, DOMPurify removes data attributes, mustaches and ERB
473     */
474
475    var SAFE_FOR_TEMPLATES = false;
476    /* Decide if document with <html>... should be returned */
477
478    var WHOLE_DOCUMENT = false;
479    /* Track whether config is already set on this instance of DOMPurify. */
480
481    var SET_CONFIG = false;
482    /* Decide if all elements (e.g. style, script) must be children of
483     * document.body. By default, browsers might move them to document.head */
484
485    var FORCE_BODY = false;
486    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
487     * string (or a TrustedHTML object if Trusted Types are supported).
488     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
489     */
490
491    var RETURN_DOM = false;
492    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
493     * string  (or a TrustedHTML object if Trusted Types are supported) */
494
495    var RETURN_DOM_FRAGMENT = false;
496    /* Try to return a Trusted Type object instead of a string, return a string in
497     * case Trusted Types are not supported  */
498
499    var RETURN_TRUSTED_TYPE = false;
500    /* Output should be free from DOM clobbering attacks?
501     * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
502     */
503
504    var SANITIZE_DOM = true;
505    /* Achieve full DOM Clobbering protection by isolating the namespace of named
506     * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
507     *
508     * HTML/DOM spec rules that enable DOM Clobbering:
509     *   - Named Access on Window (§7.3.3)
510     *   - DOM Tree Accessors (§3.1.5)
511     *   - Form Element Parent-Child Relations (§4.10.3)
512     *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
513     *   - HTMLCollection (§4.2.10.2)
514     *
515     * Namespace isolation is implemented by prefixing `id` and `name` attributes
516     * with a constant string, i.e., `user-content-`
517     */
518
519    var SANITIZE_NAMED_PROPS = false;
520    var SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
521    /* Keep element content when removing element? */
522
523    var KEEP_CONTENT = true;
524    /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
525     * of importing it into a new Document and returning a sanitized copy */
526
527    var IN_PLACE = false;
528    /* Allow usage of profiles like html, svg and mathMl */
529
530    var USE_PROFILES = {};
531    /* Tags to ignore content of when KEEP_CONTENT is true */
532
533    var FORBID_CONTENTS = null;
534    var DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
535    /* Tags that are safe for data: URIs */
536
537    var DATA_URI_TAGS = null;
538    var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
539    /* Attributes safe for values like "javascript:" */
540
541    var URI_SAFE_ATTRIBUTES = null;
542    var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
543    var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
544    var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
545    var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
546    /* Document namespace */
547
548    var NAMESPACE = HTML_NAMESPACE;
549    var IS_EMPTY_INPUT = false;
550    /* Allowed XHTML+XML namespaces */
551
552    var ALLOWED_NAMESPACES = null;
553    var DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
554    /* Parsing of strict XHTML documents */
555
556    var PARSER_MEDIA_TYPE;
557    var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
558    var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
559    var transformCaseFunc;
560    /* Keep a reference to config to pass to hooks */
561
562    var CONFIG = null;
563    /* Ideally, do not touch anything below this line */
564
565    /* ______________________________________________ */
566
567    var formElement = document.createElement('form');
568
569    var isRegexOrFunction = function isRegexOrFunction(testValue) {
570      return testValue instanceof RegExp || testValue instanceof Function;
571    };
572    /**
573     * _parseConfig
574     *
575     * @param  {Object} cfg optional config literal
576     */
577    // eslint-disable-next-line complexity
578
579
580    var _parseConfig = function _parseConfig(cfg) {
581      if (CONFIG && CONFIG === cfg) {
582        return;
583      }
584      /* Shield configuration object from tampering */
585
586
587      if (!cfg || _typeof(cfg) !== 'object') {
588        cfg = {};
589      }
590      /* Shield configuration object from prototype pollution */
591
592
593      cfg = clone(cfg);
594      PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes
595      SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE; // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
596
597      transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
598      /* Set configuration parameters */
599
600      ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
601      ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
602      ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
603      URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent
604      cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent
605      transformCaseFunc // eslint-disable-line indent
606      ) // eslint-disable-line indent
607      : DEFAULT_URI_SAFE_ATTRIBUTES;
608      DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent
609      cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent
610      transformCaseFunc // eslint-disable-line indent
611      ) // eslint-disable-line indent
612      : DEFAULT_DATA_URI_TAGS;
613      FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
614      FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
615      FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
616      USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
617      ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
618
619      ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
620
621      ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
622
623      ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
624
625      SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
626
627      WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
628
629      RETURN_DOM = cfg.RETURN_DOM || false; // Default false
630
631      RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
632
633      RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
634
635      FORCE_BODY = cfg.FORCE_BODY || false; // Default false
636
637      SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
638
639      SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
640
641      KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
642
643      IN_PLACE = cfg.IN_PLACE || false; // Default false
644
645      IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1;
646      NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
647
648      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
649        CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
650      }
651
652      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
653        CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
654      }
655
656      if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
657        CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
658      }
659
660      if (SAFE_FOR_TEMPLATES) {
661        ALLOW_DATA_ATTR = false;
662      }
663
664      if (RETURN_DOM_FRAGMENT) {
665        RETURN_DOM = true;
666      }
667      /* Parse profile info */
668
669
670      if (USE_PROFILES) {
671        ALLOWED_TAGS = addToSet({}, _toConsumableArray(text));
672        ALLOWED_ATTR = [];
673
674        if (USE_PROFILES.html === true) {
675          addToSet(ALLOWED_TAGS, html$1);
676          addToSet(ALLOWED_ATTR, html);
677        }
678
679        if (USE_PROFILES.svg === true) {
680          addToSet(ALLOWED_TAGS, svg$1);
681          addToSet(ALLOWED_ATTR, svg);
682          addToSet(ALLOWED_ATTR, xml);
683        }
684
685        if (USE_PROFILES.svgFilters === true) {
686          addToSet(ALLOWED_TAGS, svgFilters);
687          addToSet(ALLOWED_ATTR, svg);
688          addToSet(ALLOWED_ATTR, xml);
689        }
690
691        if (USE_PROFILES.mathMl === true) {
692          addToSet(ALLOWED_TAGS, mathMl$1);
693          addToSet(ALLOWED_ATTR, mathMl);
694          addToSet(ALLOWED_ATTR, xml);
695        }
696      }
697      /* Merge configuration parameters */
698
699
700      if (cfg.ADD_TAGS) {
701        if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
702          ALLOWED_TAGS = clone(ALLOWED_TAGS);
703        }
704
705        addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
706      }
707
708      if (cfg.ADD_ATTR) {
709        if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
710          ALLOWED_ATTR = clone(ALLOWED_ATTR);
711        }
712
713        addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
714      }
715
716      if (cfg.ADD_URI_SAFE_ATTR) {
717        addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
718      }
719
720      if (cfg.FORBID_CONTENTS) {
721        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
722          FORBID_CONTENTS = clone(FORBID_CONTENTS);
723        }
724
725        addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
726      }
727      /* Add #text in case KEEP_CONTENT is set to true */
728
729
730      if (KEEP_CONTENT) {
731        ALLOWED_TAGS['#text'] = true;
732      }
733      /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
734
735
736      if (WHOLE_DOCUMENT) {
737        addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
738      }
739      /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
740
741
742      if (ALLOWED_TAGS.table) {
743        addToSet(ALLOWED_TAGS, ['tbody']);
744        delete FORBID_TAGS.tbody;
745      } // Prevent further manipulation of configuration.
746      // Not available in IE8, Safari 5, etc.
747
748
749      if (freeze) {
750        freeze(cfg);
751      }
752
753      CONFIG = cfg;
754    };
755
756    var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
757    var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML
758    // namespace. We need to specify them explicitly
759    // so that they don't get erroneously deleted from
760    // HTML namespace.
761
762    var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
763    /* Keep track of all possible SVG and MathML tags
764     * so that we can perform the namespace checks
765     * correctly. */
766
767    var ALL_SVG_TAGS = addToSet({}, svg$1);
768    addToSet(ALL_SVG_TAGS, svgFilters);
769    addToSet(ALL_SVG_TAGS, svgDisallowed);
770    var ALL_MATHML_TAGS = addToSet({}, mathMl$1);
771    addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
772    /**
773     *
774     *
775     * @param  {Element} element a DOM element whose namespace is being checked
776     * @returns {boolean} Return false if the element has a
777     *  namespace that a spec-compliant parser would never
778     *  return. Return true otherwise.
779     */
780
781    var _checkValidNamespace = function _checkValidNamespace(element) {
782      var parent = getParentNode(element); // In JSDOM, if we're inside shadow DOM, then parentNode
783      // can be null. We just simulate parent in this case.
784
785      if (!parent || !parent.tagName) {
786        parent = {
787          namespaceURI: NAMESPACE,
788          tagName: 'template'
789        };
790      }
791
792      var tagName = stringToLowerCase(element.tagName);
793      var parentTagName = stringToLowerCase(parent.tagName);
794
795      if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
796        return false;
797      }
798
799      if (element.namespaceURI === SVG_NAMESPACE) {
800        // The only way to switch from HTML namespace to SVG
801        // is via <svg>. If it happens via any other tag, then
802        // it should be killed.
803        if (parent.namespaceURI === HTML_NAMESPACE) {
804          return tagName === 'svg';
805        } // The only way to switch from MathML to SVG is via`
806        // svg if parent is either <annotation-xml> or MathML
807        // text integration points.
808
809
810        if (parent.namespaceURI === MATHML_NAMESPACE) {
811          return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
812        } // We only allow elements that are defined in SVG
813        // spec. All others are disallowed in SVG namespace.
814
815
816        return Boolean(ALL_SVG_TAGS[tagName]);
817      }
818
819      if (element.namespaceURI === MATHML_NAMESPACE) {
820        // The only way to switch from HTML namespace to MathML
821        // is via <math>. If it happens via any other tag, then
822        // it should be killed.
823        if (parent.namespaceURI === HTML_NAMESPACE) {
824          return tagName === 'math';
825        } // The only way to switch from SVG to MathML is via
826        // <math> and HTML integration points
827
828
829        if (parent.namespaceURI === SVG_NAMESPACE) {
830          return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
831        } // We only allow elements that are defined in MathML
832        // spec. All others are disallowed in MathML namespace.
833
834
835        return Boolean(ALL_MATHML_TAGS[tagName]);
836      }
837
838      if (element.namespaceURI === HTML_NAMESPACE) {
839        // The only way to switch from SVG to HTML is via
840        // HTML integration points, and from MathML to HTML
841        // is via MathML text integration points
842        if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
843          return false;
844        }
845
846        if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
847          return false;
848        } // We disallow tags that are specific for MathML
849        // or SVG and should never appear in HTML namespace
850
851
852        return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
853      } // For XHTML and XML documents that support custom namespaces
854
855
856      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
857        return true;
858      } // The code should never reach this place (this means
859      // that the element somehow got namespace that is not
860      // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
861      // Return false just in case.
862
863
864      return false;
865    };
866    /**
867     * _forceRemove
868     *
869     * @param  {Node} node a DOM node
870     */
871
872
873    var _forceRemove = function _forceRemove(node) {
874      arrayPush(DOMPurify.removed, {
875        element: node
876      });
877
878      try {
879        // eslint-disable-next-line unicorn/prefer-dom-node-remove
880        node.parentNode.removeChild(node);
881      } catch (_) {
882        
vendor: 7,032 bytes, lines 882-1103
882try {
883          node.outerHTML = emptyHTML;
884        } catch (_) {
885          node.remove();
886        }
887      }
888    };
889    /**
890     * _removeAttribute
891     *
892     * @param  {String} name an Attribute name
893     * @param  {Node} node a DOM node
894     */
895
896
897    var _removeAttribute = function _removeAttribute(name, node) {
898      try {
899        arrayPush(DOMPurify.removed, {
900          attribute: node.getAttributeNode(name),
901          from: node
902        });
903      } catch (_) {
904        arrayPush(DOMPurify.removed, {
905          attribute: null,
906          from: node
907        });
908      }
909
910      node.removeAttribute(name); // We void attribute values for unremovable "is"" attributes
911
912      if (name === 'is' && !ALLOWED_ATTR[name]) {
913        if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
914          try {
915            _forceRemove(node);
916          } catch (_) {}
917        } else {
918          try {
919            node.setAttribute(name, '');
920          } catch (_) {}
921        }
922      }
923    };
924    /**
925     * _initDocument
926     *
927     * @param  {String} dirty a string of dirty markup
928     * @return {Document} a DOM, filled with the dirty markup
929     */
930
931
932    var _initDocument = function _initDocument(dirty) {
933      /* Create a HTML document */
934      var doc;
935      var leadingWhitespace;
936
937      if (FORCE_BODY) {
938        dirty = '<remove></remove>' + dirty;
939      } else {
940        /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
941        var matches = stringMatch(dirty, /^[\r\n\t ]+/);
942        leadingWhitespace = matches && matches[0];
943      }
944
945      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
946        // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
947        dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
948      }
949
950      var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
951      /*
952       * Use the DOMParser API by default, fallback later if needs be
953       * DOMParser not work for svg when has multiple root element.
954       */
955
956      if (NAMESPACE === HTML_NAMESPACE) {
957        try {
958          doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
959        } catch (_) {}
960      }
961      /* Use createHTMLDocument in case DOMParser is not available */
962
963
964      if (!doc || !doc.documentElement) {
965        doc = implementation.createDocument(NAMESPACE, 'template', null);
966
967        try {
968          doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
969        } catch (_) {// Syntax error if dirtyPayload is invalid xml
970        }
971      }
972
973      var body = doc.body || doc.documentElement;
974
975      if (dirty && leadingWhitespace) {
976        body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
977      }
978      /* Work on whole document or just its body */
979
980
981      if (NAMESPACE === HTML_NAMESPACE) {
982        return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
983      }
984
985      return WHOLE_DOCUMENT ? doc.documentElement : body;
986    };
987    /**
988     * _createIterator
989     *
990     * @param  {Document} root document/fragment to create iterator for
991     * @return {Iterator} iterator instance
992     */
993
994
995    var _createIterator = function _createIterator(root) {
996      return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise
997      NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
998    };
999    /**
1000     * _isClobbered
1001     *
1002     * @param  {Node} elm element to check for clobbering attacks
1003     * @return {Boolean} true if clobbered, false if safe
1004     */
1005
1006
1007    var _isClobbered = function _isClobbered(elm) {
1008      return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
1009    };
1010    /**
1011     * _isNode
1012     *
1013     * @param  {Node} obj object to check whether it's a DOM node
1014     * @return {Boolean} true is object is a DOM node
1015     */
1016
1017
1018    var _isNode = function _isNode(object) {
1019      return _typeof(Node) === 'object' ? object instanceof Node : object && _typeof(object) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string';
1020    };
1021    /**
1022     * _executeHook
1023     * Execute user configurable hooks
1024     *
1025     * @param  {String} entryPoint  Name of the hook's entry point
1026     * @param  {Node} currentNode node to work on with the hook
1027     * @param  {Object} data additional hook parameters
1028     */
1029
1030
1031    var _executeHook = function _executeHook(entryPoint, currentNode, data) {
1032      if (!hooks[entryPoint]) {
1033        return;
1034      }
1035
1036      arrayForEach(hooks[entryPoint], function (hook) {
1037        hook.call(DOMPurify, currentNode, data, CONFIG);
1038      });
1039    };
1040    /**
1041     * _sanitizeElements
1042     *
1043     * @protect nodeName
1044     * @protect textContent
1045     * @protect removeChild
1046     *
1047     * @param   {Node} currentNode to check for permission to exist
1048     * @return  {Boolean} true if node was killed, false if left alive
1049     */
1050
1051
1052    var _sanitizeElements = function _sanitizeElements(currentNode) {
1053      var content;
1054      /* Execute a hook if present */
1055
1056      _executeHook('beforeSanitizeElements', currentNode, null);
1057      /* Check if element is clobbered or can clobber */
1058
1059
1060      if (_isClobbered(currentNode)) {
1061        _forceRemove(currentNode);
1062
1063        return true;
1064      }
1065      /* Check if tagname contains Unicode */
1066
1067
1068      if (regExpTest(/[\u0080-\uFFFF]/, currentNode.nodeName)) {
1069        _forceRemove(currentNode);
1070
1071        return true;
1072      }
1073      /* Now let's check the element's type and name */
1074
1075
1076      var tagName = transformCaseFunc(currentNode.nodeName);
1077      /* Execute a hook if present */
1078
1079      _executeHook('uponSanitizeElement', currentNode, {
1080        tagName: tagName,
1081        allowedTags: ALLOWED_TAGS
1082      });
1083      /* Detect mXSS attempts abusing namespace confusion */
1084
1085
1086      if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
1087        _forceRemove(currentNode);
1088
1089        return true;
1090      }
1091      /* Mitigate a problem with templates inside select */
1092
1093
1094      if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
1095        _forceRemove(currentNode);
1096
1097        return true;
1098      }
1099      /* Remove element if anything forbids its presence */
1100
1101
1102      if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1103        /* Check if we have a custom element to handle */
vendor: 1,196 bytes, lines 1104-1135
1104        if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
1105          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1106          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
1107        }
1108        /* Keep content except for bad-listed elements */
1109
1110
1111        if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1112          var parentNode = getParentNode(currentNode) || currentNode.parentNode;
1113          var childNodes = getChildNodes(currentNode) || currentNode.childNodes;
1114
1115          if (childNodes && parentNode) {
1116            var childCount = childNodes.length;
1117
1118            for (var i = childCount - 1; i >= 0; --i) {
1119              parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
1120            }
1121          }
1122        }
1123
1124        _forceRemove(currentNode);
1125
1126        return true;
1127      }
1128      /* Check whether element has a valid namespace */
1129
1130
1131      if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
1132        _forceRemove(currentNode);
1133
1134        return true;
1135      }
vendor: 10,142 bytes, lines 1135-1412
1135
1136
1137      if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
1138        _forceRemove(currentNode);
1139
1140        return true;
1141      }
1142      /* Sanitize element content to be template-safe */
1143
1144
1145      if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
1146        /* Get the element's text content */
1147        content = currentNode.textContent;
1148        content = stringReplace(content, MUSTACHE_EXPR$1, ' ');
1149        content = stringReplace(content, ERB_EXPR$1, ' ');
1150        content = stringReplace(content, TMPLIT_EXPR$1, ' ');
1151
1152        if (currentNode.textContent !== content) {
1153          arrayPush(DOMPurify.removed, {
1154            element: currentNode.cloneNode()
1155          });
1156          currentNode.textContent = content;
1157        }
1158      }
1159      /* Execute a hook if present */
1160
1161
1162      _executeHook('afterSanitizeElements', currentNode, null);
1163
1164      return false;
1165    };
1166    /**
1167     * _isValidAttribute
1168     *
1169     * @param  {string} lcTag Lowercase tag name of containing element.
1170     * @param  {string} lcName Lowercase attribute name.
1171     * @param  {string} value Attribute value.
1172     * @return {Boolean} Returns true if `value` is valid, otherwise false.
1173     */
1174    // eslint-disable-next-line complexity
1175
1176
1177    var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1178      /* Make sure attribute cannot clobber */
1179      if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1180        return false;
1181      }
1182      /* Allow valid data-* attributes: At least one character after "-"
1183          (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1184          XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1185          We don't need to check the value; it's always URI safe. */
1186
1187
1188      if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
1189        if ( // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1190        // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1191        // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1192        _basicCustomElementTest(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || // Alternative, second condition checks if it's an `is`-attribute, AND
1193        // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1194        lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
1195          return false;
1196        }
1197        /* Check value is safe. First, is attr inert? If so, is safe */
1198
1199      } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (!value) ; else {
1200        return false;
1201      }
1202
1203      return true;
1204    };
1205    /**
1206     * _basicCustomElementCheck
1207     * checks if at least one dash is included in tagName, and it's not the first char
1208     * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1209     * @param {string} tagName name of the tag of the node to sanitize
1210     */
1211
1212
1213    var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
1214      return tagName.indexOf('-') > 0;
1215    };
1216    /**
1217     * _sanitizeAttributes
1218     *
1219     * @protect attributes
1220     * @protect nodeName
1221     * @protect removeAttribute
1222     * @protect setAttribute
1223     *
1224     * @param  {Node} currentNode to sanitize
1225     */
1226
1227
1228    var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1229      var attr;
1230      var value;
1231      var lcName;
1232      var l;
1233      /* Execute a hook if present */
1234
1235      _executeHook('beforeSanitizeAttributes', currentNode, null);
1236
1237      var attributes = currentNode.attributes;
1238      /* Check if we have attributes; if not we might have a text node */
1239
1240      if (!attributes) {
1241        return;
1242      }
1243
1244      var hookEvent = {
1245        attrName: '',
1246        attrValue: '',
1247        keepAttr: true,
1248        allowedAttributes: ALLOWED_ATTR
1249      };
1250      l = attributes.length;
1251      /* Go backwards over all attributes; safely remove bad ones */
1252
1253      while (l--) {
1254        attr = attributes[l];
1255        var _attr = attr,
1256            name = _attr.name,
1257            namespaceURI = _attr.namespaceURI;
1258        value = name === 'value' ? attr.value : stringTrim(attr.value);
1259        lcName = transformCaseFunc(name);
1260        /* Execute a hook if present */
1261
1262        hookEvent.attrName = lcName;
1263        hookEvent.attrValue = value;
1264        hookEvent.keepAttr = true;
1265        hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1266
1267        _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
1268
1269        value = hookEvent.attrValue;
1270        /* Did the hooks approve of the attribute? */
1271
1272        if (hookEvent.forceKeepAttr) {
1273          continue;
1274        }
1275        /* Remove attribute */
1276
1277
1278        _removeAttribute(name, currentNode);
1279        /* Did the hooks approve of the attribute? */
1280
1281
1282        if (!hookEvent.keepAttr) {
1283          continue;
1284        }
1285        /* Work around a security issue in jQuery 3.0 */
1286
1287
1288        if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
1289          _removeAttribute(name, currentNode);
1290
1291          continue;
1292        }
1293        /* Sanitize attribute content to be template-safe */
1294
1295
1296        if (SAFE_FOR_TEMPLATES) {
1297          value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
1298          value = stringReplace(value, ERB_EXPR$1, ' ');
1299          value = stringReplace(value, TMPLIT_EXPR$1, ' ');
1300        }
1301        /* Is `value` valid for this attribute? */
1302
1303
1304        var lcTag = transformCaseFunc(currentNode.nodeName);
1305
1306        if (!_isValidAttribute(lcTag, lcName, value)) {
1307          continue;
1308        }
1309        /* Full DOM Clobbering protection via namespace isolation,
1310         * Prefix id and name attributes with `user-content-`
1311         */
1312
1313
1314        if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
1315          // Remove the attribute with this value
1316          _removeAttribute(name, currentNode); // Prefix the value and later re-create the attribute with the sanitized value
1317
1318
1319          value = SANITIZE_NAMED_PROPS_PREFIX + value;
1320        }
1321        /* Handle attributes that require Trusted Types */
1322
1323
1324        if (trustedTypesPolicy && _typeof(trustedTypes) === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1325          if (namespaceURI) ; else {
1326            switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1327              case 'TrustedHTML':
1328                value = trustedTypesPolicy.createHTML(value);
1329                break;
1330
1331              case 'TrustedScriptURL':
1332                value = trustedTypesPolicy.createScriptURL(value);
1333                break;
1334            }
1335          }
1336        }
1337        /* Handle invalid data-* attribute set by try-catching it */
1338
1339
1340        try {
1341          if (namespaceURI) {
1342            currentNode.setAttributeNS(namespaceURI, name, value);
1343          } else {
1344            /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1345            currentNode.setAttribute(name, value);
1346          }
1347
1348          arrayPop(DOMPurify.removed);
1349        } catch (_) {}
1350      }
1351      /* Execute a hook if present */
1352
1353
1354      _executeHook('afterSanitizeAttributes', currentNode, null);
1355    };
1356    /**
1357     * _sanitizeShadowDOM
1358     *
1359     * @param  {DocumentFragment} fragment to iterate over recursively
1360     */
1361
1362
1363    var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1364      var shadowNode;
1365
1366      var shadowIterator = _createIterator(fragment);
1367      /* Execute a hook if present */
1368
1369
1370      _executeHook('beforeSanitizeShadowDOM', fragment, null);
1371
1372      while (shadowNode = shadowIterator.nextNode()) {
1373        /* Execute a hook if present */
1374        _executeHook('uponSanitizeShadowNode', shadowNode, null);
1375        /* Sanitize tags and elements */
1376
1377
1378        if (_sanitizeElements(shadowNode)) {
1379          continue;
1380        }
1381        /* Deep shadow DOM detected */
1382
1383
1384        if (shadowNode.content instanceof DocumentFragment) {
1385          _sanitizeShadowDOM(shadowNode.content);
1386        }
1387        /* Check attributes, sanitize if necessary */
1388
1389
1390        _sanitizeAttributes(shadowNode);
1391      }
1392      /* Execute a hook if present */
1393
1394
1395      _executeHook('afterSanitizeShadowDOM', fragment, null);
1396    };
1397    /**
1398     * Sanitize
1399     * Public method providing core sanitation functionality
1400     *
1401     * @param {String|Node} dirty string or DOM node
1402     * @param {Object} configuration object
1403     */
1404    // eslint-disable-next-line complexity
1405
1406
1407    DOMPurify.sanitize = function (dirty) {
1408      var cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1409      var body;
1410      var importedNode;
1411      var currentNode;
1412      var 
vendor: 4,689 bytes, lines 1412-1569
1412oldNode;
1413      var returnNode;
1414      /* Make sure we have a string to sanitize.
1415        DO NOT return early, as this will return the wrong type if
1416        the user has requested a DOM object rather than a string */
1417
1418      IS_EMPTY_INPUT = !dirty;
1419
1420      if (IS_EMPTY_INPUT) {
1421        dirty = '<!-->';
1422      }
1423      /* Stringify, in case dirty is an object */
1424
1425
1426      if (typeof dirty !== 'string' && !_isNode(dirty)) {
1427        // eslint-disable-next-line no-negated-condition
1428        if (typeof dirty.toString !== 'function') {
1429          throw typeErrorCreate('toString is not a function');
1430        } else {
1431          dirty = dirty.toString();
1432
1433          if (typeof dirty !== 'string') {
1434            throw typeErrorCreate('dirty is not a string, aborting');
1435          }
1436        }
1437      }
1438      /* Check we can run. Otherwise fall back or ignore */
1439
1440
1441      if (!DOMPurify.isSupported) {
1442        if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
1443          if (typeof dirty === 'string') {
1444            return window.toStaticHTML(dirty);
1445          }
1446
1447          if (_isNode(dirty)) {
1448            return window.toStaticHTML(dirty.outerHTML);
1449          }
1450        }
1451
1452        return dirty;
1453      }
1454      /* Assign config vars */
1455
1456
1457      if (!SET_CONFIG) {
1458        _parseConfig(cfg);
1459      }
1460      /* Clean up removed elements */
1461
1462
1463      DOMPurify.removed = [];
1464      /* Check if dirty is correctly typed for IN_PLACE */
1465
1466      if (typeof dirty === 'string') {
1467        IN_PLACE = false;
1468      }
1469
1470      if (IN_PLACE) {
1471        /* Do some early pre-sanitization to avoid unsafe root nodes */
1472        if (dirty.nodeName) {
1473          var tagName = transformCaseFunc(dirty.nodeName);
1474
1475          if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1476            throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1477          }
1478        }
1479      } else if (dirty instanceof Node) {
1480        /* If dirty is a DOM element, append to an empty document to avoid
1481           elements being stripped by the parser */
1482        body = _initDocument('<!---->');
1483        importedNode = body.ownerDocument.importNode(dirty, true);
1484
1485        if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
1486          /* Node is already a body, use as is */
1487          body = importedNode;
1488        } else if (importedNode.nodeName === 'HTML') {
1489          body = importedNode;
1490        } else {
1491          // eslint-disable-next-line unicorn/prefer-dom-node-append
1492          body.appendChild(importedNode);
1493        }
1494      } else {
1495        /* Exit directly if we have nothing to do */
1496        if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes
1497        dirty.indexOf('<') === -1) {
1498          return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1499        }
1500        /* Initialize the document to work on */
1501
1502
1503        body = _initDocument(dirty);
1504        /* Check we have a DOM node from the data */
1505
1506        if (!body) {
1507          return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1508        }
1509      }
1510      /* Remove first element node (ours) if FORCE_BODY is set */
1511
1512
1513      if (body && FORCE_BODY) {
1514        _forceRemove(body.firstChild);
1515      }
1516      /* Get node iterator */
1517
1518
1519      var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
1520      /* Now start iterating over the created document */
1521
1522
1523      while (currentNode = nodeIterator.nextNode()) {
1524        /* Fix IE's strange behavior with manipulated textNodes #89 */
1525        if (currentNode.nodeType === 3 && currentNode === oldNode) {
1526          continue;
1527        }
1528        /* Sanitize tags and elements */
1529
1530
1531        if (_sanitizeElements(currentNode)) {
1532          continue;
1533        }
1534        /* Shadow DOM detected, sanitize it */
1535
1536
1537        if (currentNode.content instanceof DocumentFragment) {
1538          _sanitizeShadowDOM(currentNode.content);
1539        }
1540        /* Check attributes, sanitize if necessary */
1541
1542
1543        _sanitizeAttributes(currentNode);
1544
1545        oldNode = currentNode;
1546      }
1547
1548      oldNode = null;
1549      /* If we sanitized `dirty` in-place, return it. */
1550
1551      if (IN_PLACE) {
1552        return dirty;
1553      }
1554      /* Return sanitized string or DOM */
1555
1556
1557      if (RETURN_DOM) {
1558        if (RETURN_DOM_FRAGMENT) {
1559          returnNode = createDocumentFragment.call(body.ownerDocument);
1560
1561          while (body.firstChild) {
1562            // eslint-disable-next-line unicorn/prefer-dom-node-append
1563            returnNode.appendChild(body.firstChild);
1564          }
1565        } else {
1566          returnNode = body;
1567        }
1568
1569        if (ALLOWED_ATTR.shadowroot
vendor: 4,276 bytes, lines 1569-1717
1569 || ALLOWED_ATTR.shadowrootmod) {
1570          /*
1571            AdoptNode() is not used because internal state is not reset
1572            (e.g. the past names map of a HTMLFormElement), this is safe
1573            in theory but we would rather not risk another attack vector.
1574            The state that is cloned by importNode() is explicitly defined
1575            by the specs.
1576          */
1577          returnNode = importNode.call(originalDocument, returnNode, true);
1578        }
1579
1580        return returnNode;
1581      }
1582
1583      var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1584      /* Serialize doctype if allowed */
1585
1586      if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1587        serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1588      }
1589      /* Sanitize final string template-safe */
1590
1591
1592      if (SAFE_FOR_TEMPLATES) {
1593        serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$1, ' ');
1594        serializedHTML = stringReplace(serializedHTML, ERB_EXPR$1, ' ');
1595        serializedHTML = stringReplace(serializedHTML, TMPLIT_EXPR$1, ' ');
1596      }
1597
1598      return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1599    };
1600    /**
1601     * Public method to set the configuration once
1602     * setConfig
1603     *
1604     * @param {Object} cfg configuration object
1605     */
1606
1607
1608    DOMPurify.setConfig = function (cfg) {
1609      _parseConfig(cfg);
1610
1611      SET_CONFIG = true;
1612    };
1613    /**
1614     * Public method to remove the configuration
1615     * clearConfig
1616     *
1617     */
1618
1619
1620    DOMPurify.clearConfig = function () {
1621      CONFIG = null;
1622      SET_CONFIG = false;
1623    };
1624    /**
1625     * Public method to check if an attribute value is valid.
1626     * Uses last set config, if any. Otherwise, uses config defaults.
1627     * isValidAttribute
1628     *
1629     * @param  {string} tag Tag name of containing element.
1630     * @param  {string} attr Attribute name.
1631     * @param  {string} value Attribute value.
1632     * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
1633     */
1634
1635
1636    DOMPurify.isValidAttribute = function (tag, attr, value) {
1637      /* Initialize shared config vars if necessary. */
1638      if (!CONFIG) {
1639        _parseConfig({});
1640      }
1641
1642      var lcTag = transformCaseFunc(tag);
1643      var lcName = transformCaseFunc(attr);
1644      return _isValidAttribute(lcTag, lcName, value);
1645    };
1646    /**
1647     * AddHook
1648     * Public method to add DOMPurify hooks
1649     *
1650     * @param {String} entryPoint entry point for the hook to add
1651     * @param {Function} hookFunction function to execute
1652     */
1653
1654
1655    DOMPurify.addHook = function (entryPoint, hookFunction) {
1656      if (typeof hookFunction !== 'function') {
1657        return;
1658      }
1659
1660      hooks[entryPoint] = hooks[entryPoint] || [];
1661      arrayPush(hooks[entryPoint], hookFunction);
1662    };
1663    /**
1664     * RemoveHook
1665     * Public method to remove a DOMPurify hook at a given entryPoint
1666     * (pops it from the stack of hooks if more are present)
1667     *
1668     * @param {String} entryPoint entry point for the hook to remove
1669     * @return {Function} removed(popped) hook
1670     */
1671
1672
1673    DOMPurify.removeHook = function (entryPoint) {
1674      if (hooks[entryPoint]) {
1675        return arrayPop(hooks[entryPoint]);
1676      }
1677    };
1678    /**
1679     * RemoveHooks
1680     * Public method to remove all DOMPurify hooks at a given entryPoint
1681     *
1682     * @param  {String} entryPoint entry point for the hooks to remove
1683     */
1684
1685
1686    DOMPurify.removeHooks = function (entryPoint) {
1687      if (hooks[entryPoint]) {
1688        hooks[entryPoint] = [];
1689      }
1690    };
1691    /**
1692     * RemoveAllHooks
1693     * Public method to remove all DOMPurify hooks
1694     *
1695     */
1696
1697
1698    DOMPurify.removeAllHooks = function () {
1699      hooks = {};
1700    };
1701
1702    return DOMPurify;
1703  }
1704
1705  var purify = createDOMPurify();
1706
1707  return purify;
1708
1709}));
1710//# sourceMappingURL=purify.js.map
1711
1712/**
1713 * jQuery fixes
1714 *
1715 * Note: The following analysis and/or changes was required also to be applied in our code base:
1716 *	- Ensure there is no JSONP ajax call
1717 *	- Ensure to not use self closing tag when a closing tag is usally needed.
1718 *		ex: `<div />` need to change for `<div></div>`
1719 *		This is applicable to all tags except the one in this list: area|br|col|embed|hr|img|input|link|meta|param
1720 *	- Review how `<tr>`, `<td>`, `<script>` and `<link>` are inserted with `$()`.
1721 *		Their insersion need to be completed differently like the by using the javascript DOM interface.
1722 *	- All content that related to jQuery DOM manipulation are sanitized with DOMPurify
1723 *	- You can't use jQuery for parsing XML document, you can use DOMParser() as an alternative
1724 *	- Ensure AJAX and fetch are sanitized before they response are used
1725 */
1726/*
1727
1728// Test case for the jQuery fixes bellow
1729
1730Test: jQuery()
1731
1732	- Run in your browser console
1733	> $( "<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>" ).appendTo( "#wb-cont" );
1734	result: No iframe inserted around the heading level 1
1735
1736Test:
1737jQuery.html()
1738jQuery.append()
1739jQuery.prepend()
1740jQuery.before()
1741jQuery.after()
1742
1743	- Use the data-ajax demo page: /demos/data-ajax/data-ajax-en.html
1744	- Insert the following in the ajaxed in file: <p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>
1745	- Check if the dirty HTML are cleaned for the example: "append", "prepend", "before", "after"
1746	- The data-ajax-replace will also test the jQuery.html() function.
1747
1748Test: jQuery.replaceWith()
1749
1750	- Run in your browser console
1751	> $("#wb-cont").replaceWith( "<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>" );
1752	result: No iframe inserted around the heading level 1
1753
1754Test: jQuery.parseHTML()
1755
1756	- Run in your browser console, pass unsafe HTML and it should return sanitized HTML
1757	> jQuery.parseHTML( "<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>" );
1758	result: return an array with 1 element, [ p ]
1759
1760Test: jQuery.htmlPrefilter()
1761
1762	- Run in your browser console
1763	> jQuery.htmlPrefilter( "<tr/>" );
1764	result: return <tr/>
1765
1766Test: jQuery.extend
1767
1768	- Run in your browser console
1769	> var dataFromAPI = JSON.parse( '{ "new": "property", "__proto__": { "polluted": "true" } }' );
1770	> var myObject = jQuery.extend( '{ "new": "property" }', dataFromAPI );
1771	> console.log( myObject.polluted );
1772	result: > undefined
1773
1774*/
1775
1776( function( jQuery, DOMPurify, window ) {
1777"use strict";
1778
1779/**
1780 * Copied from: https://git.drupalcode.org/project/drupal/-/commit/d2f26902ef5d6de58caaf2a7c766eb2115b1c17e
1781 *
1782 * This is almost verbatim copied from jQuery 3.4.0.
1783 *
1784 * Only two minor changes have been made:
1785 * - The call to isFunction() is changed to jQuery.isFunction().
1786 * - The two calls to Array.isArray() is changed to jQuery.isArray().
1787 *
1788 * The above two changes ensure compatibility with all older jQuery versions
1789 * (1.4.4 - 3.3.1) and older browser versions (e.g., IE8).
1790 */
1791jQuery.extend = jQuery.fn.extend = function() {
1792	var options, name, src, copy, copyIsArray, clone,
1793		target = arguments[ 0 ] || {},
1794		i = 1,
1795		length = arguments.length,
1796		deep = false;
1797
1798	// Handle a deep copy situation
1799	if ( typeof target === "boolean" ) {
1800		deep = target;
1801
1802		// Skip the boolean and the target
1803		target = arguments[ i ] || {};
1804		i++;
1805	}
1806
1807	// Handle case when target is a string or something (possible in deep copy)
1808	if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
1809		target = {};
1810	}
1811
1812	// Extend jQuery itself if only one argument is passed
1813	if ( i === length ) {
1814		target = this;
1815		i--;
1816	}
1817
1818	for ( ; i < length; i++ ) {
1819
1820		// Only deal with non-null/undefined values
1821		options = arguments[ i ];
1822		if ( options !== undefined || options !== null ) {
1823
1824			// Extend the base object
1825			for ( name in options ) {
1826				copy = options[ name ];
1827
1828				// Prevent Object.prototype pollution
1829				// Prevent never-ending loop
1830				if ( name === "__proto__" || target === copy ) {
1831					continue;
1832				}
1833
1834				// Recurse if we're merging plain objects or arrays
1835				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
1836					( copyIsArray = jQuery.isArray( copy ) ) ) ) {
1837					src = target[ name ];
1838
1839					// Ensure proper type for the source value
1840					if ( copyIsArray && !jQuery.isArray( src ) ) {
1841						clone = [];
1842					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
1843						clone = {};
1844					} else {
1845						clone = src;
1846					}
1847					copyIsArray = false;
1848
1849					// Never move original objects, clone them
1850					target[ name ] = jQuery.extend( deep, clone, copy );
1851
1852					// Don't bring in undefined values
1853				} else if ( copy !== undefined ) {
1854					target[ name ] = copy;
1855				}
1856			}
1857		}
1858	}
1859
1860	// Return the modified object
1861	return target;
1862};
1863
1864/**
1865 * Security advisories: https://github.com/jquery/jquery/security/advisories/GHSA-gxr4-xjj5-5px2
1866 */
1867jQuery.htmlPrefilter = function( html ) {
1868	return html;
1869};
1870
1871/**
1872 * Security advisories: https://github.com/jquery/jquery/security/advisories/GHSA-jpcq-cgw6-v4j6
1873 *
1874 * This implementation leverage DOMPurify for filtering every string prior DOM manipulation by jQuery
1875 *
1876 */
1877var localParseHTML = jQuery.parseHTML,
1878	append = jQuery.fn.append,
1879	prepend = jQuery.fn.prepend,
1880	before = jQuery.fn.before,
1881	after = jQuery.fn.after,
1882	replaceWith = jQuery.fn.replaceWith,
1883	jqInit = jQuery.fn.init,
1884	dataTableAllowedTag = [
1885		"<tbody/>",
1886		"<tr/>",
1887		"<td />",
1888		"<td/>"
1889	],
1890	sanitize = function( html ) {
1891
1892		// Add an exception for DataTable plugin
1893		if ( window.DataTable && dataTableAllowedTag.indexOf( html ) !== -1 ) {
1894			return html;
1895		}
1896
1897		return DOMPurify.sanitize( html );
1898	};
1899
1900jQuery.parseHTML = function( data, context, keepScripts ) {
1901	return localParseHTML( sanitize( data ), context, keepScripts );
1902};
1903
1904jQuery.domManip = null;
1905
1906jQuery.append = jQuery.fn.append = function() {
1907	var args = arguments,
1908		value = args[ 0 ];
1909	if ( typeof value === "string" ) {
1910		value = sanitize( value );
1911		args[ 0 ] = value;
1912	}
1913	return append.apply( this, args );
1914};
1915
1916jQuery.prepend = jQuery.fn.prepend = function() {
1917	var args = arguments,
1918		value = args[ 0 ];
1919	if ( typeof value === "string" ) {
1920		value = sanitize( value );
1921		args[ 0 ] = value;
1922	}
1923	return prepend.apply( this, args );
1924};
1925
1926jQuery.before = jQuery.fn.before = function() {
1927	var args = arguments,
1928		value = args[ 0 ];
1929	if ( typeof value === "string" ) {
1930		value = sanitize( value );
1931		args[ 0 ] = value;
1932	}
1933	return before.apply( this, args );
1934};
1935
1936jQuery.after = jQuery.fn.after = function() {
1937	var args = arguments,
1938		value = args[ 0 ];
1939	if ( typeof value === "string" ) {
1940		value = sanitize( value );
1941		args[ 0 ] = value;
1942	}
1943	return after.apply( this, args );
1944};
1945
1946jQuery.replaceWith = jQuery.fn.replaceWith = function() {
1947	var args = arguments,
1948		value = args[ 0 ];
1949	if ( typeof value === "string" ) {
1950		value = sanitize( value );
1951		args[ 0 ] = value;
1952	}
1953	return replaceWith.apply( this, args );
1954};
1955
1956jQuery.fn.init = function( selector, context, root ) {
1957	if ( typeof selector === "string" ) {
1958		selector = sanitize( selector );
1959	}
1960	return new jqInit( selector, context, root );
1961};
1962
1963jQuery.html = function( value ) {
1964	return jQuery.html( sanitize( value ) );
1965};
1966
1967} )( jQuery, DOMPurify, window );
1968
1969/* Modernizr (Custom Build) | MIT & BSD
1970 * Build: https://modernizr.com/download/#-elem_details-elem_progress_meter-mathml-cors-load-mq-css3-input-inputtypes-svg-cssclasses-csstransitions-fontface-backgroundsize-borderimage-teststyles-testprops-testallprops-hasevents-prefixes-domprefixes
1971 */
1972;window.Modernizr=function(e,t,n){function L(e){f.cssText=e}function A(e,t){return L(p.join(e+";")+(t||""))}function O(e,t){return typeof e===t}function M(e,t){return!!~(""+e).indexOf(t)}function _(e,t){for(var r in e){var i=e[r];if(!M(i,"-")&&f[i]!==n)return t=="pfx"?i:!0}return!1}function D(e,t,r){for(var i in e){var s=t[e[i]];if(s!==n)return r===!1?e[i]:O(s,"function")?s.bind(r||t):s}return!1}function P(e,t,n){var r=e.charAt(0).toUpperCase()+e.slice(1),i=(e+" "+v.join(r+" ")+r).split(" ");return O(t,"string")||O(t,"undefined")?_(i,t):(i=(e+" "+m.join(r+" ")+r).split(" "),D(i,t,n))}function H(){i.input=function(n){for(var r=0,i=n.length;r<i;r++)w[n[r]]=n[r]in l;return w.list&&(w.list=!!t.createElement("datalist")&&!!e.HTMLDataListElement),w}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),i.inputtypes=function(e){for(var r=0,i,s,u,a=e.length;r<a;r++)l.setAttribute("type",s=e[r]),i=l.type!=="text",i&&(l.value=c,l.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(s)&&l.style.WebkitAppearance!==n?(o.appendChild(l),u=t.defaultView,i=u.getComputedStyle&&u.getComputedStyle(l,null).WebkitAppearance!=="textfield"&&l.offsetHeight!==0,o.removeChild(l)):/^(search|tel)$/.test(s)||(/^(url|email)$/.test(s)?i=l.checkValidity&&l.checkValidity()===!1:i=l.value!=c)),b[e[r]]=!!i;return b}("search tel url email datetime date month week time datetime-local number range color".split(" "))}
1972var r="2.8.3",i={},s=!0,o=t.documentElement,u="modernizr",a=t.createElement(u),f=a.style,l=t.createElement("input"),c=":)",h={}.toString,p=" -webkit- -moz- -o- -ms- ".split(" "),d="Webkit Moz O ms",v=d.split(" "),m=d.toLowerCase().split(" "),g={svg:"http://www.w3.org/2000/svg"},y={},b={},w={},E=[],S=E.slice,x,T=function(e,n,r,i){var s,a,f,l,c=t.createElement("div"),h=t.body,p=h||t.createElement("body");if(parseInt(r,10))while(r--)f=t.createElement("div"),f.id=i?i[r]:u+(r+1),c.appendChild(f);return s=["&#173;",'<style id="s',u,'">',e,"</style>"].join(""),c.id=u,(h?c:p).innerHTML+=s,p.appendChild(c),h||(p.style.background="",p.style.overflow="hidden",l=o.style.overflow,o.style.overflow="hidden",o.appendChild(p)),a=n(c,e),h?c.parentNode.removeChild(c):(p.parentNode.removeChild(p),o.style.overflow=l),!!a},N=function(t){var n=e.matchMedia||e.msMatchMedia;if(n)return n(t)&&n(t).matches||!1;var r;return T("@media "+t+" { #"+u+" { position: absolute; } }",function(t){r=(e.getComputedStyle?getComputedStyle(t,null):t.currentStyle)["position"]=="absolute"}),r},C={}.hasOwnProperty,k;!O(C,"undefined")&&!O(C.call,"undefined")?k=function(e,t){return C.call(e,t)}:k=function(e,t){return t in e&&O(e.constructor.prototype[t],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError;var r=S.call(arguments,1),i=function(){if(this instanceof i){var e=function(){};e.prototype=n.prototype;var s=new e,o=n.apply(s,r.concat(S.call(arguments)));return Object(o)===o?o:s}return n.apply(t,r.concat(S.call(arguments)))};return i}),y.backgroundsize=function(){return P("backgroundSize")},y.borderimage=function(){return P("borderImage")},y.csstransitions=function(){return P("transition")},y.fontface=function(){var e;return T('@font-face {font-family:"font";src:url("https://")}',function(n,r){var i=t.getElementById("smodernizr"),s=i.sheet||i.styleSheet,o=s?s.cssRules&&s.cssRules[0]?s.cssRules[0].cssText:s.cssText||"":"";e=/src/i.test(o)&&o.indexOf(r.split(" ")[0])===0}),e},y.svg=function(){return!!t.createElementNS&&!!t.createElementNS(g.svg,"svg").createSVGRect};for(var B in y)k(y,B)&&(x=B.toLowerCase(),i[x]=y[B](),E.push((i[x]?"":"no-")+x));return i.input||H(),i.addTest=function(e,t){if(typeof e=="object")for(var r in e)k(e,r)&&i.addTest(r,e[r]);else{e=e.toLowerCase();if(i[e]!==n)return i;t=typeof t=="function"?t():t,typeof s!="undefined"&&s&&(o.className+=" "+(t?"":"no-")+e),i[e]=t}return i},L(""),a=l=null,i._version=r,i._prefixes=p,i._domPrefixes=m,i._cssomPrefixes=v,i.mq=N,i.testProp=function(e){return _([e])},i.testAllProps=P,i.testStyles=T,o.className=o.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(s?" js "+E.join(" "):""),i}(this,this.document),function(e,t,n){function r(e){return"[object Function]"==d.call(e)}function i(e){return"string"==typeof e}function s(){}function o(e){return!e||"loaded"==e||"complete"==e||"uninitialized"==e}function u(){var e=v.shift();m=1,e?e.t?h(function(){("c"==e.t?k.injectCss:k.injectJs)(e.s,0,e.a,e.x,e.e,1)},0):(e(),u()):m=0}function a(e,n,r,i,s,a,f){function l(t){if(!d&&o(c.readyState)&&(w.r=d=1,!m&&u(),c.onload=c.onreadystatechange=null,t)){"img"!=e&&h(function(){b.removeChild(c)},50);for(var r in T[n])T[n].hasOwnProperty(r)&&T[n][r].onload()}}var f=f||k.errorTimeout,c=t.createElement(e),d=0,g=0,w={t:r,s:n,e:s,a:a,x:f};1===T[n]&&(g=1,T[n]=[]),"object"==e?c.data=n:(c.src=n,c.type=e),c.width=c.height="0",c.onerror=c.onload=c.onreadystatechange=function(){l.call(this,g)},v.splice(i,0,w),"img"!=e&&(g||2===T[n]?(b.insertBefore(c,y?null:p),h(l,f)):T[n].push(c))}function f(e,t,n,r,s){return m=0,t=t||"j",i(e)?a("c"==t?E:w,e,t,this.i++,n,r,s):(v.splice(this.i++,0,e),1==v.length&&u()),this}function l(){var e=k;return e.loader={load:f,i:0},e}var c=t.documentElement,h=e.setTimeout,p=t.getElementsByTagName("script")[0],d={}.toString,v=[],m=0,g="MozAppearance"in c.style,y=g&&!!t.createRange().compareNode,b=y?c:p.parentNode,c=e.opera&&"[object Opera]"==d.call(e.opera),c=!!t.attachEvent&&!c,w=g?"object":c?"script":"img",E=c?"script":w,S=Array.isArray||function(e){return"[object Array]"==d.call(e)},x=[],T={},N={timeout:function(e,t){return t.length&&(e.timeout=t[0]),e}},C,k;k=function(e){function t(e){var e=e.split("!"),t=x.length,n=e.pop(),r=e.length,n={url:n,origUrl:n,prefixes:e},i,s,o;for(s=0;s<r;s++)o=e[s].split("="),(i=N[o.shift()])&&(n=i(n,o));for(s=0;s<t;s++)n=x[s](n);return n}function o(e,i,s,o,u){var a=t(e),f=a.autoCallback;a.url.split(".").pop().split("?").shift(),a.bypass||(i&&(i=r(i)?i:i[e]||i[o]||i[e.split("/").pop().split("?")[0]]),a.instead?a.instead(e,i,s,o,u):(T[a.url]?a.noexec=!0:T[a.url]=1,s.load(a.url,a.forceCSS||!a.forceJS&&"css"==a.url.split(".").pop().split("?").shift()?"c":n,a.noexec,a.attrs,a.timeout),(r(i)||r(f))&&s.load(function(){l(),i&&i(a.origUrl,u,o),f&&f(a.origUrl,u,o),T[a.url]=2})))}function u(e,t){function n(e,n){if(e){if(i(e))n||(f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}),o(e,f,t,0,u);else if(Object(e)===e)for(p in h=function(){var t=0,n;for(n in e)e.hasOwnProperty(n)&&t++;return t}(),e)e.hasOwnProperty(p)&&(!n&&!--h&&(r(f)?f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}:f[p]=function(e){return function(){var t=[].slice.call(arguments);e&&e.apply(this,t),c()}}(l[p])),o(e[p],f,t,p,u))}else!n&&c()}var u=!!e.test,a=e.load||e.both,f=e.callback||s,l=f,c=e.complete||s,h,p;n(u?e.yep:e.nope,!!a),a&&n(a)}var a,f,c=this.yepnope.loader;if(i(e))o(e,0,c,0);else if(S(e))for(a=0;a<e.length;a++)f=e[a],i(f)?o(f,0,c,0):S(f)?k(f):Object(f)===f&&u(f,c);else Object(e)===e&&u(e,c)}
1972,k.addPrefix=function(e,t){N[e]=t},k.addFilter=function(e){x.push(e)},k.errorTimeout=1e4,null==t.readyState&&t.addEventListener&&(t.readyState="loading",t.addEventListener("DOMContentLoaded",C=function(){t.removeEventListener("DOMContentLoaded",C,0),t.readyState="complete"},0)),e.yepnope=l(),e.yepnope.executeStack=u,e.yepnope.injectJs=function(e,n,r,i,a,f){var l=t.createElement("script"),c,d,i=i||k.errorTimeout;l.src=e;for(d in r)l.setAttribute(d,r[d]);n=f?u:n||s,l.onreadystatechange=l.onload=function(){!c&&o(l.readyState)&&(c=1,n(),l.onload=l.onreadystatechange=null)},h(function(){c||(c=1,n(1))},i),a?l.onload():p.parentNode.insertBefore(l,p)},e.yepnope.injectCss=function(e,n,r,i,o,a){var i=t.createElement("link"),f,n=a?u:n||s;i.href=e,i.rel="stylesheet",i.type="text/css";for(f in r)i.setAttribute(f,r[f]);o||(p.parentNode.insertBefore(i,p),h(n,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))},Modernizr.addTest("details",function(){var e=document,t=e.createElement("details"),n,r,i;return"open"in t?(r=e.body||function(){var t=e.documentElement;return n=!0,t.insertBefore(e.createElement("body"),t.firstElementChild||t.firstChild)}(),t.innerHTML="<summary>a</summary>b",t.style.display="block",r.appendChild(t),i=t.offsetHeight,t.open=!0,i=i!=t.offsetHeight,r.removeChild(t),n&&r.parentNode.removeChild(r),i):!1}),Modernizr.addTest("progressbar",function(){return document.createElement("progress").max!==undefined}),Modernizr.addTest("meter",function(){return document.createElement("meter").max!==undefined}),Modernizr.addTest("mathml",function(){var e=!1;if(document.createElementNS){var t="http://www.w3.org/1998/Math/MathML",n=document.createElement("div");n.style.position="absolute";var r=n.appendChild(document.createElementNS(t,"math")).appendChild(document.createElementNS(t,"mfrac"));r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("xx")),r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("yy")),document.body.appendChild(n),e=n.offsetHeight>n.offsetWidth}return e}),Modernizr.addTest("cors",!!(window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest));
1973
1974/**
1975 * String.prototype.includes() polyfill
1976 * @author Ricokola
1977 * @license MIT
1978 */
1979if ( !String.prototype.includes ) {
1980	String.prototype.includes = function( string ) {
1981
1982		return this.indexOf( string ) !== -1;
1983
1984	};
1985}
1986
1987/**
1988 * String.prototype.replaceAll() polyfill
1989 * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
1990 * @author Chris Ferdinandi
1991 * @license MIT
1992 */
1993if ( !String.prototype.replaceAll ) {
1994	String.prototype.replaceAll = function( str, newStr ) {
1995
1996		// If a regex pattern
1997		if ( Object.prototype.toString.call( str ).toLowerCase() === "[object regexp]" ) {
1998			return this.replace( str, newStr );
1999		}
2000
2001		// If a string
2002		return this.replace( new RegExp( str, "g" ), newStr );
2003
2004	};
2005}
2006
2007/**
2008 * @title WET-BOEW Vapour loader
2009 * @overview Helper methods for WET
2010 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
2011 * @author WET Community
2012 */
2013/*
2014 * Vapour Object that will store tombstone data for plugins to leverage
2015 */
2016( function( $, window, document, undef ) {
2017"use strict";
2018
2019/**
2020 * @method getUrlParts
2021 * @param {String} url of URL to examine
2022 * @return {Object} of the parts of passed URL
2023 */
2024var getUrlParts = function( url ) {
2025		var a = document.createElement( "a" );
2026		a.href = url;
2027		return {
2028			href: a.href,
2029			absolute: a.href,
2030			host: a.host,
2031			hostname: a.hostname,
2032			port: a.port,
2033			pathname: a.pathname.replace( /^([^/])/, "/$1" ), // Prefix pathname with a slash in browsers that don't natively do it (i.e. all versions of IE and possibly early versions of Edge). See pull request #8110.
2034			protocol: a.protocol,
2035			hash: a.hash,
2036			search: a.search,
2037
2038			// A collection of the parameters of the query string part of the URL.
2039			params: ( function() {
2040				var queryString = a.search.replace( /(^\?)/, "" );
2041				if ( !queryString ) {
2042					return {};
2043				}
2044				return queryString.split( "&" ).map( function( n ) {
2045					return ( n = n.split( "=" ), this[ n[ 0 ] ] = decodeURIComponent( n[ 1 ] ), this );
2046				}.bind( {} ) )[ 0 ];
2047			}() )
2048		};
2049	},
2050
2051	/**
2052	 * @variable seed
2053	 * @return a unique number for auto-generating ids
2054	 */
2055	seed = 0,
2056
2057	/**
2058	 * @variable $src
2059	 * @return {jQuery Element} of wb script element
2060	 */
2061	$src = $( "script[src*='wet-boew.js'],script[src*='wet-boew.min.js'],script[data-wb-core]" )
2062		.last(),
2063
2064	/**
2065	 * @variable i18n
2066	 * @return {string} of WET document language
2067	 */
2068	lang = ( function( ele ) {
2069		let lang = document.documentElement.lang;
2070		const shortLangLength = 2;
2071
2072		// Perform extra checks if the page uses a long language code
2073		if ( lang.length > shortLangLength ) {
2074			let longLangs = [ "pt-BR", "zh-Hans" ]; // Built-in long language codes
2075
2076			// Check if any custom long language codes have been specified
2077			// Specify by adding data-wb-core and data-lang-long="en-CA en-US etc" attributes to WET's script element (e.g. wet-boew.js or wet-boew.min.js)
2078			if ( ele[ 0 ].hasAttribute( "data-wb-core" ) &&  ele[ 0 ].hasAttribute( "data-lang-long" ) ) {
2079				const longLangsCustom = ele.attr( "data-lang-long" ).split( " " );
2080
2081				// Add extra language codes to the beginning of the longLangs array to match them more quickly
2082				longLangs = longLangsCustom.concat( longLangs );
2083			}
2084
2085			// Shorten the language code if it doesn't exist in the longLangs array
2086			if ( longLangs.indexOf( lang ) === -1 ) {
2087				lang = lang.substring( 0, shortLangLength );
2088			}
2089		}
2090
2091		return lang;
2092	}( $src ) ),
2093
2094	paths = ( function( ele ) {
2095		var paths = {};
2096
2097		paths.home = ele.prop( "src" )
2098			.split( "?" )[ 0 ].split( "/" )
2099			.slice( 0, -1 )
2100			.join( "/" );
2101		paths.asset = paths.home + "/../assets";
2102		paths.template = paths.home + "/assets/templates";
2103		paths.dep = paths.home + "/deps";
2104		paths.js = paths.home;
2105		paths.css = paths.home.substring( 0, paths.home.length - 2 ) + "css";
2106		paths.mode = ele.prop( "src" ).indexOf( ".min" ) < 0 ? "" : ".min";
2107
2108		if ( ele[ 0 ].hasAttribute( "data-wb-core" ) ) {
2109			$.extend( paths, {
2110				home: ele.attr( "data-home" ),
2111				asset: ele.attr( "data-asset" ),
2112				template: ele.attr( "data-template" ),
2113				dep: ele.attr( "data-dep" ),
2114				js: ele.attr( "data-js" ),
2115				css: ele.attr( "data-css" ),
2116				mode: ele.attr( "data-mode" )
2117			} );
2118		}
2119
2120		return paths;
2121	}( $src ) ),
2122
2123	/**
2124	 * @variable oldie
2125	 * @return {integer} of IE version
2126	 */
2127	oldie = ( function() {
2128		var undef,
2129			v = 3,
2130			div = document.createElement( "div" ),
2131			all = div.getElementsByTagName( "i" );
2132
2133		while ( (
2134			div.innerHTML = "<!--[if gt IE " + ( v += 1 ) + "]><i></i><![endif]-->",
2135			all[ 0 ]
2136		) ) { /* empty */ }
2137
2138		return v > 4 ? v : undef;
2139	}() ),
2140
2141	/**
2142	 * @variable currentpage
2143	 * @return {Object} of parts of the current page URL
2144	 */
2145	currentpage = getUrlParts( window.location.href ),
2146
2147	/**
2148	 * @variable disabled
2149	 * @return {boolean} of state of disabled flag
2150	 */
2151	disabled = ( function() {
2152		var disabledSaved = "false",
2153			disabled;
2154
2155		try {
2156			disabledSaved = localStorage.getItem( "wbdisable" ) || disabledSaved;
2157		} catch ( e ) {
2158
2159			/* swallow error */
2160		}
2161
2162		disabled = currentpage.params.wbdisable || disabledSaved;
2163		return ( typeof disabled === "string" ) ? ( disabled.toLowerCase() === "true" ) : Boolean( disabled );
2164	}() ),
2165
2166	/*-----------------------------
2167	 * Core Library Object
2168	 *-----------------------------
2169	 */
2170	wb = {
2171		"/": paths.home,
2172		"/assets": paths.asset,
2173		"/templates": paths.template,
2174		"/deps": paths.dep,
2175		lang: lang,
2176		mode: paths.mode,
2177		doc: $( document ),
2178		win: $( window ),
2179		html: $( "html" ),
2180		pageUrlParts: currentpage,
2181		getUrlParts: getUrlParts,
2182		isDisabled: disabled,
2183		isStarted: false,
2184		isReady: false,
2185		ignoreHashChange: false,
2186		initQueue: 0,
2187
2188		getPath: function( property ) {
2189			return Object.prototype.hasOwnProperty.call( this, property ) ? this[ property ] : undef;
2190		},
2191
2192		getMode: function() {
2193			return this.mode;
2194		},
2195
2196		getId: function() {
2197			return "wb-auto-" + ( seed += 1 );
2198		},
2199
2200		init: function( event, componentName, selector, noAutoId ) {
2201			var	eventTarget = event.target,
2202				isEvent = !!eventTarget,
2203				node = isEvent ? eventTarget : event,
2204				initedClass = componentName + "-inited",
2205				isDocumentNode = node === document;
2206
2207			// Filter out any events triggered by descendants and only initializes
2208			// the element once (if is an event and document node is not the target)
2209			if ( !isEvent || isDocumentNode || ( event.currentTarget === node &&
2210				node.className.indexOf( initedClass ) === -1 ) ) {
2211
2212				this.initQueue += 1;
2213				this.remove( selector );
2214				if ( !isDocumentNode ) {
2215					node.className += " " + initedClass;
2216
2217					if ( !noAutoId && !node.id ) {
2218						node.id = wb.getId();
2219					}
2220				}
2221
2222				return node;
2223			}
2224
2225			return undef;
2226		},
2227
2228		ready: function( $elm, componentName, context ) {
2229			if ( $elm ) {
2230
2231				// Trigger any nested elements (excluding nested within nested)
2232				$elm
2233					.find( wb.allSelectors )
2234					.addClass( "wb-init" )
2235					.filter( ":not(#" + $elm.attr( "id" ) + " .wb-init .wb-init)" )
2236					.trigger( "timerpoke.wb" );
2237
2238				// Identify that the component is ready
2239				$elm.trigger( "wb-ready." + componentName, context );
2240				this.initQueue -= 1;
2241			} else {
2242				this.doc.trigger( "wb-ready." + componentName, context );
2243			}
2244
2245			// Identify that global initialization is complete
2246			if ( !this.isReady && this.isStarted && this.initQueue < 1 ) {
2247				this.isReady = true;
2248				this.doc.trigger( "wb-ready.wb" );
2249			}
2250		},
2251
2252		// Lets load some variables into wb for IE detection
2253		other: !oldie,
2254		desktop: ( window.orientation === undefined ),
2255		ie: !!oldie,
2256		ie6: ( oldie === 6 ),
2257		ie7: ( oldie === 7 ),
2258		ie8: ( oldie === 8 ),
2259		ie9: ( oldie === 9 ),
2260		ielt7: ( oldie < 7 ),
2261		ielt8: ( oldie < 8 ),
2262		ielt9: ( oldie < 9 ),
2263		ielt10: ( oldie < 10 ),
2264		ie11: ( navigator.userAgent.includes( "Trident/7." ) ),
2265
2266		selectors: [],
2267
2268		resizeEvents: "xxsmallview.wb xsmallview.wb smallview.wb mediumview.wb largeview.wb xlargeview.wb",
2269
2270		// For Charts and Geomap
2271		drawColours: [
2272			"#8d201c",
2273			"#EE8310",
2274			"#2a7da6",
2275			"#5a306b",
2276			"#285228",
2277			"#154055",
2278			"#555555",
2279			"#f6d200",
2280			"#d73d38",
2281			"#418541",
2282			"#87aec9",
2283			"#23447e",
2284			"#999999"
2285		],
2286
2287		// Get and generate a unique session id
2288		sessionGUID: function() {
2289			var sessionId = sessionStorage.getItem( "wb-session-GUID" );
2290			if ( !sessionId ) {
2291				sessionId = wb.guid();
2292				sessionStorage.setItem( "wb-session-GUID", sessionId );
2293			}
2294			return sessionId;
2295		},
2296
2297		// Add a selector to be targeted by timerpoke
2298		add: function( selector ) {
2299			var exists = false,
2300				len = wb.selectors.length,
2301				i;
2302
2303			// Lets ensure we are not running if things are disabled
2304			if ( wb.isDisabled && selector !== "#wb-tphp" ) {
2305				return 0;
2306			}
2307
2308			// Check to see if the selector is already targeted
2309			for ( i = 0; i !== len; i += 1 ) {
2310				if ( wb.selectors[ i ] === selector ) {
2311					exists = true;
2312					break;
2313				}
2314			}
2315
2316			// Add the selector if it isn't already targeted
2317			if ( !exists ) {
2318				wb.selectors.push( selector );
2319			}
2320		},
2321
2322		// Remove a selector targeted by timerpoke
2323		remove: function( selector ) {
2324			var len = this.selectors.length,
2325				i;
2326
2327			for ( i = 0; i !== len; i += 1 ) {
2328				if ( this.selectors[ i ] === selector ) {
2329					this.selectors.splice( i, 1 );
2330					break;
2331				}
2332			}
2333		},
2334
2335		// Handles triggering of timerpoke events
2336		timerpoke: function( initial ) {
2337			var selectorsLocal = wb.selectors.slice( 0 ),
2338				len = selectorsLocal.length,
2339				selector, $elms, $foundElms, i;
2340
2341			if ( initial ) {
2342				$foundElms = $();
2343				for ( i = 0; i !== len; i += 1 ) {
2344					selector = selectorsLocal[ i ];
2345					$elms = $( selector );
2346					if ( $elms.length !== 0 ) {
2347						$foundElms = $foundElms.add( $elms );
2348
2349					// If the selector returns no elements, remove the selector
2350					} else {
2351						wb.remove( selector );
2352					}
2353				}
2354
2355				// Keep only the non-nested plugin/polyfill elements
2356				$elms = $foundElms.filter( ":not(.wb-init .wb-init)" ).addClass( "wb-init" );
2357			} else {
2358				$elms = $( selectorsLocal.join( ", " ) );
2359			}
2360			$elms.trigger( "timerpoke.wb" );
2361		},
2362
2363		start: function() {
2364
2365			// Save a copy of all the possible selectors
2366			wb.allSelectors = wb.selectors.join( ", " );
2367
2368			// Initiate timerpoke events right way
2369			wb.timerpoke( true );
2370			this.isStarted = true;
2371			this.ready();
2372
2373			// Initiate timerpoke events again every half second
2374			setInterval( wb.timerpoke, 500 );
2375		},
2376
2377		i18nDict: {},
2378		i18n: function( key, state, mixin ) {
2379			var dictionary = wb.i18nDict,
2380
2381				// eg. 000 or 001 ie. 0 or 1
2382				truthiness = ( typeof key === "string" && key !== "" ) |
2383
2384					// eg. 000 or 010 ie. 0 or 2
2385					( typeof state === "string" && state !== "" ) << 1 |
2386
2387					// eg. 000 or 100 ie. 0 or 4
2388					( typeof mixin === "string" && mixin !== "" ) << 2;
2389
2390			switch ( truthiness ) {
2391			case 1:
2392
2393				// only key was provided
2394				return dictionary[ key ];
2395
2396			case 3:
2397
2398				// key and state were provided
2399				return dictionary[ key ][ state ];
2400
2401			case 7:
2402
2403				// key, state, and mixin were provided
2404				return dictionary[ key ][ state ].replace( "[MIXIN]", mixin );
2405			default:
2406				return "";
2407			}
2408		},
2409
2410		hashString: function( str ) {
2411
2412			// Sources:
2413			//	https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
2414			//	https://jsperf.com/hashing-strings
2415			var hash = 0,
2416				chr, i;
2417
2418			if ( str.length === 0 ) {
2419				return hash;
2420			}
2421
2422			for ( i = 0; i < str.length; i++ ) {
2423				chr = str.charCodeAt( i );
2424				hash = ( ( hash << 5 ) - hash ) + chr;
2425
2426				// Convert to 32bit integer
2427				hash = hash & hash;
2428			}
2429
2430			return hash;
2431		},
2432
2433		stripWhitespace: function( str ) {
2434			return str.replace( /\s+/g, "" );
2435		},
2436
2437		// Core function to deal with the dependency racing issue
2438		whenLibReady: function( testCallback, readyCallback ) {
2439			if ( testCallback() ) {
2440				readyCallback();
2441			} else {
2442				setTimeout( function() {
2443					wb.whenLibReady( testCallback, readyCallback );
2444				}, 50 );
2445			}
2446
2447		}
2448	};
2449
2450window.wb = wb;
2451
2452/*-----------------------------
2453 * Yepnope Prefixes
2454 *-----------------------------*/
2455/*
2456 * Establish the base path to be more flexible in terms of WCMS where JS can
2457 * reside in theme folders and not in the root of sites
2458 * @TODO: For modularity the prefixes where written independently as we are
2459 * flushing out some use cases on better grouping and optimization of polyfills.
2460 * Once this more hashed out, we could optimize the prefixes down to one or tw
2460o
2461 * prefixes "site" and "disabled" to thin out the codeblock a bit more
2462 * increase performance due to redundant chaining of the prefixes.
2463 */
2464
2465/**
2466 * @prefix: site! - adds the root js directory of yepnope resources
2467 */
2468yepnope.addPrefix( "site", function( resourceObj ) {
2469	resourceObj.url = paths.js + "/" + resourceObj.url;
2470	return resourceObj;
2471} );
2472
2473/**
2474 * @prefix: plyfll! - builds the path for the polyfill resource
2475 */
2476yepnope.addPrefix( "plyfll", function( resourceObj ) {
2477	var path,
2478		url = resourceObj.url;
2479
2480	if ( disabled && url.indexOf( "svg" ) === -1 ) {
2481		resourceObj.bypass = true;
2482	} else if ( !paths.mode ) {
2483		url = url.replace( ".min", "" );
2484	}
2485
2486	if ( url.indexOf( ".css" ) !== -1 ) {
2487		resourceObj.forceCSS = true;
2488		path = paths.css;
2489	} else {
2490		path = paths.js;
2491	}
2492	resourceObj.url = path + "/polyfills/" + url;
2493
2494	return resourceObj;
2495} );
2496
2497/**
2498 * @prefix: i18n! - adds the correct document language for our i18n library
2499 */
2500yepnope.addPrefix( "i18n", function( resourceObj ) {
2501	resourceObj.url = paths.js + "/" + resourceObj.url + lang + paths.mode + ".js";
2502	return resourceObj;
2503} );
2504
2505/**
2506 * @prefix: mthjx! - adds the root directory of MathJax resources
2507 */
2508yepnope.addPrefix( "mthjx", function( resourceObj ) {
2509	resourceObj.url = paths.js + "/MathJax/" + resourceObj.url;
2510	return resourceObj;
2511} );
2512
2513/*-----------------------------
2514 * Deps loading, call "complete" callback when the deps is ready if a testReady is defined
2515 *-----------------------------*/
2516wb.modernizrLoad = Modernizr.load;
2517Modernizr.load = function( options ) {
2518	var i, i_len, i_cache,
2519		testReady, complete;
2520	if ( !$.isArray( options ) ) {
2521		options = [ options ];
2522	}
2523	i_len = options.length;
2524	for ( i = 0; i !== i_len; i += 1 ) {
2525		i_cache = options[ i ];
2526		testReady = i_cache.testReady;
2527		complete = i_cache.complete;
2528		if ( testReady && complete ) {
2529			i_cache.complete = wb.whenLibReady( testReady, complete );
2530		}
2531	}
2532	wb.modernizrLoad( options );
2533};
2534
2535/*-----------------------------
2536 * Modernizr Polyfill Loading
2537 *-----------------------------*/
2538Modernizr.load( [
2539	{
2540		test: Modernizr.details,
2541		nope: [
2542			"plyfll!details.min.js",
2543			"plyfll!details.min.css"
2544		]
2545	}, {
2546		test: Modernizr.input.list,
2547		nope: [
2548			"plyfll!datalist.min.js",
2549			"plyfll!datalist.min.css"
2550		]
2551	}, {
2552		test: Modernizr.inputtypes.date,
2553		nope: [
2554			"plyfll!datepicker.min.js",
2555			"plyfll!datepicker.min.css"
2556		]
2557	}, {
2558		test: Modernizr.inputtypes.range,
2559		nope: [
2560			"plyfll!slider.min.js",
2561			"plyfll!slider_wrapper.min.js",
2562			"plyfll!slider.min.css"
2563		],
2564		callback: function( url ) {
2565			if ( url === "slider.min.js" ) {
2566				window.fdSlider.onDomReady();
2567			}
2568		}
2569	}, {
2570		test: Modernizr.progressbar,
2571		nope: [
2572			"plyfll!progress.min.js",
2573			"plyfll!progress.min.css"
2574		]
2575	}, {
2576		test: Modernizr.mathml,
2577
2578		// Cleanup Modernizr test and add selector to global timer
2579		complete: function() {
2580			var	componentName = "wb-math",
2581				selector = "math",
2582				math = document.getElementsByTagName( selector ),
2583				$document = wb.doc;
2584
2585			// Cleanup elements that Modernizr.mathml test leaves behind.
2586			if ( math.length ) {
2587				document.body.removeChild( math[ math.length - 1 ].parentNode );
2588			}
2589
2590			// Defer loading the polyfill till an element is detected due to the size
2591			if ( !Modernizr.mathml ) {
2592				let isTrident = new Boolean( window.navigator.msSaveOrOpenBlob );
2593
2594				// Bind the init event of the plugin
2595				$document.one( "timerpoke.wb wb-init." + componentName, selector, function() {
2596
2597					// Start initialization
2598					wb.init( document, componentName, selector );
2599
2600					// Disable MathJax's context menu to more closely mimic native MathML implementations
2601					window.MathJax = {
2602						options: {
2603							enableMenu: false
2604						}
2605					};
2606
2607					// Extra tasks for IE11
2608					if ( isTrident ) {
2609
2610						// Load an ES6 polyfill
2611						Modernizr.load( "timeout=500!https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6" );
2612
2613						// Specify the CDN's font URL
2614						// Note: IE11 is unable to resolve this on its own
2615						window.MathJax.chtml = {
2616							fontURL: "https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2"
2617						};
2618					}
2619
2620					// Load the MathML dependency. Since the polyfill is only loaded
2621					// when !Modernizr.mathml, we can skip the test here.
2622					Modernizr.load( [ {
2623
2624						// Load latest version of MathJax 3 from a CDN
2625						// Also load a CSS workaround for a MathJax 3.2.0 bug (refer to CSS file for details)
2626						load: [
2627							"timeout=500!https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js",
2628							"plyfll!mathml.min.css"
2629						],
2630						complete: function() {
2631
2632							// Wait a moment to reduce the risk of a race condition
2633							setTimeout( function() {
2634
2635								// Specify a font URL for a local copy of MathJax 3 for IE11
2636								// Note: Useful if IE11 has internet access but fails to reach the CDN
2637								if ( isTrident && !window.MathJax.startup ) {
2638									window.MathJax.chtml.fontURL = paths.js + "/MathJax/output/chtml/fonts/woff-v2";
2639								}
2640
2641								// Fall back on a local copy of MathJax 3 if the CDN is unreachable
2642								// Note: Won't work with IE11 in isolated networks (ES6 polyfill has no local fallback)
2643								Modernizr.load( [ {
2644									test: window.MathJax.startup,
2645									nope: "mthjx!mml-chtml.js",
2646									complete: function() {
2647
2648										// Try loading a local copy of MathJax 2 as a last dith effort
2649										Modernizr.load( [ {
2650											test: window.MathJax.startup,
2651											nope: "mthjx!MathJax.js?config=Accessible",
2652											complete: function() {
2653
2654												// Identify that initialization has completed
2655												wb.ready( $document, componentName );
2656											}
2657										} ] );
2658									}
2659								} ] );
2660							}, 100 );
2661						}
2662					} ] );
2663				} );
2664
2665				wb.add( selector );
2666			}
2667		}
2668	}, {
2669		test: Modernizr.meter,
2670		nope: [
2671			"plyfll!meter.min.js",
2672			"plyfll!meter.min.css"
2673		]
2674	}, {
2675		test: Modernizr.touch,
2676		yep: "plyfll!mobile.min.js"
2677	}, {
2678		test: Modernizr.svg,
2679		nope: "plyfll!svg.min.js"
2680	}, {
2681		load: "i18n!i18n/",
2682		testReady: function() {
2683			return wb.i18nDict.tphp;
2684		},
2685		complete: function() {
2686			wb.start();
2687		}
2688	}
2689] );
2690
2691} )( jQuery, window, document );
2692
2693/**
2694 * @title WET-BOEW JQuery Helper Methods
2695 * @overview Helper methods for WET
2696 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
2697 * @author WET Community
2698 * Credits: https://web.archive.org/web/20130826230640/http://kaibun.net/blog/2013/04/19/a-fully-fledged-coffeescript-boilerplate-for-jquery-plugins/
2699 */
2700( function( $, wb ) {
2701
2702wb.getData = function( element, dataName ) {
2703	var elm = !element.jquery ? element : element[ 0 ],
2704		dataAttr = elm.getAttribute( "data-" + dataName ),
2705		dataObj;
2706
2707	if ( dataAttr ) {
2708		try {
2709			dataObj = JSON.parse( dataAttr );
2710			$.data( elm, dataName, dataObj );
2711		} catch ( error ) {
2712			console.info( elm );
2713			$.error( "Bad JSON array in data-" + dataName + " attribute" );
2714		}
2715	}
2716
2717	return dataObj;
2718};
2719
2720/*
2721 * Initiate an in-browser download from a blob
2722 * @param blob: a reference to a blob object
2723 * @param filename: a suggested file name to save as under
2724 * @param title: (Optional) a title added to the link. Its use case is for web analytics tracking.
2725*/
2726wb.download = function( blob, filename, title ) {
2727
2728	var objectURL = URL.createObjectURL( blob ),
2729		anchor = document.createElement( "a" );
2730
2731	filename = filename || "unnamed"; // Ensure a filename is defined
2732
2733	anchor.textContent = title || "";
2734	anchor.download = filename;
2735
2736	anchor.hidden = true;
2737	document.body.appendChild( anchor ); // Added to the body for the web analytic tracking use case.
2738
2739	if ( window.navigator.msSaveOrOpenBlob ) {
2740
2741		// This is for IE11 support
2742		anchor.addEventListener( "click", function( ) {
2743			window.navigator.msSaveOrOpenBlob( blob, filename );
2744		} );
2745		anchor.setAttribute( "target", "_blank" );
2746	} else {
2747		anchor.href = objectURL;
2748	}
2749
2750	anchor.click();
2751
2752	// Clean the DOM, remove the accessory anchor at the next tick
2753	setTimeout( function() {
2754		document.body.removeChild( anchor );
2755	}, 1 );
2756
2757	// Revoke the ojbect, A setTimeout is used because Blob API don't have a download complete event.
2758	setTimeout( function() {
2759		if ( typeof objectURL === "string" ) {
2760			URL.revokeObjectURL( objectURL );
2761		} else {
2762			objectURL.remove();
2763		}
2764	}, 40000 ); // The revoking time is arbitrary
2765
2766};
2767
2768/* ---------------------------------
2769@extension: shuffleDOM
2770@returns: [list] shuffles a list of items randomly
2771-------------------------------- */
2772wb.shuffleDOM = function( $elm ) {
2773	var allElems = $elm.get(),
2774		shuffled = $.map( allElems, function() {
2775			var random = Math.floor( Math.random() * allElems.length ),
2776				randEl = $( allElems[ random ] ).clone( true )[ 0 ];
2777			allElems.splice( random, 1 );
2778			return randEl;
2779		} ),
2780		elm_len = $elm.length,
2781		i;
2782
2783	for ( i = 0; i < elm_len; i++ ) {
2784		$( $elm[ i ] ).replaceWith( $( shuffled[ i ] ) );
2785	}
2786
2787	return $( shuffled );
2788};
2789
2790/* ---------------------------------
2791@extension: pickElements
2792@returns: [collection] of randoms elements
2793-------------------------------- */
2794wb.pickElements = function( $elm, numOfElm ) {
2795	var nbElm = $elm.size(),
2796		elmCopies,
2797		i, swap;
2798
2799	numOfElm = numOfElm || 1;
2800
2801	// Special cases
2802	if ( numOfElm > nbElm ) {
2803		return $elm.pushStack( $elm );
2804	} else if ( numOfElm === 1 ) {
2805		return $elm.filter( ":eq(" + Math.floor( Math.random() * nbElm ) + ")" );
2806	}
2807
2808	// Create a randomized copy of the set of elements,
2809	// using Fisher-Yates sorting
2810	elmCopies = $elm.get();
2811
2812	for ( i = 0; i < nbElm - 1; i++ ) {
2813		swap = Math.floor( Math.random() * ( nbElm - i ) ) + i;
2814		elmCopies[ swap ] = elmCopies.splice( i, 1, elmCopies[ swap ] )[ 0 ];
2815	}
2816	elmCopies = elmCopies.slice( 0, numOfElm );
2817
2818	// Finally, filter jQuery stack
2819	return $elm.filter( function( idx ) {
2820		return $.inArray( $elm.get( idx ), elmCopies ) > -1;
2821	} );
2822};
2823
2824/* ---------------------------------
2825Adds a link to the Skip links navigation
2826@param text: Text to display in the anchor or button
2827@param attr: JSO with { attribute: value, ... } to add attributes to the anchor or button. Minimum is { href: "#your-anchor" } for the anchor tag
2828@param isBtn: (Optional) Bool if true element is a button, otherwise it is an anchor by default
2829@param isLast: (Optional) Bool if true element will be inserted last in the list
2830-------------------------------- */
2831wb.addSkipLink = function( text, attr, isBtn, isLast ) {
2832	var list = document.getElementById( "wb-tphp" ),
2833		li = document.createElement( "li" ),
2834		elm = document.createElement( ( isBtn ? "button" : "a" ) ),
2835		key;
2836
2837	// Add skip link's proprietary classes to new element
2838	li.className = "wb-slc";
2839	elm.className = "wb-sl";
2840
2841	// Add given attributes to element
2842	for ( key in attr ) {
2843		elm.setAttribute( key, attr[ key ] );
2844	}
2845
2846	// Append text and new element to the skip link list (after main content)
2847	elm.appendChild( document.createTextNode( text ) );
2848	li.appendChild( elm );
2849
2850	if ( isLast ) {
2851		list.appendChild( li );
2852	} else {
2853		list.insertBefore( li, list.childNodes[ 2 ] );
2854	}
2855
2856	return true;
2857};
2858
2859} )( jQuery, wb );
2860
2861( function( wb, window ) {
2862
2863"use strict";
2864
2865// Escapes the characters in a string for use in a jQuery selector
2866// Based on https://totaldev.com/content/escaping-characters-get-valid-jquery-id
2867wb.jqEscape = function( selector ) {
2868	// eslint-disable-next-line no-useless-escape
2869	return selector.replace( /([;&,\.\+\*\~':"\\\!\^\/#$%@\[\]\(\)=>\|])/g, "\\$1" );
2870};
2871
2872// RegEx used by formattedNumCompare
2873wb.formattedNumCompareRegEx = /(<[^>]*>|[^\d.])/g;
2874
2875// Compares two formatted numbers (e.g., 1.2.12 or 1,000,345)
2876wb.formattedNumCompare = function( a, b ) {
2877	var regEx = wb.formattedNumCompareRegEx,
2878		aMultiple = a.indexOf( "-" ) === -1 ? 1 : -1,
2879		aNumbers = ( ( a === "-" || a === "" ) ? "0" : a.replace( regEx, "" ) ).split( "." ),
2880		bMultiple = b.indexOf( "-" ) === -1 ? 1 : -1,
2881		bNumbers = ( ( b === "-" || b === "" ) ? "0" : b.replace( regEx, "" ) ).split( "." ),
2882		len = aNumbers.length,
2883		i, result;
2884
2885	for ( i = 0; i !== len; i += 1 ) {
2886		result = parseInt( aNumbers[ i ], 10 ) * aMultiple - parseInt( bNumbers[ i ], 10 ) * bMultiple;
2887		if ( result !== 0 ) {
2888			break;
2889		}
2890	}
2891	return result;
2892};
2893
2894// Compare two strings with special characters (e.g., Cyrillic or Chinese characters)
2895wb.i18nTextCompare = function( a, b ) {
2896	return wb.normalizeDiacritics( a ).localeCompare( wb.normalizeDiacritics( b ) );
2897};
2898
2899// Based upon https://gist.github.com/instanceofme/1731620
2900// Licensed under WTFPL v2 http://sam.zoy.org/wtfpl/COPYING
2901wb.normalizeDiacritics = function( str ) {
2902	var diacritics = {
2903			"\u24B6": "A",
2904			"\uFF21": "A",
2905			"\u00C0": "A",
2906			"\u00C1": "A",
2907			"\u00C2": "A",
2908			"\u1EA6": "A",
2909			"\u1EA4": "A",
2910			"\u1EAA": "A",
2911			"\u1EA8": "A",
2912			"\u00C3": "A",
2913			"\u0100": "A",
2914			"\u0102": "A",
2915			"\u1EB0": "A",
2916			"\u1EAE": "A",
2917			"\u1EB4": "A",
2918			"\u1EB2": "A",
2919			"\u0226": "A",
2920			"\u01E0": "A",
2921			"\u00C4": "A",
2922			"\u01DE": "A",
2923			"\u1EA2": "A",
2924			"\u00C5": "A",
2925			"\u01FA": "A",
2926			"\u01CD": "A",
2927			"\u0200": "A",
2928			"\u0202": "A",
2929			"\u1EA0": "A",
2930			"\u1EAC": "A",
2931			"\u1EB6": "A",
2932			"\u1E00": "A",
2933			"\u0104": "A",
2934			"\u023A": "A",
2935			"\u2C6F": "A",
2936			"\uA732": "AA",
2937			"\u00C6": "AE",
2938			"\u01FC": "AE",
2939			"\u01E2": "AE",
2940			"\uA734": "AO",
2941			"\uA736": "AU",
2942			"\uA738": "AV",
2943			"\uA73A": "AV",
2944			"\uA73C": "AY",
2945			"\u24B7": "B",
2946			"\uFF22": "B",
2947			"\u1E02": "B",
2948			"\u1E04": "B",
2949			"\u1E06": "B",
2950			"\u0243": "B",
2951			"\u0182": "B",
2952			"\u0181": "B",
2953			"\u24B8": "C",
2954			"\uFF23": "C",
2955			"\u0106": "C",
2956			"\u0108": "C",
2957			"\u010A": "C",
2958			"\u010C": "C",
2959			"\u00C7": "C",
2960			"\u1E08": "C",
2961			"\u0187": "C",
2962			"\u023B": "C",
2963			"\uA73E": "C",
2964			"\u24B9": "D",
2965			"\uFF24": "D",
2966			"\u1E0A": "D",
2967			"\u010E": "D",
2968			"\u1E0C": "D",
2969			"\u1E10": "D",
2970			"\u1E12": "D",
2971			"\u1E0E": "D",
2972			"\u0110": "D",
2973			"\u018B": "D",
2974			"\u018A": "D",
2975			"\u0189": "D",
2976			"\uA779": "D",
2977			"\u01F1": "DZ",
2978			"\u01C4": "DZ",
2979			"\u01F2": "Dz",
2980			"\u01C5": "Dz",
2981			"\u24BA": "E",
2982			"\uFF25": "E",
2983			"\u00C8": "E",
2984			"\u00C9": "E",
2985			"\u00CA": "E",
2986			"\u1EC0": "E",
2987			"\u1EBE": "E",
2988			"\u1EC4": "E",
2989			"\u1EC2": "E",
2990			"\u1EBC": "E",
2991			"\u0112": "E",
2992			"\u1E14": "E",
2993			"\u1E16": "E",
2994			"\u0114": "E",
2995			"\u0116": "E",
2996			"\u00CB": "E",
2997			"\u1EBA": "E",
2998			"\u011A": "E",
2999			"\u0204": "E",
3000			"\u0206": "E",
3001			"\u1EB8": "E",
3002			"\u1EC6": "E",
3003			"\u0228": "E",
3004			"\u1E1C": "E",
3005			"\u0118": "E",
3006			"\u1E18": "E",
3007			"\u1E1A": "E",
3008			"\u0190": "E",
3009			"\u018E": "E",
3010			"\u24BB": "F",
3011			"\uFF26": "F",
3012			"\u1E1E": "F",
3013			"\u0191": "F",
3014			"\uA77B": "F",
3015			"\u24BC": "G",
3016			"\uFF27": "G",
3017			"\u01F4": "G",
3018			"\u011C": "G",
3019			"\u1E20": "G",
3020			"\u011E": "G",
3021			"\u0120": "G",
3022			"\u01E6": "G",
3023			"\u0122": "G",
3024			"\u01E4": "G",
3025			"\u0193": "G",
3026			"\uA7A0": "G",
3027			"\uA77D": "G",
3028			"\uA77E": "G",
3029			"\u24BD": "H",
3030			"\uFF28": "H",
3031			"\u0124": "H",
3032			"\u1E22": "H",
3033			"\u1E26": "H",
3034			"\u021E": "H",
3035			"\u1E24": "H",
3036			"\u1E28": "H",
3037			"\u1E2A": "H",
3038			"\u0126": "H",
3039			"\u2C67": "H",
3040			"\u2C75": "H",
3041			"\uA78D": "H",
3042			"\u24BE": "I",
3043			"\uFF29": "I",
3044			"\u00CC": "I",
3045			"\u00CD": "I",
3046			"\u00CE": "I",
3047			"\u0128": "I",
3048			"\u012A": "I",
3049			"\u012C": "I",
3050			"\u0130": "I",
3051			"\u00CF": "I",
3052			"\u1E2E": "I",
3053			"\u1EC8": "I",
3054			"\u01CF": "I",
3055			"\u0208": "I",
3056			"\u020A": "I",
3057			"\u1ECA": "I",
3058			"\u012E": "I",
3059			"\u1E2C": "I",
3060			"\u0197": "I",
3061			"\u24BF": "J",
3062			"\uFF2A": "J",
3063			"\u0134": "J",
3064			"\u0248": "J",
3065			"\u24C0": "K",
3066			"\uFF2B": "K",
3067			"\u1E30": "K",
3068			"\u01E8": "K",
3069			"\u1E32": "K",
3070			"\u0136": "K",
3071			"\u1E34": "K",
3072			"\u0198": "K",
3073			"\u2C69": "K",
3074			"\uA740": "K",
3075			"\uA742": "K",
3076			"\uA744": "K",
3077			"\uA7A2": "K",
3078			"\u24C1": "L",
3079			"\uFF2C": "L",
3080			"\u013F": "L",
3081			"\u0139": "L",
3082			"\u013D": "L",
3083			"\u1E36": "L",
3084			"\u1E38": "L",
3085			"\u013B": "L",
3086			"\u1E3C": "L",
3087			"\u1E3A": "L",
3088			"\u0141": "L",
3089			"\u023D": "L",
3090			"\u2C62": "L",
3091			"\u2C60": "L",
3092			"\uA748": "L",
3093			"\uA746": "L",
3094			"\uA780": "L",
3095			"\u01C7": "LJ",
3096			"\u01C8": "Lj",
3097			"\u24C2": "M",
3098			"\uFF2D": "M",
3099			"\u1E3E": "M",
3100			"\u1E40": "M",
3101			"\u1E42": "M",
3102			"\u2C6E": "M",
3103			"\u019C": "M",
3104			"\u24C3": "N",
3105			"\uFF2E": "N",
3106			"\u01F8": "N",
3107			"\u0143": "N",
3108			"\u00D1": "N",
3109			"\u1E44": "N",
3110			"\u0147": "N",
3111			"\u1E46": "N",
3112			"\u0145": "N",
3113			"\u1E4A": "N",
3114			"\u1E48": "N",
3115			"\u0220": "N",
3116			"\u019D": "N",
3117			"\uA790": "N",
3118			"\uA7A4": "N",
3119			"\u01CA": "NJ",
3120			"\u01CB": "Nj",
3121			"\u24C4": "O",
3122			"\uFF2F": "O",
3123			"\u00D2": "O",
3124			"\u00D3": "O",
3125			"\u00D4": "O",
3126			"\u1ED2": "O",
3127			"\u1ED0": "O",
3128			"\u1ED6": "O",
3129			"\u1ED4": "O",
3130			"\u00D5": "O",
3131			"\u1E4C": "O",
3132			"\u022C": "O",
3133			"\u1E4E": "O",
3134			"\u014C": "O",
3135			"\u1E50": "O",
3136			"\u1E52": "O",
3137			"\u014E": "O",
3138			"\u022E": "O",
3139			"\u0230": "O",
3140			"\u00D6": "O",
3141			"\u022A": "O",
3142			"\u1ECE": "O",
3143			"\u0150": "O",
3144			"\u01D1": "O",
3145			"\u020C": "O",
3146			"\u020E": "O",
3147			"\u01A0": "O",
3148			"\u1EDC": "O",
3149			"\u1EDA": "O",
3150			"\u1EE0": "O",
3151			"\u1EDE": "O",
3152			"\u1EE2": "O",
3153			"\u1ECC": "O",
3154			"\u1ED8": "O",
3155			"\u01EA": "O",
3156			"\u01EC": "O",
3157			"\u00D8": "O",
3158			"\u01FE": "O",
3159			"\u0186": "O",
3160			"\u019F": "O",
3161			"\uA74A": "O",
3162			"\uA74C": "O",
3163			"\u0152": "OE",
3164			"\u01A2": "OI",
3165			"\uA74E": "OO",
3166			"\u0222": "OU",
3167			"\u24C5": "P",
3168			"\uFF30": "P",
3169			"\u1E54": "P",
3170			"\u1E56": "P",
3171			"\u01A4": "P",
3172			"\u2C63": "P",
3173			"\uA750": "P",
3174			"\uA752": "P",
3175			"\uA754": "P",
3176			"\u24C6": "Q",
3177			"\uFF31": "Q",
3178			"\uA756": "Q",
3179			"\uA758": "Q",
3180			"\u024A": "Q",
3181			"\u24C7": "R",
3182			"\uFF32": "R",
3183			"\u0154": "R",
3184			"\u1E58": "R",
3185			"\u0158": "R",
3186			"\u0210": "R",
3187			"\u0212": "R",
3188			"\u1E5A": "R",
3189			"\u1E5C": "R",
3190			"\u0156": "R",
3191			"\u1E5E": "R",
3192			"\u024C": "R",
3193			"\u2C64": "R",
3194			"\uA75A": "R",
3195			"\uA7A6": "R",
3196			"\uA782": "R",
3197			"\u24C8": "S",
3198			"\uFF33": "S",
3199			"\u015A": "S",
3200			"\u1E64": "S",
3201			"\u015C": "S",
3202			"\u1E60": "S",
3203			"\u0160": "S",
3204			"\u1E66": "S",
3205			"\u1E62": "S",
3206			"\u1E68": "S",
3207			"\u0218": "S",
3208			"\u015E": "S",
3209			"\u2C7E": "S",
3210			"\uA7A8": "S",
3211			"\uA784": "S",
3212			"\u1E9E": "SS",
3213			"\u24C9": "T",
3214			"\uFF34": "T",
3215			"\u1E6A": "T",
3216			"\u0164": "T",
3217			"\u1E6C": "T",
3218			"\u021A": "T",
3219			"\u0162": "T",
3220			"\u1E70": "T",
3221			"\u1E6E": "T",
3222			"\u0166": "T",
3223			"\u01AC": "T",
3224			"\u01AE": "T",
3225			"\u023E": "T",
3226			"\uA786": "T",
3227			"\uA728": "TZ",
3228			"\u24CA": "U",
3229			"\uFF35": "U",
3230			"\u00D9": "U",
3231			"\u00DA": "U",
3232			"\u00DB": "U",
3233			"\u0168": "U",
3234			"\u1E78": "U",
3235			"\u016A": "U",
3236			"\u1E7A": "U",
3237			"\u016C": "U",
3238			"\u00DC": "U",
3239			"\u01DB": "U",
3240			"\u01D7": "U",
3241			"\u01D5": "U",
3242			"\u01D9": "U",
3243			"\u1EE6": "U",
3244			"\u016E": "U",
3245			"\u0170": "U",
3246			"\u01D3": "U",
3247			"\u0214": "U",
3248			"\u0216": "U",
3249			"\u01AF": "U",
3250			"\u1EEA": "U",
3251			"\u1EE8": "U",
3252			"\u1EEE": "U",
3253			"\u1EEC": "U",
3254			"\u1EF0": "U",
3255			"\u1EE4": "U",
3256			"\u1E72": "U",
3257			"\u0172": "U",
3258			"\u1E76": "U",
3259			"\u1E74": "U",
3260			"\u0244": "U",
3261			"\u24CB": "V",
3262			"\uFF36": "V",
3263			"\u1E7C": "V",
3264			"\u1E7E": "V",
3265			"\u01B2": "V",
3266			"\uA75E": "V",
3267			"\u0245": "V",
3268			"\uA760": "VY",
3269			"\u24CC": "W",
3270			"\uFF37": "W",
3271			"\u1E80": "W",
3272			"\u1E82": "W",
3273			"\u0174": "W",
3274			"\u1E86": "W",
3275			"\u1E84": "W",
3276			"\u1E88": "W",
3277			"\u2C72": "W",
3278			"\u24CD": "X",
3279			"\uFF38": "X",
3280			"\u1E8A": "X",
3281			"\u1E8C": "X",
3282			"\u24CE": "Y",
3283			"\uFF39": "Y",
3284			"\u1EF2": "Y",
3285			"\u00DD": "Y",
3286			"\u0176": "Y",
3287			"\u1EF8": "Y",
3288			"\u0232": "Y",
3289			"\u1E8E": "Y",
3290			"\u0178": "Y",
3291			"\u1EF6": "Y",
3292			"\u1EF4": "Y",
3293			"\u01B3": "Y",
3294			"\u024E": "Y",
3295			"\u1EFE": "Y",
3296			"\u24CF": "Z",
3297			"\uFF3A": "Z",
3298			"\u0179": "Z",
3299			"\u1E90": "Z",
3300			"\u017B": "Z",
3301			"\u017D": "Z",
3302			"\u1E92": "Z",
3303			"\u1E94": "Z",
3304			"\u01B5": "Z",
3305			"\u0224": "Z",
3306			"\u2C7F": "Z",
3307			"\u2C6B": "Z",
3308			"\uA762": "Z",
3309			"\u24D0": "a",
3310			"\uFF41": "a",
3311			"\u1E9A": "a",
3312			"\u00E0": "a",
3313			"\u00E1": "a",
3314			"\u00E2": "a",
3315			"\u1EA7": "a",
3316			"\u1EA5": "a",
3317			"\u1EAB": "a",
3318			"\u1EA9": "a",
3319			"\u00E3": "a",
3320			"\u0101": "a",
3321			"\u0103": "a",
3322			"\u1EB1": "a",
3323			"\u1EAF": "a",
3324			"\u1EB5": "a",
3325			"\u1EB3": "a",
3326			"\u0227": "a",
3327			"\u01E1": "a",
3328			"\u00E4": "a",
3329			"\u01DF": "a",
3330			"\u1EA3": "a",
3331			"\u00E5": "a",
3332			"\u01FB": "a",
3333			"\u01CE": "a",
3334			"\u0201": "a",
3335			"\u0203": "a",
3336			"\u1EA1": "a",
3337			"\u1EAD": "a",
3338			"\u1EB7": "a",
3339			"\u1E01": "a",
3340			"\u0105": "a",
3341			"\u2C65": "a",
3342			"\u0250": "a",
3343			"\uA733": "aa",
3344			"\u00E6": "ae",
3345			"\u01FD": "ae",
3346			"\u01E3": "ae",
3347			"\uA735": "ao",
3348			"\uA737": "au",
3349			"\uA739": "av",
3350			"\uA73B": "av",
3351			"\uA73D": "ay",
3352			"\u24D1": "b",
3353			"\uFF42": "b",
3354			"\u1E03": "b",
3355			"\u1E05": "b",
3356			"\u1E07": "b",
3357			"\u0180": "b",
3358			"\u0183": "b",
3359			"\u0253": "b",
3360			"\u24D2": "c",
3361			"\uFF43": "c",
3362			"\u0107": "c",
3363			"\u0109": "c",
3364			"\u010B": "c",
3365			"\u010D": "c",
3366			"\u00E7": "c",
3367			"\u1E09": "c",
3368			"\u0188": "c",
3369			"\u023C": "c",
3370			"\uA73F": "c",
3371			"\u2184": "c",
3372			"\u24D3": "d",
3373			"\uFF44": "d",
3374			"\u1E0B": "d",
3375			"\u010F": "d",
3376			"\u1E0D": "d",
3377			"\u1E11": "d",
3378			"\u1E13": "d",
3379			"\u1E0F": "d",
3380			"\u0111": "d",
3381			"\u018C": "d",
3382			"\u0256": "d",
3383			"\u0257": "d",
3384			"\uA77A": "d",
3385			"\u01F3": "dz",
3386			"\u01C6": "dz",
3387			"\u24D4": "e",
3388			"\uFF45": "e",
3389			"\u00E8": "e",
3390			"\u00E9": "e",
3391			"\u00EA": "e",
3392			"\u1EC1": "e",
3393			"\u1EBF": "e",
3394			"\u1EC5": "e",
3395			"\u1EC3": "e",
3396			"\u1EBD": "e",
3397			"\u0113": "e",
3398			"\u1E15": "e",
3399			"\u1E17": "e",
3400			"\u0115": "e",
3401			"\u0117": "e",
3402			"\u00EB": "e",
3403			"\u1EBB": "e",
3404			"\u011B": "e",
3405			"\u0205": "e",
3406			"\u0207": "e",
3407			"\u1EB9": "e",
3408			"\u1EC7": "e",
3409			"\u0229": "e",
3410			"\u1E1D": "e",
3411			"\u0119": "e",
3412			"\u1E19": "e",
3413			"\u1E1B": "e",
3414			"\u0247": "e",
3415			"\u025B": "e",
3416			"\u01DD": "e",
3417			"\u24D5": "f",
3418			"\uFF46": "f",
3419			"\u1E1F": "f",
3420			"\u0192": "f",
3421			"\uA77C": "f",
3422			"\u24D6": "g",
3423			"\uFF47": "g",
3424			"\u01F5": "g",
3425			"\u011D": "g",
3426			"\u1E21": "g",
3427			"\u011F": "g",
3428			"\u0121": "g",
3429			"\u01E7": "g",
3430			"\u0123": "g",
3431			"\u01E5": "g",
3432			"\u0260": "g",
3433			"\uA7A1": "g",
3434			"\u1D79": "g",
3435			"\uA77F": "g",
3436			"\u24D7": "h",
3437			"\uFF48": "h",
3438			"\u0125": "h",
3439			"\u1E23": "h",
3440			"\u1E27": "h",
3441			"\u021F": "h",
3442			"\u1E25": "h",
3443			"\u1E29": "h",
3444			"\u1E2B": "h",
3445			"\u1E96": "h",
3446			"\u0127": "h",
3447			"\u2C68": "h",
3448			"\u2C76": "h",
3449			"\u0265": "h",
3450			"\u0195": "hv",
3451			"\u24D8": "i",
3452			"\uFF49": "i",
3453			"\u00EC": "i",
3454			"\u00ED": "i",
3455			"\u00EE": "i",
3456			"\u0129": "i",
3457			"\u012B": "i",
3458			"\u012D": "i",
3459			"\u00EF": "i",
3460			"\u1E2F": "i",
3461			"\u1EC9": "i",
3462			"\u01D0": "i",
3463			"\u0209": "i",
3464			"\u020B": "i",
3465			"\u1ECB": "i",
3466			"\u012F": "i",
3467			"\u1E2D": "i",
3468			"\u0268": "i",
3469			"\u0131": "i",
3470			"\u24D9": "j",
3471			"\uFF4A": "j",
3472			"\u0135": "j",
3473			"\u01F0": "j",
3474			"\u0249": "j",
3475			"\u24DA": "k",
3476			"\uFF4B": "k",
3477			"\u1E31": "k",
3478			"\u01E9": "k",
3479			"\u1E33": "k",
3480			"\u0137": "k",
3481			"\u1E35": "k",
3482			"\u0199": "k",
3483			"\u2C6A": "k",
3484			"\uA741": "k",
3485			"\uA743": "k",
3486			"\uA745": "k",
3487			"\uA7A3": "k",
3488			"\u24DB": "l",
3489			"\uFF4C": "l",
3490			"\u0140": "l",
3491			"\u013A": "l",
3492			"\u013E": "l",
3493			"\u1E37": "l",
3494			"\u1E39": "l",
3495			"\u013C": "l",
3496			"\u1E3D": "l",
3497			"\u1E3B": "l",
3498			"\u0142": "l",
3499			"\u019A": "l",
3500			"\u026B": "l",
3501			"\u2C61": "l",
3502			"\uA749": "l",
3503			"\uA781": "l",
3504			"\uA747": "l",
3505			"\u01C9": "lj",
3506			"\u24DC": "m",
3507			"\uFF4D": "m",
3508			"\u1E3F": "m",
3509			"\u1E41": "m",
3510			"\u1E43": "m",
3511			"\u0271": "m",
3512			"\u026F": "m",
3513			"\u24DD": "n",
3514			"\uFF4E": "n",
3515			"\u01F9": "n",
3516			"\u0144": "n",
3517			"\u00F1": "n",
3518			"\u1E45": "n",
3519			"\u0148": "n",
3520			"\u1E47": "n",
3521			"\u0146": "n",
3522			"\u1E4B": "n",
3523			"\u1E49": "n",
3524			"\u019E": "n",
3525			"\u0272": "n",
3526			"\u0149": "n",
3527			"\uA791": "n",
3528			"\uA7A5": "n",
3529			"\u01CC": "nj",
3530			"\u24DE": "o",
3531			"\uFF4F": "o",
3532			"\u00F2": "o",
3533			"\u00F3": "o",
3534			"\u00F4": "o",
3535			"\u1ED3": "o",
3536			"\u1ED1": "o",
3537			"\u1ED7": "o",
3538			"\u1ED5": "o",
3539			"\u00F5": "o",
3540			"\u1E4D": "o",
3541			"\u022D": "o",
3542			"\u1E4F": "o",
3543			"\u014D": "o",
3544			"\u1E51": "o",
3545			"\u1E53": "o",
3546			"\u014F": "o",
3547			"\u022F": "o",
3548			"\u0231": "o",
3549			"\u00F6": "o",
3550			"\u022B": "o",
3551			"\u1ECF": "o",
3552			"\u0151": "o",
3553			"\u01D2": "o",
3554			"\u020D": "o",
3555			"\u020F": "o",
3556			"\u01A1": "o",
3557			"\u1EDD": "o",
3558			"\u1EDB": "o",
3559			"\u1EE1": "o",
3560			"\u1EDF": "o",
3561			"\u1EE3": "o",
3562			"\u1ECD": "o",
3563			"\u1ED9": "o",
3564			"\u01EB": "o",
3565			"\u01ED": "o",
3566			"\u00F8": "o",
3567			"\u01FF": "o",
3568			"\u0254": "o",
3569			"\uA74B": "o",
3570			"\uA74D": "o",
3571			"\u0275": "o",
3572			"\u0153": "oe",
3573			"\u0276": "oe",
3574			"\u01A3": "oi",
3575			"\u0223": "ou",
3576			"\uA74F": "oo",
3577			"\u24DF": "p",
3578			"\uFF50": "p",
3579			"\u1E55": "p",
3580			"\u1E57": "p",
3581			"\u01A5": "p",
3582			"\u1D7D": "p",
3583			"\uA751": "p",
3584			"\uA753": "p",
3585			"\uA755": "p",
3586			"\u24E0": "q",
3587			"\uFF51": "q",
3588			"\u024B": "q",
3589			"\uA757": "q",
3590			"\uA759": "q",
3591			"\u24E1": "r",
3592			"\uFF52": "r",
3593			"\u0155": "r",
3594			"\u1E59": "r",
3595			"\u0159": "r",
3596			"\u0211": "r",
3597			"\u0213": "r",
3598			"\u1E5B": "r",
3599			"\u1E5D": "r",
3600			"\u0157": "r",
3601			"\u1E5F": "r",
3602			"\u024D": "r",
3603			"\u027D": "r",
3604			"\uA75B": "r",
3605			"\uA7A7": "r",
3606			"\uA783": "r",
3607			"\u24E2": "s",
3608			"\uFF53": "s",
3609			"\u015B": "s",
3610			"\u1E65": "s",
3611			"\u015D": "s",
3612			"\u1E61": "s",
3613			"\u0161": "s",
3614			"\u1E67": "s",
3615			"\u1E63": "s",
3616			"\u1E69": "s",
3617			"\u0219": "s",
3618			"\u015F": "s",
3619			"\u023F": "s",
3620			"\uA7A9": "s",
3621			"\uA785": "s",
3622			"\u017F": "s",
3623			"\u1E9B": "s",
3624			"\u00DF": "ss",
3625			"\u24E3": "t",
3626			"\uFF54": "t",
3627			"\u1E6B": "t",
3628			"\u1E97": "t",
3629			"\u0165": "t",
3630			"\u1E6D": "t",
3631			"\u021B": "t",
3632			"\u0163": "t",
3633			"\u1E71": "t",
3634			"\u1E6F": "t",
3635			"\u0167": "t",
3636			"\u01AD": "t",
3637			"\u0288": "t",
3638			"\u2C66": "t",
3639			"\uA787": "t",
3640			"\uA729": "tz",
3641			"\u24E4": "u",
3642			"\uFF55": "u",
3643			"\u00F9": "u",
3644			"\u00FA": "u",
3645			"\u00FB": "u",
3646			"\u0169": "u",
3647			"\u1E79": "u",
3648			"\u016B": "u",
3649			"\u1E7B": "u",
3650			"\u016D": "u",
3651			"\u00FC": "u",
3652			"\u01DC": "u",
3653			"\u01D8": "u",
3654			"\u01D6": "u",
3655			"\u01DA": "u",
3656			"\u1EE7": "u",
3657			"\u016F": "u",
3658			"\u0171": "u",
3659			"\u01D4": "u",
3660			"\u0215": "u",
3661			"\u0217": "u",
3662			"\u01B0": "u",
3663			"\u1EEB": "u",
3664			"\u1EE9": "u",
3665			"\u1EEF": "u",
3666			"\u1EED": "u",
3667			"\u1EF1": "u",
3668			"\u1EE5": "u",
3669			"\u1E73": "u",
3670			"\u0173": "u",
3671			"\u1E77": "u",
3672			"\u1E75": "u",
3673			"\u0289": "u",
3674			"\u24E5": "v",
3675			"\uFF56": "v",
3676			"\u1E7D": "v",
3677			"\u1E7F": "v",
3678			"\u028B": "v",
3679			"\uA75F": "v",
3680			"\u028C": "v",
3681			"\uA761": "vy",
3682			"\u24E6": "w",
3683			"\uFF57": "w",
3684			"\u1E81": "w",
3685			"\u1E83": "w",
3686			"\u0175": "w",
3687			"\u1E87": "w",
3688			"\u1E85": "w",
3689			"\u1E98": "w",
3690			"\u1E89": "w",
3691			"\u2C73": "w",
3692			"\u24E7": "x",
3693			"\uFF58": "x",
3694			"\u1E8B": "x",
3695			"\u1E8D": "x",
3696			"\u24E8": "y",
3697			"\uFF59": "y",
3698			"\u1EF3": "y",
3699			"\u00FD": "y",
3700			"\u0177": "y",
3701			"\u1EF9": "y",
3702			"\u0233": "y",
3703			"\u1E8F": "y",
3704			"\u00FF": "y",
3705			"\u1EF7": "y",
3706			"\u1E99": "y",
3707			"\u1EF5": "y",
3708			"\u01B4": "y",
3709			"\u024F": "y",
3710			"\u1EFF": "y",
3711			"\u24E9": "z",
3712			"\uFF5A": "z",
3713			"\u017A": "z",
3714			"\u1E91": "z",
3715			"\u017C": "z",
3716			"\u017E": "z",
3717			"\u1E93": "z",
3718			"\u1E95": "z",
3719			"\u01B6": "z",
3720			"\u0225": "z",
3721			"\u0240": "z",
3722			"\u2C6C": "z",
3723			"\uA763": "z",
3724			"\uFF10": "0",
3725			"\u2080": "0",
3726			"\u24EA": "0",
3727			"\u2070": "0",
3728			"\u00B9": "1",
3729			"\u2474": "1",
3730			"\u2081": "1",
3731			"\u2776": "1",
3732			"\u24F5": "1",
3733			"\u2488": "1",
3734			"\u2460": "1",
3735			"\uFF11": "1",
3736			"\u00B2": "2",
3737			"\u2777": "2",
3738			"\u2475": "2",
3739			"\uFF12": "2",
3740			"\u2082": "2",
3741			"\u24F6": "2",
3742			"\u2461": "2",
3743			"\u2489": "2",
3744			"\u00B3": "3",
3745			"\uFF13": "3",
3746			"\u248A": "3",
3747			"\u2476": "3",
3748			"\u2083": "3",
3749			"\u2778": "3",
3750			"\u24F7": "3",
3751			"\u2462": "3",
3752			"\u24F8": "4",
3753			"\u2463": "4",
3754			"\u248B": "4",
3755			"\uFF14": "4",
3756			"\u2074": "4",
3757			"\u2084": "4",
3758			"\u2779": "4",
3759			"\u2477": "4",
3760			"\u248C": "5",
3761			"\u2085": "5",
3762			"\u24F9": "5",
3763			"\u2478": "5",
3764			"\u277A": "5",
3765			"\u2464": "5",
3766			"\uFF15": "5",
3767			"\u2075": "5",
3768			"\u2479": "6",
3769			"\u2076": "6",
3770			"\uFF16": "6",
3771			"\u277B": "6",
3772			"\u2086": "6",
3773			"\u2465": "6",
3774			"\u24FA": "6",
3775			"\u248D": "6",
3776			"\uFF17": "7",
3777			"\u2077": "7",
3778			"\u277C": "7",
3779			"\u24FB": "7",
3780			"\u248E": "7",
3781			"\u2087": "7",
3782			"\u247A": "7",
3783			"\u2466": "7",
3784			"\u2467": "8",
3785			"\u248F": "8",
3786			"\u24FC": "8",
3787			"\u247B": "8",
3788			"\u2078": "8",
3789			"\uFF18": "8",
3790			"\u277D": "8",
3791			"\u2088": "8",
3792			"\u24FD": "9",
3793			"\uFF19": "9",
3794			"\u2490": "9",
3795			"\u277E": "9",
3796			"\u247C": "9",
3797			"\u2089": "9",
3798			"\u2468": "9",
3799			"\u2079": "9"
3800		},
3801		chars = str.split( "" ),
3802		len = chars.length,
3803		normalized = false,
3804		i, character;
3805	for ( i = 0; i !== len; i += 1 ) {
3806		character = chars[ i ];
3807		if ( Object.prototype.hasOwnProperty.call( diacritics, character ) ) {
3808			chars[ i ] = diacritics[ character ];
3809			normalized = true;
3810		}
3811	}
3812	return ( normalized ? chars.join( "" ) : str );
3813};
3814
3815/**
3816 * @namespace wb.string
3817 */
3818wb.string = {
3819
3820	/*
3821	 * Left-pads a number with zeros.
3822	 * @memberof wb.string
3823	 * @param {number} number The original number to pad.
3824	 * @param {number} length The width of the resulting padded number, not the number of zeros to add to the front of the string.
3825	 * @return {string} The padded string
3826	 */
3827	pad: function( number, length ) {
3828		var str = number + "",
3829			diff = length - str.length,
3830			i;
3831		for ( i = 0; i !== diff; i += 1 ) {
3832			str = "0" + str;
3833		}
3834		return str;
3835	},
3836
3837	/*
3838	 * Convert a base64 string into an ArrayBuffer (Note: this function are not fully UTF-8 supported and may create interoperability issue)
3839	 * ref. https://www.isummation.com/blog/convert-arraybuffer-to-base64-string-and-vice-versa/
3840	 * @memberof wb.string
3841	 * @param {string} Base64 browser encoded
3842	 * @return {ArrayBuffer} string converted into ArrayBuffer
3843	 */
3844	base64ToArrayBuffer: function( base64 ) {
3845		var binary_string = window.atob( base64 ),
3846			len = binary_string.length,
3847			bytes = new Uint8Array( len ),
3848			i;
3849		for ( i = 0; i < len; i++ ) {
3850			bytes[ i ] = binary_string.charCodeAt( i );
3851		}
3852		return bytes.buffer;
3853	},
3854
3855	/*
3856	 * Convert an ArrayBuffer into base64 string (Note: this function are not fully UTF-8 supported and may create interoperability issue)
3857	 * ref. https://www.isummation.com/blog/convert-arraybuffer-to-base64-string-and-vice-versa/
3858	 * @memberof wb.string
3859	 * @param {ArrayBuffer}
3860	 * @return {string} ArrayBuffer converted into base64 string
3861	 */
3862	arrayBufferToBase64: function( buffer ) {
3863		var binary = "",
3864			bytes = new Uint8Array( buffer ),
3865			len = bytes.byteLength,
3866			i;
3867		for ( i = 0; i < len; i++ ) {
3868			binary += String.fromCharCode( bytes[ i ] );
3869		}
3870		return window.btoa( binary );
3871	},
3872
3873	/*
3874	 * Convert an hexadecimal string into an ArrayBuffer
3875	 * ref. https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276
3876	 * @memberof wb.string
3877	 * @param {string} Encoded string in hexadecimal
3878	 * @return {Uint8Array} Binary array buffer
3879	 */
3880	fromHexString: function( hexString ) {
3881		return hexString === null ? null : Uint8Array.from( hexString.match( /.{1,2}/g ).map( function( byte ) {
3882			return parseInt( byte, 16 );
3883		} ) );
3884	},
3885
3886	/*
3887	 * Convert an ArrayBuffer into an hexadecimal string
3888	 * ref. https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276
3889	 * @memberof wb.string
3890	 * @param {Uint8Array} Binary array buffer
3891	 * @return {string} Encoded string in hexadecimal
3892	 */
3893	toHexString: function( bytes ) {
3894		return bytes.reduce( function( str, byte ) {
3895			return str + byte.toString( 16 ).padStart( 2, "0" );
3896		}, "" );
3897	}
3898
3899};
3900
3901/*
3902 * A suite of date related functions for easier parsing of dates
3903 * @namespace wb.date
3904 */
3905wb.date = {
3906
3907	/*
3908	 * Converts the date to a date-object. The input can be:
3909	 * <ul>
3910	 * <li>a Date object: returned without modification.</li>
3911	 * <li>an array: Interpreted as [year,month,day]. NOTE: month is 0-11.</li>
3912	 * <li>a number: Interpreted as number of milliseconds since 1 Jan 1970 (a timestamp).</li>
3913	 * <li>a string: Any format supported by the javascript engine, like 'YYYY/MM/DD', 'MM/DD/YYYY', 'Jan 31 2009' etc.</li>
3914	 * <li>an object: Interpreted as an object with year, month and date attributes. **NOTE** month is 0-11.</li>
3915	 * </ul>
3916	 * @memberof wb.date
3917	 * @param {Date | number[] | number | string | object} dateValue
3918	 * @return {Date | NaN}
3919	 */
3920	convert: function( dateValue ) {
3921		var dateConstructor = dateValue.constructor;
3922
3923		switch ( dateConstructor ) {
3924		case Date:
3925			return dateConstructor;
3926		case Array:
3927			return new Date( dateValue[ 0 ], dateValue[ 1 ], dateValue[ 2 ] );
3928		case Number:
3929		case String:
3930			return new Date( dateValue );
3931		default:
3932			return typeof dateValue === "object" ? new Date( dateValue.year, dateValue.month, dateValue.date ) : NaN;
3933		}
3934	},
3935
3936	/*
3937	 * Compares two dates (input can be any type supported by the convert function).
3938	 * @memberof wb.date
3939	 * @param {Date | number[] | number | string | object} dateValue1
3940	 * @param {Date | number[] | number | string | object} dateValue2
3941	 * @return {number | NaN}
3942	 * @example returns
3943	 * -1 if dateValue1 < dateValue2
3944	 * 0 if dateValue1 = dateValue2
3945	 * 1 if dateValue1 > dateValue2
3946	 * NaN if dateValue1 or dateValue2 is an illegal date
3947	 */
3948	compare: function( dateValue1, dateValue2 ) {
3949		var convert = wb.date.convert;
3950
3951		if ( isFinite( dateValue1 = convert( dateValue1 ).valueOf() ) && isFinite( dateValue2 = convert( dateValue2 ).valueOf() ) ) {
3952			return ( dateValue1 > dateValue2 ) - ( dateValue1 < dateValue2 );
3953		}
3954		return NaN;
3955	},
3956
3957	/*
3958	 * Cross-browser safe way of translating a date to ISO format
3959	 * @memberof wb.date
3960	 * @param {Date | number[] | number | string | object} dateValue
3961	 * @param {boolean} withTime Optional. Whether to include the time in the result, or just the date. False if blank.
3962	 * @return {string}
3963	 * @example
3964	 * toDateISO( new Date() )
3965	 * returns "2012-04-27"
3966	 * toDateISO( new Date(), true )
3967	 * returns "2012-04-27 13:46"
3968	 */
3969	toDateISO: function( dateValue, withTime ) {
3970		var date = wb.date.convert( dateValue ),
3971			pad = wb.string.pad;
3972
3973		return date.getFullYear() + "-" + pad( date.getMonth() + 1, 2, "0" ) + "-" + pad( date.getDate(), 2, "0" ) +
3974			( withTime ? " " + pad( date.getHours(), 2, "0" ) + ":" + pad( date.getMinutes(), 2, "0" ) : "" );
3975	},
3976
3977	/*
3978	 * Cross-browser safe way of creating a date object from a date string in ISO format
3979	 * @memberof wb.date
3980	 * @param {string} dateISO Date string in ISO format
3981	 * @return {Date}
3982	 */
3983	fromDateISO: function( dateISO ) {
3984		var date = null;
3985
3986		if ( dateISO && /\d{4}-\d{2}-\d{2}/.test( dateISO ) ) {
3987			date = new Date( dateISO.substr( 0, 4 ), dateISO.substr( 5, 2 ) - 1, dateISO.substr( 8, 2 ), 0, 0, 0, 0 );
3988		}
3989		return date;
3990	}
3991};
3992
3993/*
3994 * Returns a RFC4122 compliant Global Unique ID (GUID).
3995 * Originally from https://stackoverflow.com/a/2117523/455535
3996 */
3997wb.guid = function() {
3998	return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function( replacementChar ) {
3999		var rand = Math.random() * 16 | 0,
4000			newChar = replacementChar === "x" ? rand : ( rand & 0x3 | 0x8 );
4001		return newChar.toString( 16 );
4002	} );
4003};
4004
4005wb.escapeAttribute = function( str ) {
4006	return str.replace( /'/g, "&#39;" ).replace( /"/g, "&#34;" );
4007};
4008
4009/*
4010 * Returns an escaped HTML string
4011 */
4012wb.escapeHTML = function( str ) {
4013	return wb.escapeAttribute( str
4014		.replace( /&/g, "&#38;" )
4015		.replace( /</g, "&#60;" )
4016		.replace( />/g, "&#62;" ) );
4017};
4018
4019/*
4020 * Returns a UTF-8 output from Base64
4021 * Reference: https://developer.mozilla.org/fr/docs/Glossary/Base64 (To be reviewed later because escape function is deprecated)
4022 */
4023wb.decodeUTF8Base64 = function( str ) {
4024	return decodeURIComponent( escape( atob( str ) ) );
4025};
4026
4027/*
4028* Find most common Personal Identifiable Information (PII) in a string and return either the cleaned string either true/false
4029* @param {string} str (required) - the content that needs to be verified
4030*
4031* @param {boolean} scope - if true will scrub the content
4032* @param {object} (optional) the 2nd param (scope) can also be an object having the following properties (optional):
4033* 	{string} any key name of the default patterns e.g. email, digits, etc. with the value 1. The function will only scrub the content that match the regex of the default patterns passed in this object
4034* 	{regex} customCase - this param is a regex. It will search and replace the values corresponding that pattern
4035*
4036* @param {object} opts (optional) - the 3rd param of the function that can contain the following properties (optional):
4037* 	{boolean} isCustomExclusive - if true, it will scrubb only the custom regex if the regex is the only property of the "scope" object
4038* 	{bolean} useFullBlock - if true, it will replace the scrubbed characters with the "█" symbol;
4039* 	{string} replaceWith - this string will replace the scrubbed content
4040*
4041
4042* @return {string | true | false}
4043* @example
4044* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", true )
4045* returns "email:, phone:",
4046*
4047* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", false )
4048* returns true
4049*
4050* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", { email:1 }{ replaceWith: [REDACTED/CAVIARDÉ] } )
4051* returns "email:[REDACTED/CAVIARDÉ], phone:123 123 1234"
4052*
4053* wb.findPotentialPII( "email:[email protected], phone:123 123 1234, numéro de cas 12345678", { "customCase":/\b(?:case[\s-]?number[\s\-\\.]?(?:\d{5,10}))|(?:numéro[\s-]?de[\s-]?cas[\s\-\\.]?(?:\d{5,10}))/ig }, { useFullBlock:1})
4054* returns "phone:████████████, email:█████████████, postalCode:██████, ██████████████████████"
4055*/
4056wb.findPotentialPII = function( str, scope, opts ) {
4057	if ( str && typeof str  !== "string" ) {
4058		return false;
4059	}
4060	var oRegEx = {
4061			digits: /\d(?:[\s\-\\.\\/]?\d){8,}(?!\d)/ig, //9digits or more pattern
4062			passport: /\b[A-Za-z]{2}[\s\\.-]*?\d{6}\b/ig, //canadian nr passport pattern
4063			email: /\b(?:[a-zA-Z0-9_\-\\.]+)(?:@|%40|%2540)(?:[a-zA-Z0-9_\-\\.]+)\.(?:[a-zA-Z]{2,5})\b/ig, //email pattern
4064			postalCode: /\b[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d\b/ig, //postal code pattern
4065			username: /(?:(username|user)[%20]?([:=]|(%EF%BC%9A))[^\s&]*)/ig,
4066			password: /(?:(password|pass)[%20]?([:=]|(%EF%BC%9A))[^\s&]*)/ig
4067		},
4068		isFound = false,
4069		txtMarker = opts && opts.replaceWith ? opts.replaceWith : "",
4070		toClean = typeof scope === "object" ? true : scope,
4071		arMatchedStr,
4072		settings = opts || {},
4073		defaultSettings = {
4074			isCustomExclusive: false,
4075			useFullBlock: false,
4076			replaceWith: ""
4077		},
4078		isFullBlock = settings.useFullBlock || false,
4079		validatedScope = typeof scope === "object" ? {} : oRegEx;
4080	settings = $.extend( {}, defaultSettings, settings );
4081
4082	if ( Object.keys( validatedScope ).length === 0 ) {
4083		if ( settings.isCustomExclusive ) {
4084			for ( var key in scope ) {
4085				if ( scope[ key ] instanceof RegExp ) {
4086					validatedScope[ key ] = scope[ key ];
4087				}
4088			}
4089		} else {
4090			if ( Object.keys( scope ).length === 1 && Object.values( scope )[ 0 ] instanceof RegExp ) {
4091				validatedScope = oRegEx;
4092				validatedScope[ Object.keys( scope )[ 0 ] ] = Object.values( scope )[ 0 ];
4093			} else {
4094				for ( var keyScope in scope ) {
4095					if ( Object.prototype.hasOwnProperty.call( oRegEx, keyScope ) ) {
4096						validatedScope [ keyScope ] = oRegEx [ keyScope ];
4097					} else {
4098						if ( scope[ keyScope ]  instanceof RegExp ) {
4099							validatedScope [ keyScope ] = scope [ keyScope ];
4100						}
4101					}
4102				}
4103			}
4104		}
4105	}
4106
4107	for ( var valKey in validatedScope ) {
4108		arMatchedStr = str.match( validatedScope[ valKey ] );
4109		if ( arMatchedStr ) {
4110			isFound = true;
4111			if ( toClean ) {
4112				txtMarker = isFullBlock ? "█".repeat( arMatchedStr[ 0 ].length ) : txtMarker;
4113				str = str.replaceAll( validatedScope[ valKey ], txtMarker );
4114			}
4115		}
4116	}
4117
4118	return toClean && isFound ? str : isFound;
4119};
4120
4121} )( wb, window );
4122
4123( function( $, undef ) {
4124"use strict";
4125
4126var methods,
4127	settings = {
4128		"default": "wet-boew"
4129	};
4130
4131methods = {
4132
4133	init: function( options ) {
4134		return $.extend( settings, options || {} );
4135	},
4136
4137	show: function( onlyAria ) {
4138		$( this ).each( function() {
4139			var $elm = $( this );
4140			$elm.attr( "aria-hidden", "false" );
4141			if ( onlyAria === undef ) {
4142				$elm.removeClass( "wb-inv" );
4143			}
4144		} );
4145	},
4146
4147	hide: function( onlyAria ) {
4148		$( this )
4149			.each( function() {
4150				var $elm = $( this );
4151				$elm.attr( "aria-hidden", "true" );
4152				if ( onlyAria === undef ) {
4153					return $elm.addClass( "wb-inv" );
4154				}
4155			} );
4156	},
4157
4158	toggle: function( to, from ) {
4159		$( this )
4160			.addClass( to )
4161			.removeClass( from );
4162	}
4163};
4164
4165$.fn.wb = function( method ) {
4166
4167	if ( methods[ method ] ) {
4168		methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
4169	} else if ( typeof method === "object" || !method ) {
4170		methods.init.apply( this, arguments );
4171	} else {
4172		$.error( "Method " + method + " does not exist on jquery.wb" );
4173	}
4174};
4175
4176} )( jQuery );
4177
4178/*
4179:focusable and :tabable jQuery helper expressions - https://github.com/jquery/jquery-ui/blob/24756a978a977d7abbef5e5bce403837a01d964f/ui/jquery.ui.core.js
4180*/
4181( function( $ ) {
4182
4183"use strict";
4184
4185function focusable( element, isTabIndexNotNaN, visibility ) {
4186	var map, mapName, img,
4187		nodeName = element.nodeName.toLowerCase( );
4188	if ( nodeName === "area" ) {
4189		map = element.parentNode;
4190		mapName = map.name;
4191		if ( !element.href || !mapName || map.nodeName.toLowerCase( ) !== "map" ) {
4192			return false;
4193		}
4194		img = $( "img[usemap=#" + mapName + "]" )[ 0 ];
4195		return !!img && visible( img );
4196	}
4197	if ( visibility ) {
4198		return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled :
4199			nodeName === "a" ?
4200				element.href || isTabIndexNotNaN :
4201				isTabIndexNotNaN ) &&
4202		visible( element ); /* the element and all of its ancestors must be visible */
4203	} else {
4204		return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled :
4205			nodeName === "a" ?
4206				element.href || isTabIndexNotNaN :
4207				isTabIndexNotNaN );
4208	}
4209}
4210
4211function visible( element ) {
4212	return $.expr.filters.visible( element ) && !$( element )
4213		.parents( )
4214		.addBack( )
4215		.filter( function() {
4216			return $.css( this, "visibility" ) === "hidden";
4217		} )
4218		.length;
4219}
4220
4221$.extend( $.expr[ ":" ], {
4222	data: $.expr.createPseudo ? $.expr.createPseudo( function( dataName ) {
4223		return function( elem ) {
4224			return !!$.data( elem, dataName );
4225		};
4226	} ) :
4227
4228	// support: jQuery <1.8
4229
4230		function( elem, i, match ) {
4231			return !!$.data( elem, match[ 3 ] );
4232		},
4233	focusable: function( element ) {
4234		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ), true );
4235	},
4236	discoverable: function( element ) {
4237		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
4238	},
4239	tabbable: function( element ) {
4240		var tabIndex = $.attr( element, "tabindex" ),
4241			isTabIndexNaN = isNaN( tabIndex );
4242		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
4243	}
4244} );
4245
4246} )( jQuery );
4247
4248/**
4249 * @title WET-BOEW Add to calendar
4250 * @overview Create an add to calendar button for an event
4251 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4252 * @author @ricokola
4253 */
4254( function( $, wb ) {
4255"use strict";
4256
4257/*
4258 * Variable and function definitions.
4259 * These are global to the plugin - meaning that they will be initialized once per page,
4260 * not once per instance of plugin on the page. So, this is a good place to define
4261 * variables that are common to all instances of the plugin on a page.
4262 */
4263var componentName = "wb-addcal",
4264	selector = ".provisional." + componentName,
4265	initEvent = "wb-init." + componentName,
4266	$document = wb.doc,
4267
4268	/**
4269	* @method init
4270	* @param {jQuery Event} event Event that triggered the function call
4271	*/
4272	init = function( event ) {
4273
4274		// Start initialization
4275		// returns DOM object = proceed with init
4276		// returns undefined = do not proceed with init (e.g., already initialized)
4277		var elm = wb.init( event, componentName, selector ),
4278			$elm = $( elm );
4279
4280		if ( elm ) {
4281
4282			wb.ready( $( elm ), componentName );
4283
4284			var properties = elm.querySelectorAll( "[property]" ),
4285				event_details = new Object(),
4286				place_details = [],
4287				i,
4288				i_len,
4289				prop_cache,
4290				googleLink,
4291				icsFile,
4292				i18nDict = {
4293					en: {
4294						"addcal-addto": "Add to",
4295						"addcal-calendar": "calendar",
4296						"addcal-other": "Other (Outlook, Apple, etc.)"
4297					},
4298					fr: {
4299						"addcal-addto": "Ajouter au",
4300						"addcal-calendar": "calendrier",
4301						"addcal-other": "Autre (Outlook, Apple, etc.)"
4302					}
4303				};
4304
4305			// Initiate dictionary
4306			i18nDict = i18nDict[ $( "html" ).attr( "lang" ) || "en" ];
4307			i18nDict = {
4308				addto: i18nDict[ "addcal-addto" ],
4309				calendar: i18nDict[ "addcal-calendar" ],
4310				ical: i18nDict[ "addcal-other" ]
4311			};
4312
4313			// Set date stamp with the date modified
4314			event_details.dtStamp = dtToISOString( $( "time[property='dateModified']" ) );
4315
4316			i_len = properties.length;
4317			for ( i = 0; i < i_len; i++ ) {
4318				prop_cache = properties[ i ];
4319				switch ( prop_cache.getAttribute( "property" ) ) {
4320				case "name":
4321
4322					// If the property=name is inside an element with typeof=Place defined
4323					if ( $( prop_cache ).parentsUntil( ( "." + componentName ), "[typeof=Place]" ).length ) {
4324						event_details.placeName = prop_cache.textContent;
4325					} else {
4326						event_details.name = prop_cache.textContent;
4327					}
4328					break;
4329				case "description":
4330					event_details.description = prop_cache.textContent.replace( /(\r\n|\n|\r)/gm, " " );
4331					break;
4332				case "startDate":
4333					event_details.sDate = dtToISOString( $( "time[property='startDate']", $elm ) );
4334					break;
4335				case "endDate":
4336					event_details.eDate = dtToISOString( $( "time[property='endDate']", $elm ) );
4337					break;
4338				case "location":
4339
4340					// If the location doesn't have typeof defined OR has typeof=VirtualLocation without URL inside.
4341					if ( !prop_cache.getAttribute( "typeof" ) || ( prop_cache.getAttribute( "typeof" ) === "VirtualLocation" && !$( prop_cache ).find( "[property=url]" ).length ) ) {
4342						event_details.placeName = prop_cache.textContent;
4343					}
4344					break;
4345				case "streetAddress":
4346					event_details.placeAddress = prop_cache.textContent;
4347					break;
4348				case "addressLocality":
4349					event_details.placeLocality = prop_cache.textContent;
4350					break;
4351				case "addressRegion":
4352					event_details.placeRegion = prop_cache.textContent;
4353					break;
4354				case "postalCode":
4355					event_details.placePostalCode = prop_cache.textContent;
4356					break;
4357				case "url":
4358
4359					// If the property=url is inside a property=location
4360					if ( $( prop_cache ).parentsUntil( ( "." + componentName ), "[property=location]" ).length ) {
4361						event_details.placeName = prop_cache.textContent;
4362					}
4363					break;
4364				}
4365			}
4366
4367			place_details.push( ( event_details.placeName || "" ), ( event_details.placeAddress || "" ), ( event_details.placeLocality || "" ), ( event_details.placeRegion || "" ), ( event_details.placePostalCode || "" ) );
4368
4369			// Error handling
4370			if ( !event_details.name ) {
4371				throw componentName + ": Event title is missing.";
4372			} else if ( !event_details.sDate ) {
4373				throw componentName + ": Start date is missing.";
4374			} else if ( !event_details.eDate ) {
4375				throw componentName + ": End date is missing.";
4376			}
4377
4378			// Set Unique Identifier (UID) and Date Stamp (DSTAMP)
4379			event_details.uid = window.location.href.replace( /\.|-|\/|:|[G-Zg-z]/g, "" ).toUpperCase().substr( -10 ) + "-" + event_details.sDate + "-" + event_details.dtStamp;
4380
4381			// Set google calendar link
4382			googleLink = encodeURI( "https://www.google.com/calendar/render?action=TEMPLATE" +  "&text=" + event_details.name +  "&details=" +
4383			event_details.description +  "&dates=" + event_details.sDate + "/" + event_details.eDate + "&location=" + place_details.join( " " ) );
4384
4385			// Set ICS file for Outlook, Apple and other calendars
4386			icsFile = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//WET-BOEW//Add to Calendar v4.0//\n
4386BEGIN:VEVENT\nDTSTAMP:" + event_details.dtStamp + "\nSUMMARY:" + event_details.name +  "\nDESCRIPTION:" + event_details.description + "\nUID:" + event_details.uid + "\nDTSTART:" + event_details.sDate + "\nDTEND:" + event_details.eDate + "\nLOCATION:" + place_details.join( " " ) + "\nEND:VEVENT\nEND:VCALENDAR";
4387
4388			elm.dataset.icsFile = icsFile;
4389
4390			// Create and add details summary to the wb-addcal event and initiate the unordered list
4391			$elm.append( "<details class='max-content " + componentName + "-buttons'><summary>" + i18nDict.addto + " " + i18nDict.calendar +
4392			"</summary><ul class='list-unstyled mrgn-bttm-0'><li><a class='btn btn-link' href='" + googleLink.replace( /'/g, "%27" ) + "' target='_blank' rel='noreferrer noopener'>Google<span class='wb-inv'>" + i18nDict.calendar + "</span></a></li><li><button class='btn btn-link download-ics'>" + i18nDict.ical +
4393			"<span class='wb-inv'>Calendar</span></button></li></ul></details>" );
4394		}
4395
4396		wb.ready( $( elm ), componentName );
4397
4398	};
4399
4400// Convert date to ISO string and formating for ICS file
4401var dtToISOString = function( date ) {
4402	if ( date.is( "[datetime]" ) ) {
4403		date = date.attr( "datetime" );
4404	} else {
4405		date = date.text();
4406	}
4407
4408	return new Date( date ).toISOString().replace( /\..*[0-9]/g, "" ).replace( /-|:|\./g, "" );
4409};
4410
4411$document.on( "click", ".download-ics", function( event ) {
4412	var icsFile = $( event.currentTarget ).parentsUntil( "." + componentName ).parent()[ 0 ];
4413	icsFile =  $( icsFile ).attr( "data-ics-file" );
4414	wb.download( new Blob( [ icsFile ], { type: "text/calendar;charset=utf-8" } ), "evenement-gc-event.ics" );
4415} );
4416
4417// Bind the init event of the plugin
4418$document.on( "timerpoke.wb " + initEvent, selector, init );
4419
4420// Add the timer poke to initialize the plugin
4421wb.add( selector );
4422
4423} )( jQuery, wb );
4424
4425/**
4426 * @title WET-BOEW Ajax Fetch [ ajax-fetch ]
4427 * @overview A basic AjaxLoader wrapper for WET-BOEW
4428 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4429 * @author WET Community
4430 */
4431( function( $, wb, DOMPurify ) {
4432"use strict";
4433
4434/*
4435 * Variable and function definitions.
4436 * These are global to the plugin - meaning that they will be initialized once per page,
4437 * not once per instance of plugin on the page. So, this is a good place to define
4438 * variables that are common to all instances of the plugin on a page.
4439 */
4440var $document = wb.doc;
4441
4442// Event binding
4443$document.on( "ajax-fetch.wb", function( event ) {
4444
4445	// TODO: Remove event.element in future versions
4446	var caller = event.element || event.target,
4447		fetchOpts = event.fetch,
4448		urlParts = fetchOpts.url.split( " " ),
4449		url = urlParts[ 0 ],
4450		urlSubParts = url.split( "#" ),
4451		urlHash = urlSubParts[ 1 ],
4452		selector = urlParts[ 1 ] || ( urlHash ? "#" + urlHash : false ),
4453		fetchData = {},
4454		callerId, fetchNoCacheURL, urlSub,
4455		fetchNoCache = fetchOpts.nocache,
4456		fetchNoCacheKey = fetchOpts.nocachekey || wb.cacheBustKey || "wbCacheBust";
4457
4458	// Separate the URL from the filtering criteria
4459	if ( selector ) {
4460		fetchOpts.url = urlParts[ 0 ];
4461	}
4462
4463	if ( fetchNoCache ) {
4464		if ( fetchNoCache === "nocache" ) {
4465			fetchNoCacheURL = wb.guid();
4466		} else {
4467			fetchNoCacheURL = wb.sessionGUID();
4468		}
4469		fetchNoCacheURL = fetchNoCacheKey + "=" + fetchNoCacheURL;
4470
4471		urlSub = urlSubParts[ 0 ];
4472		if ( urlSub.indexOf( "?" ) !== -1 ) {
4473			url = urlSub + "&" + fetchNoCacheURL + ( urlHash ? "#" + urlHash : "" );
4474		} else {
4475			url = urlSub + "?" + fetchNoCacheURL + ( urlHash ? "#" + urlHash : "" );
4476		}
4477
4478		fetchOpts.url = url;
4479	}
4480
4481	// Filter out any events triggered by descendants
4482	if ( caller === event.target || event.currentTarget === event.target ) {
4483
4484		if ( !caller.id ) {
4485			caller.id = wb.getId();
4486		}
4487		callerId = caller.id;
4488
4489		// Ensure we don't allow jsonp load
4490		if ( fetchOpts.dataType && fetchOpts.dataType === "jsonp" ) {
4491			fetchOpts.dataType = "json";
4492		}
4493		if ( fetchOpts.jsonp ) {
4494			fetchOpts.jsonp = false;
4495		}
4496
4497		$.ajax( fetchOpts )
4498			.done( function( response, status, xhr ) {
4499				var responseType = typeof response;
4500
4501				if ( selector ) {
4502					response = $( "<div>" + response + "</div>" ).find( selector );
4503				}
4504
4505				fetchData.pointer = $( "<div id='" + wb.getId() + "' data-type='" + responseType + "'></div>" )
4506					.append( responseType === "string" ? response : "" );
4507
4508				if ( !xhr.responseJSON ) {
4509					try {
4510						response = $( response );
4511					} catch ( e ) {
4512						response = DOMPurify.sanitize( xhr.responseText );
4513					}
4514				} else {
4515					response = xhr.responseText;
4516				}
4517
4518				fetchData.response = response;
4519				fetchData.hasSelector = !!selector;
4520				fetchData.status = status;
4521				fetchData.xhr = xhr;
4522
4523				$( "#" + callerId ).trigger( {
4524					type: "ajax-fetched.wb",
4525					fetch: fetchData
4526				}, this );
4527			} )
4528			.fail( function( xhr, status, error ) {
4529				$( "#" + callerId ).trigger( {
4530					type: "ajax-failed.wb",
4531					fetch: {
4532						xhr: xhr,
4533						status: status,
4534						error: error
4535					}
4536				}, this );
4537			}, this );
4538	}
4539} );
4540
4541} )( jQuery, wb, DOMPurify );
4542
4543/**
4544 * @title WET-BOEW Set background image
4545 * @overview Apply a background image or detects the change in screen width and replace the background image accordingly
4546 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4547 * @author @namjohn920, @duboisp
4548 */
4549( function( $, wb ) {
4550"use strict";
4551
4552/*
4553 * Variable and function definitions.
4554 * These are global to the plugin - meaning that they will be initialized once per page,
4555 * not once per instance of plugin on the page. So, this is a good place to define
4556 * variables that are common to all instances of the plugin on a page.
4557 */
4558var $document = wb.doc,
4559	$window = wb.win,
4560	componentName = "wb-bgimg",
4561	selector = "[data-bgimg-srcset], [data-bgimg]",
4562	bgViews = {},
4563	ids = [],
4564
4565	init = function( event ) {
4566
4567		var elm, elmId,
4568			bgImg, bgimgSrcset, bgRawViews,
4569			i, i_len, i_views,
4570			imgSrc, imgSize;
4571
4572		// Start initialization
4573		// returns DOM object = proceed with init
4574		// returns undefined = do not proceed with init (e.g., already initialized)
4575		elm = wb.init( event, componentName, selector );
4576
4577		if ( elm ) {
4578
4579			// Ensure the feature have an ID.
4580			if ( !elm.id ) {
4581				elm.id = wb.getId();
4582			}
4583			elmId = elm.id;
4584
4585			// Apply default background image
4586			bgImg = elm.dataset.bgimg;
4587			if ( bgImg ) {
4588				elm.style.backgroundImage = "url(" + bgImg + ")";
4589			}
4590
4591			// Apply background image set if defined
4592			bgimgSrcset = elm.dataset.bgimgSrcset;
4593			if ( bgimgSrcset ) {
4594				ids.push( elm.id );
4595				bgRawViews = elm.dataset.bgimgSrcset.split( "," );
4596				i_len = bgRawViews.length;
4597				bgViews[ elmId ] = [];
4598
4599				for ( i = 0; i < i_len; i++ ) {
4600					i_views = bgRawViews[ i ].trim().split( " " );
4601
4602					imgSrc = i_views[ 0 ];
4603					imgSize =  i_views[ i_views.length - 1 ];
4604
4605					imgSize = parseInt( imgSize.substring( 0, imgSize.length - 1 ) );
4606					bgViews[ elmId ].push( [ imgSrc, imgSize ] );
4607				}
4608
4609				bgViews[ elmId ].sort(
4610					function( a, b ) {
4611						return a[ 1 ] > b[ 1 ] ? 1 : -1;
4612					}
4613				);
4614
4615				selectImage();
4616
4617				// Add the resize listener
4618				$window.on( "resize", selectImage );
4619			}
4620
4621			// Identify that initialization has completed
4622			wb.ready( $( elm ), componentName );
4623		}
4624	},
4625
4626	selectImage = function() {
4627		var screenWidth = window.innerWidth,
4628			optimizedLink = {},
4629			i, i_len = ids.length, j,
4630			optimizedSize, currentId, currentId_len,
4631			currentInput,
4632			link, elm;
4633
4634		for ( i = 0; i < i_len; i++ ) {
4635			optimizedSize = Infinity;
4636			currentId = bgViews[ ids[ i ] ];
4637			currentId_len = currentId.length;
4638
4639			for ( j = 0; j < currentId_len; j++ ) {
4640				currentInput = currentId[ j ];
4641				if ( currentInput[ 1 ] >= screenWidth ) {
4642					if ( optimizedSize > currentInput[ 1 ] ) {
4643						optimizedSize = currentInput[ 1 ];
4644						optimizedLink[ ids[ i ] ] = currentInput[ 0 ];
4645					}
4646				}
4647			}
4648			if ( optimizedSize === Infinity ) {
4649				optimizedLink[ ids[ i ] ] = currentId[ currentId_len - 1 ][ 0 ];
4650			}
4651		}
4652
4653		for ( link in optimizedLink ) {
4654			elm = document.getElementById( link );
4655			elm.style.backgroundImage = "url(" + optimizedLink[ link ] + ")";
4656		}
4657	};
4658
4659// Bind the init event of the plugin
4660$document.on( "timerpoke.wb wb-init." + componentName, selector, init );
4661
4662// Add the timer poke to initialize the plugin
4663wb.add( selector );
4664
4665} )( jQuery, wb );
4666
4667/**
4668 * @title WET-BOEW Events Calendar
4669 * @overview Dynamically generates a calendar interface for navigating a list of events.
4670 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
4671 * @author WET Community
4672 */
4673( function( $, window, wb ) {
4674"use strict";
4675
4676/*
4677 * Variable and function definitions.
4678 * These are global to the plugin - meaning that they will be initialized once per page,
4679 * not once per instance of plugin on the page. So, this is a good place to define
4680 * variables that are common to all instances of the plugin on a page.
4681 */
4682var componentName = "wb-calevt",
4683	selector = "." + componentName,
4684	componentEventName = componentName + "-cal",
4685	selectorEvent = "." + componentEventName,
4686	initEvent = "wb-init" + selector,
4687	evDetails = "ev-details",
4688	setFocusEvent = "focus",
4689	dataAttr = componentName,
4690	$document = wb.doc,
4691	hiddenClass = "hidden",
4692
4693	/**
4694	 * @method init
4695	 * @param {jQuery Event} event Event that triggered this handler
4696	 */
4697	init = function( event ) {
4698
4699		// Start initialization
4700		// returns DOM object = proceed with init
4701		// returns undefined = do not proceed with init (e.g., already initialized)
4702		var elm = wb.init( event, componentName, selector ),
4703			$elm;
4704
4705		if ( elm ) {
4706			$elm = $( elm );
4707
4708			// Load ajax content
4709			$.when.apply( $, $.map( $elm.find( "[data-calevt]" ), getAjax ) )
4710				.always( function() {
4711					processEvents( $elm );
4712
4713					// Identify that initialization has completed
4714					wb.ready( $elm, componentName );
4715				} );
4716		}
4717	},
4718
4719	getAjax = function( ajaxContainer ) {
4720		var $ajaxContainer = $( ajaxContainer ),
4721			urls = $ajaxContainer.data( "calevt" ).split( /\s+/ ),
4722			dfd = $.Deferred(),
4723			len = urls.length,
4724			promises = [],
4725			i, appendData;
4726
4727		appendData = function( data ) {
4728			$ajaxContainer.append( $.trim( data ) );
4729		};
4730
4731		for ( i = 0; i < len; i += 1 ) {
4732			promises.push( $.get( urls[ i ], appendData, "html" ) );
4733		}
4734
4735		$.when.apply( $, promises ).always( function() {
4736			dfd.resolve();
4737		} );
4738
4739		return dfd.promise();
4740	},
4741
4742	processEvents = function( $elm ) {
4743		var settings = $.extend( {}, window[ componentName ], $elm.data( dataAttr ) ),
4744			year, month, events, containerId, $container,
4745			minDate, maxDate, minDateTime, maxDateTime,
4746			currDate = new Date(),
4747			currDateTime = currDate.getTime();
4748
4749		events = getEvents( $elm );
4750		containerId = $elm.data( "calevtSrc" );
4751		$container = $( "#" + containerId ).addClass( componentEventName );
4752
4753		year = settings.year;
4754		month = settings.month;
4755
4756		if ( $elm.data( "calevtMinDate" ) ) {
4757			minDate = getLocaleDate( $elm.data( "calevtMinDate" ) );
4758		}
4759		if ( $elm.data( "calevtMaxDate" ) ) {
4760			maxDate = getLocaleDate( $elm.data( "calevtMaxDate" ) );
4761		}
4762
4763		if ( !minDate || ( events.minDate < minDate ) ) {
4764			minDate = events.minDate;
4765		}
4766		if ( !maxDate || ( events.maxDate > maxDate ) ) {
4767			maxDate = events.maxDate;
4768		}
4769
4770		minDateTime = minDate.getTime();
4771		maxDateTime = maxDate.getTime();
4772
4773		if ( !year && minDateTime < currDateTime && currDateTime < maxDateTime ) {
4774			year = currDate.getFullYear();
4775		} else if ( !year && currDateTime < minDateTime ) {
4776			year = minDate.getFullYear();
4777		} else if ( !year && maxDateTime < currDateTime ) {
4778			year = maxDate.getFullYear();
4779		}
4780
4781		if ( !month && minDateTime < currDateTime && currDate.getTime() < maxDateTime ) {
4782			month = currDate.getMonth();
4783		} else if ( !month && currDateTime < minDateTime ) {
4784			month = minDate.getMonth();
4785		} else if ( !month && maxDateTime < currDateTime ) {
4786			month = maxDate.getMonth();
4787		}
4788
4789		wb.calendar.create( $container, {
4790			year: year,
4791			month: month,
4792			minDate: minDate,
4793			maxDate: maxDate,
4794			daysCallback: addEvents,
4795			events: events.list,
4796			$events: $elm
4797		} );
4798	},
4799
4800	daysBetween = function( dateLow, dateHigh ) {
4801
4802		// Simplified conversion to date object
4803		var date1 = wb.date.convert( dateLow ),
4804			date2 = wb.date.convert( dateHigh ),
4805			dstAdjust = 0,
4806			oneMinute = 1000 * 60,
4807			oneDay = oneMinute * 60 * 24,
4808			diff;
4809
4810		// Equalize times in case date objects have them
4811		date1.setHours( 0 );
4812		date1.setMinutes( 0 );
4813		date1.setSeconds( 0 );
4814		date2.setHours( 0 );
4815		date2.setMinutes( 0 );
4816		date2.setSeconds( 0 );
4817
4818		// Take care of spans across Daylight Saving Time changes
4819		if ( date2 > date1 ) {
4820			dstAdjust = ( date2.getTimezoneOffset() - date1.getTimezoneOffset() ) * oneMinute;
4821		} else {
4822			dstAdjust = ( date1.getTimezoneOffset() - date2.getTimezoneOffset() ) * oneMinute;
4823		}
4824		diff = Math.abs( date2.getTime() - date1.getTime() ) - dstAdjust;
4825		return Math.ceil( diff / oneDay );
4826	},
4827
4828	///TODO: Review this code
4829	getEvents = function( obj ) {
4830		var directLinking = !( $( obj ).hasClass( "evt-anchor" ) ),
4831			events = {
4832				minDate: null,
4833				maxDate: null,
4834				iCount: 0,
4835				list: [
4836					{
4837						a: 1
4838					}
4839				]
4840			},
4841			objEventsList = obj.find( "ul, ol" ).first(),
4842			objEventsListItems = objEventsList.find( "> li:not(.wb-fltr-out)" ),
4843			iLen = objEventsListItems.length,
4844			dateTimeRegExp = /datetime\s+\{date:\s*(\d+-\d+-\d+)\}/,
4845			i, $event, event, $objTitle, title, link, href, target,
4846			linkId, date, tCollection, tCollectionTemp,	strDate1,
4847			strDate2, z, zLen, className, dateClass;
4848
4849		for ( i = 0; i !== iLen; i += 1 ) {
4850			$event = objEventsListItems.eq( i );
4851			event = $event[ 0 ];
4852			$objTitle = $event.find( "*:header:first" );
4853			className = $objTitle.attr( "class" );
4854			title = $objTitle.text();
4855			link = $event.find( "a" )[ 0 ];
4856			href = link.getAttribute( "href" );
4857			target = link.getAttribute( "target" );
4858			zLen = 1;
4859
4860			/*
4861			 * Modification direct-linking or page-linking
4862			 *	- added the ability  to have class set the behaviour of the links
4863			 *	- default is to use the link of the item as the event link in the calendar
4864			 *	- 'evt-anchor' class dynamically generates page anchors on the links it maps to the event
4865			 */
4866			if ( !directLinking ) {
4867				linkId = event.id || wb.getId();
4868				event.id = linkId;
4869				href = "#" + linkId;
4870			}
4871
4872			date = new Date();
4873			date.setHours( 0, 0, 0, 0 );
4874			tCollection = event.getElementsByTagName( "time" );
4875
4876			/*
4877			 * Date spanning capability
4878			 *   - since there may be some dates that are capable of spanning over months we need to identify them
4879			 *     the process is see how many time nodes are in the event. 2 nodes will trigger a span
4880			 */
4881			if ( tCollection.length !== 0 ) {
4882				tCollectionTemp = tCollection[ 0 ];
4883				strDate1 = tCollectionTemp.nodeName.toLowerCase() === "time" ?
4884					tCollectionTemp.getAttribute( "datetime" ).substr( 0, 10 ).split( "-" ) :
4885					tCollectionTemp.className.match( dateTimeRegExp )[ 1 ].substr( 0, 10 ).split( "-" );
4886
4887				// Convert to zero-based month
4888				strDate1[ 1 ] = strDate1[ 1 ] - 1;
4889
4890				date.setFullYear( strDate1[ 0 ], strDate1[ 1 ], strDate1[ 2 ] );
4891
4892				if ( tCollection.length !== 1 ) {
4893
4894					// This is a spanning event
4895					tCollectionTemp = tCollection[ 1 ];
4896					strDate2 = tCollectionTemp.nodeName.toLowerCase() === "time" ?
4897						tCollectionTemp.getAttribute( "datetime" ).substr( 0, 10 ).split( "-" ) :
4898						tCollectionTemp.className.match( dateTimeRegExp )[ 1 ].substr( 0, 10 ).split( "-" );
4899
4900					// Convert to zero-based month
4901					strDate2[ 1 ] = strDate2[ 1 ] - 1;
4902
4903					zLen += daysBetween( strDate1, strDate2 );
4904				}
4905
4906				// Now loop in events to load up all the days that it would be on tomorrow.setDate(tomorrow.getDate() + 1);
4907				for ( z = 0; z !== zLen; z += 1 ) {
4908					if ( z !== 0 ) {
4909						date = new Date( date.setDate( date.getDate() + 1 ) );
4910					}
4911
4912					if ( events.minDate === null || date < events.minDate ) {
4913						events.minDate = date;
4914					}
4915
4916					if ( events.maxDate === null || date > events.maxDate ) {
4917						events.maxDate = date;
4918					}
4919
4920					events.list[ events.iCount ] = {
4921						title: title,
4922						date: new Date( date.getTime() ),
4923						href: href,
4924						target: target
4925					};
4926
4927					// Add a viewfilter
4928					dateClass = "filter-" + ( date.getFullYear() ) + "-" +
4929						wb.string.pad( date.getMonth() + 1, 2 );
4930					if ( !className ) {
4931						className = dateClass;
4932					} else if ( className.indexOf( dateClass ) === -1 ) {
4933						className += " " + dateClass;
4934					}
4935					events.iCount += 1;
4936				}
4937				$objTitle.attr( "class", className );
4938			}
4939
4940		// End of loop through objects/events
4941		}
4942
4943		//Sort events
4944		events.list.sort( function( firstEvent, secondEvent ) {
4945			return firstEvent.date - secondEvent.date;
4946		} );
4947
4948		return events;
4949	},
4950
4951	addEvents = function( year, month, $days ) {
4952		var eventsList = this.events,
4953			i, eLen, date, dayIndex, $day, $dayEvents, event, eventMonth;
4954
4955		/*
4956		 * Determines for each event, if it occurs in the display month
4957		 */
4958		for ( i = 0, eLen = eventsList.length; i !== eLen; i += 1 ) {
4959			event = eventsList[ i ];
4960			date = event.date;
4961
4962			if ( date.getFullYear() === year ) {
4963				eventMonth = date.getMonth();
4964				if ( eventMonth > month ) {
4965
4966					//End the loop if the next event is in a future month because events are sorted chronologically
4967					break;
4968				} else if ( date.getMonth() === month ) {
4969					dayIndex = date.getDate() - 1;
4970					$day = $( $days[ dayIndex ] );
4971
4972					//Get the appropriate day events if a day link exists
4973					if ( $day.parent().get( 0 ).nodeName !== "A" ) {
4974						$dayEvents = $day.next();
4975					} else {
4976						$dayEvents = $day.parent().next();
4977					}
4978
4979					//Create the event list container if it doesn't exist
4980					if ( $dayEvents.length !== 1 ) {
4981						$dayEvents = $( "<ul></ul>" ).insertAfter( $day );
4982
4983						//Determine the focus based on the day before
4984						if ( dayIndex && $days[ dayIndex - 1 ].parentNode.nodeName === "A" ) {
4985							$day.wrap( "<a class='cal-evt' tabindex='-1'></a>" );
4986						} else {
4987							$day.wrap( "<a class='cal-evt'></a>" );
4988						}
4989						$day.parent().attr( "href", "javascript:;" );
4990					}
4991
4992					//Add the event to the list
4993					$dayEvents.append( "<li><a tabindex='-1' class='cal-evt-lnk' href='" + event.href + "'>" + event.title + "</a></li>" );
4994				}
4995			}
4996		}
4997	},
4998
4999	filterEvents = function( year, month ) {
5000		this.find( "li.cal-disp-onshow" )
5001			.addClass( hiddenClass )
5002			.has( ":header[class*=filter-" + year + "-" +
5003				wb.string.pad( parseInt( month, 10 ) + 1, 2 ) + "]" )
5004			.removeClass( hiddenClass );
5005	},
5006
5007	showEvents = function() {
5008		$( this )
5009			.next()
5010			.addClass( evDetails );
5011	},
5012
5013	hideEvents = function() {
5014		var $link = $( this ),
5015			$cell = $link.closest( "td" );
5016
5017		setTimeout( function() {
5018			if ( $cell.find( "a:focus" ).length === 0 ) {
5019				$cell.find( "ul" )
5020					.removeClass( evDetails )
5021					.find( "a" )
5022					.attr( "tabindex", "-1" );
5023			}
5024		}, 5 );
5025	},
5026
5027	getLocaleDate = function( dateString ) {
5028		var date = new Date(),
5029			dateComponents = dateString.split( "-" );
5030
5031		dateComponents[ 1 ] = dateComponents[ 1 ] - 1;	// Convert to zero-based month
5032		date.setFullYear( dateComponents[ 0 ], dateComponents[ 1 ], dateComponents[ 2 ] );
5033
5034		return date;
5035	};
5036
5037// Bind the init event of the plugin
5038$document.on( "timerpoke.wb " + initEvent + " wb-redraw" + selector, selector, function( event ) {
5039
5040	var eventType = event.type,
5041		$elm = $( "#" + event.target.id ),
5042		calendarId = event.currentTarget.dataset.calevtSrc;
5043
5044	switch ( eventType ) {
5045	case "timerpoke":
5046	case "wb-init":
5047		init( event );
5048		break;
5049
5050	case "wb-redraw":
5051		$( "#" + calendarId + " .wb-clndr" ).remove();
5052		processEvents( $elm );
5053		$elm.trigger( "wb-updated" + selector );
5054		break;
5055	}
5056} );
5057
5058$document.on( "wb-navigate.wb-clndr", selectorEvent, function( event ) {
5059	var lib = event.target.lib,
5060		$calEvent;
5061
5062	if ( lib ) {
5063		$calEvent = lib.$events;
5064
5065		if ( $calEvent ) {
5066			filterEvents.call( $calEvent, event.year, event.month );
5067
5068			//TODO: Added for backwards compatibility. Remove in previous versions
5069			$calEvent.trigger( "wb-updated" + selector );
5070		}
5071	}
5072} );
5073
5074$document.on( "focusin focusout keydown", selectorEvent + " .cal-evt", function( event ) {
5075	var eventType = event.type,
5076		$link;
5077
5078	switch ( eventType ) {
5079	case "focusin":
5080		showEvents.call( event.target );
5081		break;
5082	case "focusout":
5083		hideEvents.call( event.target );
5084		break;
5085	case "keydown":
5086		$link = $( event.target );
5087		if ( ( event.which === 13 || event.which === 32 ) && $link.hasClass( "cal-evt" ) ) {
5088			$( event.target ).next().find( "a:first" ).trigger( setFocusEvent );
5089		}
5090		break;
5091	}
5092} );
5093
5094$document.on( "keydown", selectorEvent + " td > ul li", function( event ) {
5095	var $item = $( event.currentTarget ),
5096		$toFocus, $itemParent;
5097
5098	switch ( event.which ) {
5099	case 38:
5100		$toFocus = $item.prev().find( "a" );
5101		if ( $toFocus.length === 0 ) {
5102			$toFocus = $item.siblings( ":last" ).find( "a" );
5103		}
5104		$toFocus.trigger( setFocusEvent );
5105		break;
5106	case 40:
5107		$toFocus = $item.next().find( "a" );
5108		if ( $toFocus.length === 0 ) {
5109			$toFocus = $item.siblings( ":first" ).find( "a" );
5110		}
5111		$toFocus.trigger( setFocusEvent );
5112		break;
5113	case 27:
5114		$itemParent = $item.closest( "td" ).children( "a" );
5115		$itemParent.trigger( setFocusEvent );
5116		break;
5117	}
5118} );
5119
5120$document.on( "focusout", selectorEvent + " td > ul", function( event ) {
5121	hideEvents.call( event.target );
5122} );
5123
5124// Add the timer poke to initialize the plugin
5125wb.add( selector );
5126
5127} )( jQuery, window, wb );
5128
5129( function( $, DOMPurify, window, document, wb, undef ) {
5130
5131var i18nText,
5132	$document = wb.doc,
5133	selector = ".wb-clndr",
5134	navigateEvent = "wb-navigate" + selector,
5135	inited = false,
5136	currDate = new Date(),
5137	currYear = currDate.getFullYear(),
5138	currMonth = currDate.getMonth(),
5139	defaults = {
5140		year: currYear,
5141		month: currMonth,
5142		minDate: new Date( currYear, 0, 1 ),
5143		maxDate: new Date( currYear, 11, 31 )
5144	},
5145	$calBase,
5146
5147	init = function() {
5148		var i18n = wb.i18n,
5149			textWeekDayNames, textMonthNames;
5150
5151		i18nText = {
5152			monthNames: i18n( "mnths" ),
5153			prevMonth: i18n( "prvMnth" ),
5154			nextMonth: i18n( "nxtMnth" ),
5155			goToYear: i18n( "cal-goToYr" ),
5156			goToMonth: i18n( "cal-goToMnth" ),
5157			dayNames: i18n( "days" ),
5158			currDay: i18n( "currDay" ),
5159			format: i18n( "cal-format" ),
5160			calendar: i18n( "cal" )
5161		};
5162
5163		textWeekDayNames = i18nText.dayNames;
5164		textMonthNames = i18nText.monthNames;
5165
5166		$calBase = $( "<div class='wb-clndr' role='application' aria-label='" + i18nText.calendar + "'>" +
5167			"<div class='cal-nav'>" +
5168				"<span class='wb-inv current-month' aria-live='polite'></span>" + // Added for screen-readers
5169				"<button type='button' class='btn pull-left cal-month-prev'>" +
5170					"<span class='glyphicon glyphicon-arrow-left' aria-hidden='true'></span>" +
5171					"<span class='wb-inv'>" + i18nText.prevMonth + "<span></span></span>" +
5172				"</button>" +
5173				"<button type='button' class='btn pull-right cal-month-next'>" +
5174					"<span class='glyphicon glyphicon-arrow-right' aria-hidden='true'></span>" +
5175					"<span class='wb-inv'>" + i18nText.nextMonth + "<span></span></span>" +
5176				"</button>" +
5177				"<div class='form-group'>" +
5178					"<label><span class='wb-inv'>" + i18nText.goToYear + "</span><select class='cal-year'></select></label>\n" +
5179					"<label><span class='wb-inv'>" + i18nText.goToMonth + "</span><select class='cal-month'>" +
5180						( function() {
5181							var months = "",
5182								m;
5183
5184							for ( m = 0; m < 12; m += 1 ) {
5185								months += "<option value='" + m + "'>" + textMonthNames[ m ] + "</option>";
5186							}
5187
5188							return months;
5189						} )() +
5190					"</select></label>" +
5191				"</div>" +
5192			"</div>" +
5193			"<table>" +
5194				"<thead>" +
5195					"<tr>" +
5196						( function() {
5197							var days = "",
5198								d;
5199
5200							for ( d = 0; d < 7; d += 1 ) {
5201								days += "<th><abbr title='" + textWeekDayNames[ d ] + "'>" + textWeekDayNames[ d ].substr( 0, 1 ) + "</abbr></th>";
5202							}
5203
5204							return days;
5205						} )() +
5206					"</tr>" +
5207				"</thead>" +
5208				"<tbody class='cal-days'></tbody>" +
5209			"</table>" +
5210		"</div>" );
5211
5212		inited = true;
5213	},
5214
5215	createCalendar = function( $container, settings ) {
5216		var calendar = {
5217				reInit: initCalendar
5218			}, $calendarObj, calendarObj;
5219
5220		if ( !inited ) {
5221			init();
5222		}
5223
5224		$calendarObj = $calBase.clone();
5225		calendarObj = $calendarObj.get( 0 );
5226
5227		calendar.$o = $calendarObj;
5228		calendar.o = calendarObj;
5229		calendarObj.lib = calendar;
5230
5231		$calendarObj.appendTo( $container );
5232
5233		calendar.reInit( settings );
5234
5235		return calendar;
5236	},
5237
5238	initCalendar = function( settings ) {
5239		var years = "",
5240			$calendarObj = this.$o,
5241			$yearField, lastYear, y, defaultsPartial;
5242
5243		settings = settings || {};
5244
5245		if ( settings.year !== undef && settings.month !== undef ) {
5246			defaultsPartial = {
5247				minDate: new Date( settings.year, 0, 1 ),
5248				maxDate: new Date( settings.year, 11, 31 )
5249			};
5250			$.extend( this, defaultsPartial, settings );
5251		} else {
5252			$.extend( this, defaults );
5253		}
5254
5255		//Generates the list of years
5256		lastYear = this.maxDate.getFullYear();
5257		$yearField = $calendarObj.find( ".cal-year" ).empty();
5258		for ( y = this.minDate.getFullYear(); y <= lastYear; y += 1 ) {
5259			years += "<option value='" + y + "'>" + y + "</option>";
5260		}
5261		$yearField.append( years );
5262
5263		$calendarObj
5264			.trigger( {
5265				type: navigateEvent,
5266				year: this.year,
5267				month: this.month,
5268				initEvent: true
5269			} );
5270	},
5271
5272	createDays = function( calendar, year, month ) {
5273		var $container = $( calendar ).find( ".cal-days" ),
5274			daysContainer = $container.get( 0 ),
5275			dayCount = 1,
5276			textCurrentDay = i18nText.currDay,
5277			lib = calendar.lib,
5278			minDate = lib.minDate,
5279			maxDate = lib.maxDate,
5280			callback = lib.daysCallback,
5281			row, cell,
5282			date, firstDay, lastDay, currYear, currMonth, currDay, week, day, className, isCurrentDate, isoDate, printDate, breakAt
5282End, days, inRange;
5283
5284		date = new Date( year, month, 1 );
5285
5286		firstDay = date.getDay();
5287		date.setMonth( month + 1, 0 );
5288		lastDay = date.getDate();
5289
5290		// Get the current date
5291		date = new Date();
5292		currYear = date.getFullYear();
5293		currMonth = date.getMonth();
5294		currDay = date.getDate();
5295
5296		// Clean all existing rows
5297		$container.empty();
5298
5299		for ( week = 1; week < 7; week += 1 ) {
5300			row = daysContainer.insertRow();
5301
5302			for ( day = 0; day < 7; day += 1 ) {
5303
5304				if ( ( week === 1 && day < firstDay ) || ( dayCount > lastDay ) ) {
5305
5306					// Creates empty cells | Cree les cellules vides
5307					cell = row.insertCell();
5308					cell.classList.add( "cal-empty" );
5309					cell.textContent = " ";
5310
5311				} else {
5312
5313					// Creates date cells | Cree les cellules de date
5314					isCurrentDate = ( dayCount === currDay && month === currMonth && year === currYear );
5315					className = "cal-index-" + dayCount + ( isCurrentDate ? " cal-curr-day " : "" );
5316
5317					date.setFullYear( year, month, dayCount );
5318					isoDate = date.toLocalISOString().substr( 0, 10 );
5319					printDate = displayDate( date ) + ( isCurrentDate ? "<span class='wb-inv'>" + textCurrentDay + "</span>" : "" );
5320
5321					cell = row.insertCell();
5322					cell.setAttribute( "class", className );
5323					cell.innerHTML = DOMPurify.sanitize( "<time datetime='" + isoDate  + "'>" + printDate + "</time>" );
5324
5325					if ( dayCount >= lastDay ) {
5326						breakAtEnd = true;
5327					}
5328
5329					dayCount += 1;
5330				}
5331			}
5332			if ( breakAtEnd ) {
5333				break;
5334			}
5335		}
5336
5337		if ( callback ) {
5338			days = $container.find( "time" );
5339			inRange = {};
5340
5341			if ( year === minDate.getFullYear() && month === minDate.getMonth() ) {
5342				inRange.min = minDate.getDate() - 1;
5343			}
5344
5345			if ( year === maxDate.getFullYear() && month === maxDate.getMonth() ) {
5346				inRange.max = maxDate.getDate() - 1;
5347			}
5348
5349			callback.call( calendar.lib, year, month, days, inRange );
5350		}
5351	},
5352
5353	displayDate = function( date ) {
5354		var textWeekDayNames = i18nText.dayNames,
5355			textMonthNames = i18nText.monthNames;
5356
5357		return i18nText.format.replace( /\{ddd\}|\{d\}|\{M\}|\{Y\}/g, function( match ) {
5358			switch ( match ) {
5359			case "{ddd}":
5360				return textWeekDayNames[ parseInt( date.getDay(), 10 ) ];
5361			case "{d}":
5362				return parseInt( date.getDate(), 10 );
5363			case "{M}":
5364				return textMonthNames[ parseInt( date.getMonth(), 10 ) ];
5365			case "{Y}":
5366				return date.getFullYear();
5367			}
5368		} );
5369	};
5370
5371wb.calendar = {
5372	create: createCalendar
5373};
5374
5375$document.on( navigateEvent, selector, function( event ) {
5376	var calendarObj = event.currentTarget,
5377		$calendar = $( calendarObj ),
5378		year = event.year,
5379		month = event.month,
5380		lib = calendarObj.lib,
5381		maxYear = lib.maxDate.getFullYear(),
5382		maxMonth = lib.maxDate.getMonth(),
5383		minYear = lib.minDate.getFullYear(),
5384		minMonth = lib.minDate.getMonth(),
5385		$prevArrow = $calendar.find( ".cal-month-prev" ),
5386		$nextArrow = $calendar.find( ".cal-month-next" ),
5387		$monthField = $calendar.find( ".cal-month" ),
5388		disabled = "disabled";
5389
5390	if ( year !== undef ) {
5391		lib.year = year;
5392	}
5393
5394	if ( month !== undef ) {
5395		lib.month = month;
5396	}
5397
5398	//Update UI
5399	$calendar.find( ".cal-year" ).val( year );
5400
5401	$monthField.val( month );
5402
5403	$monthField.children( ":" + disabled ).removeAttr( disabled );
5404
5405	if ( year < minYear || ( year === minYear && month <= minMonth ) ) {
5406		$prevArrow.attr( disabled, disabled );
5407	} else {
5408		$prevArrow.removeAttr( disabled );
5409	}
5410
5411	if ( year > maxYear || ( year === maxYear && month >= maxMonth ) ) {
5412		$nextArrow.attr( disabled, disabled );
5413	} else {
5414		$nextArrow.removeAttr( disabled );
5415	}
5416
5417	if ( year === minYear ) {
5418		$monthField.children( ":lt(" + minMonth + ")" ).attr( disabled, disabled );
5419	}
5420
5421	if ( year === maxYear ) {
5422		$monthField.children( ":gt(" + maxMonth + ")" ).attr( disabled, disabled );
5423	}
5424
5425	createDays( event.currentTarget, event.year, event.month );
5426
5427	// Added declaration of current month in aria-live="polite" for screen-readers
5428	if ( !event.initEvent ) {
5429		$calendar.find( ".current-month" ).text( i18nText.monthNames[ month ] + " " + year );
5430	}
5431} );
5432
5433$document.on( "change", selector, function( event ) {
5434	var target = event.target,
5435		calendar = event.currentTarget,
5436		year, month;
5437
5438	switch ( target.className ) {
5439	case "cal-year":
5440		year = parseInt( target.value, 10 );
5441		month = calendar.lib.month;
5442		break;
5443	case "cal-month":
5444		year = calendar.lib.year;
5445		month = parseInt( target.value, 10 );
5446		break;
5447	}
5448
5449	$( calendar ).trigger( {
5450		type: navigateEvent,
5451		year: year,
5452		month: month
5453	} );
5454} );
5455
5456$document.on( "click", ".cal-month-prev, .cal-month-next", function( event ) {
5457	var $calendar = $( event.currentTarget ).closest( selector ),
5458		calendar = $calendar.get( 0 ),
5459		className = event.currentTarget.className,
5460		modifier = className.indexOf( "cal-month-prev" ) !== -1 ? -1 : 1,
5461		date = new Date( calendar.lib.year, calendar.lib.month + modifier, 1 );
5462
5463	$calendar.trigger( {
5464		type: navigateEvent,
5465		year: date.getFullYear(),
5466		month: date.getMonth()
5467	} );
5468	if ( wb.ie11 ) {
5469		$calendar.trigger( "focusin" );
5470	}
5471} );
5472
5473$document.on( "keydown", selector, function( event ) {
5474	var calendar = event.currentTarget,
5475		$days = $( event.currentTarget ).find( ".cal-days" ),
5476		target = event.target,
5477		which = event.which,
5478		lib = calendar.lib,
5479		date = new Date( lib.year, lib.month, 1 ),
5480		minDate = lib.minDate,
5481		maxDate = lib.maxDate,
5482		currentDate = new Date( date ),
5483		navigate = true,
5484		classMatch, isDayLink, lastDay, modifier, day;
5485
5486	if ( !event.altKey && !event.metaKey && which > 32 && which < 41 ) {
5487
5488		classMatch = target.parentNode.className.match( /cal-index-(\d{1,2})/ );
5489		isDayLink = classMatch !== null;
5490
5491		//Key binding for the entire calendar
5492		switch ( which ) {
5493
5494		//page up
5495		case 33:
5496			date.setDate( minDate.getDate() );
5497
5498			//page down
5499			/* falls through */
5500		case 34:
5501			modifier = ( which === 33 ? -1 : 1 );
5502
5503			if ( event.ctrlKey || event.shiftKey || event.altKey ) {
5504				date.setYear( date.getFullYear() + modifier );
5505			} else {
5506				date.setMonth( date.getMonth() + modifier );
5507			}
5508			break;
5509		}
5510
5511		//Key binding for navigating calendar days
5512		if ( isDayLink ) {
5513			day = parseInt( classMatch[ 1 ], 10 );
5514			date.setMonth( date.getMonth() + 1, 0 );
5515			lastDay = date.getDate();
5516			date.setDate( day > lastDay ? lastDay : day );
5517
5518			switch ( which ) {
5519
5520			// end / home
5521			case 35:
5522				date.setDate( lastDay );
5523				break;
5524			case 36:
5525				date.setDate( 1 );
5526				break;
5527
5528			// left / up / right / down arrows
5529			case 37:
5530				date.setDate( day - 1 );
5531				break;
5532			case 38:
5533				date.setDate( day - 7 );
5534				break;
5535			case 39:
5536				date.setDate( day + 1 );
5537				break;
5538			case 40:
5539				date.setDate( day + 7 );
5540				break;
5541			}
5542		}
5543
5544		if ( date < minDate || date > maxDate ) {
5545			if ( which === 35 ) {
5546				date.setDate( maxDate.getDate() );
5547			} else if ( which === 36 ) {
5548				date = minDate;
5549			} else {
5550				navigate = false;
5551			}
5552		}
5553
5554		if ( navigate && ( date.getMonth() !== currentDate.getMonth() || date.getFullYear() !== currentDate.getFullYear() ) ) {
5555			$( calendar ).trigger( {
5556				type: navigateEvent,
5557				year: date.getFullYear(),
5558				month: date.getMonth()
5559			} );
5560		}
5561
5562		if ( isDayLink ) {
5563			$days.find( ".cal-index-" + date.getDate() + " a:first" ).focus();
5564		}
5565
5566		event.preventDefault();
5567		return false;
5568	}
5569
5570} );
5571
5572( function() {
5573
5574	function pad( number ) {
5575		if ( number < 10 ) {
5576			return "0" + number;
5577		}
5578		return number;
5579	}
5580
5581	Date.prototype.toLocalISOString = function() {
5582		var tz = this.getTimezoneOffset();
5583		if ( tz === 0 ) {
5584			return this.toISOString();
5585		}
5586		return this.getFullYear() +
5587			"-" + pad( this.getMonth() + 1 ) +
5588			"-" + pad( this.getDate() ) +
5589			"T" + pad( this.getHours() ) +
5590			":" + pad( this.getMinutes() ) +
5591			":" + pad( this.getSeconds() ) +
5592			"." + ( this.getMilliseconds() / 1000 ).toFixed( 3 ).slice( 2, 5 ) +
5593			( tz < 0 ? "+" : "-" ) +
5594			pad( Math.floor( Math.abs( tz / 60 ) ) ) +
5595			":" + pad( tz % 60 );
5596	};
5597
5598	if ( !Date.prototype.toISOString ) {
5599		Date.prototype.toISOString = function() {
5600			return this.getUTCFullYear() +
5601				"-" + pad( this.getUTCMonth() + 1 ) +
5602				"-" + pad( this.getUTCDate() ) +
5603				"T" + pad( this.getUTCHours() ) +
5604				":" + pad( this.getUTCMinutes() ) +
5605				":" + pad( this.getUTCSeconds() ) +
5606				"." + ( this.getUTCMilliseconds() / 1000 ).toFixed( 3 ).slice( 2, 5 ) +
5607				"Z";
5608		};
5609	}
5610}() );
5611
5612} )( jQuery, DOMPurify, window, document, wb );
5613
5614/**
5615 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
5616 * @title Charts and Graph
5617 * @overview Draw charts from an html simple and complex data table
5618 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
5619 * @author @duboisp
5620 *
5621 */
5622( function( $, window, document, wb ) {
5623"use strict";
5624
5625/**
5626 * Variable and function definitions.
5627 * These are global to the plugin - meaning that they will be initialized once per page,
5628 * not once per instance of plugin on the page. So, this is a good place to define
5629 * variables that are common to all instances of the plugin on a page.
5630 */
5631var componentName = "wb-charts",
5632	selector = "." + componentName,
5633	initEvent = "wb-init" + selector,
5634	tableParsingEvent = "passiveparse.wb-tableparser",
5635	tableParsingCompleteEvent = "parsecomplete.wb-tableparser",
5636	$document = wb.doc,
5637	i18n, i18nText,
5638
5639	/**
5640	 * Main Entry function to create the charts
5641	 * @method createCharts
5642	 * @param {jQuery DOM element} $elm table element use to create the chart
5643	 */
5644	createCharts = function( $elm ) {
5645		var allSeries = [],
5646			chartslabels = [],
5647			dataSeries = [],
5648			nbBarChart = 0,
5649			$caption = $( "caption", $elm ),
5650			captionHtml = $caption.html() || "",
5651			captionText = $caption.text() || "",
5652			valuePoint = 0,
5653			dataCellUnitRegExp = /[^+\-., 0-9]+[^\-+0-9]*/,
5654			lowestFlotDelta, $imgContainer, $placeHolder,
5655			$wetChartContainer, htmlPlaceHolder, figurehtml,
5656			cellValue, datacolgroupfound, dataGroup, header,
5657			i, iLength, j, jLength, parsedData, rIndex, currVectorOptions,
5658			currentRowGroup, reverseTblParsing, dataGroupVector,
5659			currentDataGroupVector, dataCell, previousDataCell, currDataVector,
5660			pieQuaterFlotSeries, optionFlot, optionsCharts, globalOptions,
5661			defaultsOptions = {
5662
5663				// Flot Global Options
5664				flot: {
5665					prefix: "wb-charts-",
5666					defaults: {
5667						colors: wb.drawColours,
5668						canvas: true,
5669						xaxis: {
5670							ticks: { }
5671						}
5672					},
5673					line: { },
5674					area: {
5675						lines: {
5676							show: true,
5677							fill: true
5678						}
5679					},
5680					bar: {
5681						bars: {
5682							show: true,
5683							barWidth: 1,
5684							align: "center"
5685						}
5686					},
5687					pie: {
5688						series: {
5689							pie: {
5690								show: true
5691							}
5692						},
5693						fn: {
5694							"/series/pie/label/formatter": function( label, series ) {
5695								var textlabel;
5696								if ( !optionsCharts.decimal ) {
5697									textlabel = Math.round( series.percent );
5698								} else {
5699									textlabel = Math.round( series.percent * Math.pow( 10, optionsCharts.decimal ) );
5700									textlabel = textlabel / Math.pow( 10, optionsCharts.decimal );
5701								}
5702
5703								if ( optionsCharts.nolegend ) {
5704
5705									// Add the series label
5706									textlabel = label + "<br />" + textlabel;
5707								}
5708								return textlabel + "%";
5709							}
5710						}
5711					},
5712					donut: {
5713						base: "pie",
5714						series: {
5715							pie: {
5716								radius: 1,
5717								label: {
5718									show: true,
5719									radius: 1,
5720									threshold: 0.08
5721								},
5722								tilt: 0.5,
5723								innerRadius: 0.45,
5724								startAngle: 1
5725							}
5726						},
5727						grid: {
5728							hoverable: true
5729						}
5730					},
5731					slicelegend: {
5732						base: "pie",
5733						series: {
5734							pie: {
5735								radius: 1,
5736								label: {
5737									radius: 1,
5738									show: true,
5739									threshold: 0.05
5740								},
5741								combine: {
5742									threshold: 0.05,
5743									color: "#555",
5744									label: i18nText.slicelegend
5745								}
5746							}
5747						},
5748						fn: {
5749							"/series/pie/label/formatter": function( label ) {
5750								return label;
5751							}
5752						}
5753					}
5754				},
5755
5756				// Flot Series Options
5757				series: {
5758					prefix: "wb-charts-",
5759					defaults: { },
5760					line: { },
5761					area: {
5762						lines: {
5763							show: true,
5764							fill: true
5765						}
5766					},
5767					bar: {
5768						bars: {
5769							show: true,
5770							barWidth: 1,
5771							align: "center"
5772						}
5773					},
5774					stacked: {
5775						base: "bar"
5776					}
5777				},
5778
5779				// Wet-boew Charts Options
5780				charts: {
5781					prefix: "wb-charts-",
5782					defaults: {
5783
5784						// [string] Class name added at the figure element container
5785						graphclass: "wb-graph",
5786
5787						// [boolean] Wrap or not the table in a details/summary elements
5788						noencapsulation: false,
5789
5790						// [number] false means the deepest vector will be used for labelling
5791						labelposition: false,
5792
5793						// [number] false means the deepest vector will be used for calculate the reference
5794						referencevalue: false,
5795
5796						// [boolean] false means to move the legend from inside the charts to next to it
5797						legendinline: false,
5798
5799						// [boolean] true means that the legend will be destroyed and the label for pie chart will include the legend
5800						nolegend: false,
5801
5802						// [number] Literal number of displayed decimal for a pie charts
5803						decimal: 0,
5804
5805						// [number] Provide a default width for the charts that will be rendered
5806						width: $elm.width(),
5807
5808						// [number] Provide a default height for the charts that will be rendered
5809						height: $elm.height(),
5810
5811						// [boolean] Flag for defining if the data table should be read in reverse compared to HTML spec
5812						reversettblparsing: false,
5813						fn: {
5814							"/getcellvalue": function( elem ) {
5815
5816								// Get the number from the data cell, #3267
5817								var cellValue = $.trim( elem.dataset.wbChartsValue || $( elem ).text() );
5818								return [
5819									parseFloat( cellValue.replace( /(\d{1,3}(?:(?: |,)\d{3})*)(?:(?:.|,)(\d{1,2}))?$/, function( a, b, c ) {
5820										return b.replace( / |,/g, "" ) + "." + c || "0";
5821									} ), 10 ),
5822									cellValue.match( dataCellUnitRegExp )
5823								];
5824							}
5825						}
5826					},
5827					donut: {
5828						decimal: 1
5829					}
5830				}
5831			};
5832
5833		/**
5834		 * A little function to overwrite and add preset into the default options
5835		 *
5836		 * @method overwriteDefaultsOptions
5837		 * @param {string} scopekey - Key that represent the subject of the setting, [flot, charts, series,...]
5838		 * @param {json object} target - DefaultOptions that will be overwritten
5839		 * @param {json object} object - User defined object for overwriting options
5840		 * @return {json object} - Return the new object
5841		 */
5842		function overwriteDefaultsOptions( scopekey, target, object ) {
5843			var cachedObj, key;
5844
5845			cachedObj = object[ scopekey ];
5846			if ( !cachedObj ) {
5847				return target;
5848			}
5849			for ( key in cachedObj ) {
5850				if ( !Object.prototype.hasOwnProperty.call( cachedObj, key ) ) {
5851					continue;
5852				}
5853				target[ scopekey ][ key ] = cachedObj[ key ];
5854			}
5855			return target;
5856		}
5857
5858		// User defined options
5859		if ( !window.chartsGraphOpts ) {
5860			globalOptions = window[ componentName ];
5861
5862			// Global setting
5863			if ( globalOptions ) {
5864				overwriteDefaultsOptions( "flot", defaultsOptions, globalOptions );
5865				overwriteDefaultsOptions( "series", defaultsOptions, globalOptions );
5866				overwriteDefaultsOptions( "charts", defaultsOptions, globalOptions );
5867			}
5868
5869			// Save the setting here in a case of a second graphic on the same page
5870			window.chartsGraphOpts = defaultsOptions;
5871		}
5872		defaultsOptions = window.chartsGraphOpts;
5873
5874		/**
5875		 * A little function to ease the web editor life
5876		 *
5877		 * Apply preset defined by a set of space-separated tokens from a baseline json object and at the same time extend the result by using the HTML5 data attribute
5878		 *
5879		 * @method applyPreset
5880		 * @param {json object} baseline - Base line json object that includes predefined and userdefined preset
5881		 * @param {jQuery} $elem - Element on which the class attribute will be taken for a set of space-separated tokens
5882		 * @param {string} attribute - Name of the HTML5 data attribute for extending the object at the end
5883		 * @return {json object} - Return a new object build from the ```baseline``` or ```baseline.default``` object with the preset applied.
5884		 */
5885		function applyPreset( baseline, $elem, attribute ) {
5886
5887			var config = $.extend( true, {}, baseline.defaults || baseline ),
5888				fn = $.extend( true, {}, baseline.defaults && baseline.defaults.fn || { } ),
5889				tokens = $elem.attr( "class" ) || "",
5890				tblTokens, i, iLength, token, tokenLength,
5891
5892				// Prefix used in front of the token
5893				prefix, prefixLength,
5894				preset, key, tblFn, localKey, currObj;
5895
5896			if ( tokens.length ) {
5897
5898				prefix = ( baseline.prefix || "" );
5899				prefixLength = prefix.length;
5900
5901				// split the set of space-separated tokens
5902				tblTokens = tokens.split( " " );
5903
5904				for ( i = 0, iLength = tblTokens.length; i !== iLength; i += 1 ) {
5905
5906					// Get the current token
5907					token = tblTokens[ i ];
5908					tokenLength = token.length;
5909
5910					// Remove the token is used
5911					if ( tokenLength <= prefixLength || token.slice( 0, prefixLength ) !== prefix ) {
5912						continue;
5913					}
5914					token = token.slice( prefixLength, tokenLength );
5915
5916					preset = baseline[ token ];
5917
5918					// Apply the preset
5919					if ( preset ) {
5920						if ( preset.base ) {
5921
5922							// Like setting herited from a parent config
5923							config = $.extend( true, config, baseline[ preset.base ] );
5924							fn = $.extend( true, fn, baseline[ preset.base ].fn || { } );
5925						}
5926						config = $.extend( true, config, preset );
5927						fn = $.extend( true, fn, preset.fn || { } );
5928					}
5929				}
5930			}
5931
5932			// Extend the config from the element @data attribute
5933			config = $.extend( true, config, wb.getData( $elem, attribute ) );
5934
5935			// Merge and override the function.
5936			for ( key in fn ) {
5937				if ( !Object.prototype.hasOwnProperty.call( fn, key ) ) {
5938					continue;
5939				}
5940				tblFn = key.split( "/" );
5941				currObj = config;
5942				for ( i = 0, iLength = tblFn.length - 1; i !== iLength; i += 1 ) {
5943					localKey = tblFn.shift();
5944					if ( localKey === "" ) {
5945						continue;
5946					}
5947					if ( !currObj[ localKey ] ) {
5948						currObj[ localKey ] = { };
5949					}
5950					currObj = currObj[ localKey ];
5951				}
5952				localKey = tblFn.shift();
5953				currObj[ localKey ] = fn[ key ];
5954			}
5955			return config;
5956		}
5957
5958		// Apply any preset
5959		optionFlot = applyPreset( defaultsOptions.flot, $elm, "flot" );
5960
5961		// Apply any preset
5962		optionsCharts = applyPreset( defaultsOptions.charts, $elm, componentName );
5963
5964		// Fix default width and height in case the table is hidden or too small.
5965		optionsCharts.width = ( optionsCharts.width && optionsCharts.width > 250 ? optionsCharts.width : 250 );
5966		optionsCharts.height = ( optionsCharts.height && optionsCharts.height > 250 ? optionsCharts.height : 250 );
5967
5968		/**
5969		 * @method getColumnGroupHeaderCalculateSteps
5970		 * @param {object} colGroupHead - Column Group Header Object from the table parser
5971		 * @param {number} referenceValuePosition - Vector position use as reference for defining the steps, zero based position
5972		 */
5973		function getColumnGroupHeaderCalculateSteps( colGroupHead, referenceValuePosition ) {
5974
5975			// Get the appropriate ticks
5976			var headerCell, i, iLen,
5977				calcStep = 1,
5978				colRefValue, colCurent;
5979
5980			if ( !colGroupHead ) {
5981
5982				// There is an error. Possibly the series are missing a header.
5983				return;
5984			}
5985
5986			colRefValue = colGroupHead.col[ referenceValuePosition ];
5987			colCurent = colGroupHead.col[ 0 ];
5988
5989			for ( i = 0, iLen = colRefValue.cell.length; i !== iLen; i += 1 ) {
5990
5991				headerCell = colRefValue.cell[ i ];
5992
5993				if ( i === 0 || ( i > 0 && colCurent.cell[ i - 1 ].uid !== headerCell.uid ) ) {
5994
5995					if ( headerCell.rowgroup && headerCell.rowgroup.type === 3 ) {
5996
5997						// We only process the first column data group
5998						break;
5999					}
6000
6001					if ( headerCell.type === 1 || headerCell.type === 7 ) {
6002						if ( headerCell.child.length !== 0 ) {
6003							calcStep = calcStep * groupHeaderCalculateStepsRecursive( headerCell, 1 );
6004						}
6005					}
6006				}
6007			}
6008
6009			return calcStep;
6010		}
6011
6012		/**
6013		 * @method getRowGroupHeaderCalculateSteps
6014		 * @param {object} rowGroupHead - Row Group Header Object from the table parser
6015		 * @param {number} referenceValuePosition - Vector position use as reference for defining the steps, zero based position
6016		 * @param {number} dataColgroupStart - Column position where the column data group start
6017		 */
6018		function getRowGroupHeaderCalculateSteps( rowGroupHead, referenceValuePosition, dataColgroupStart ) {
6019
6020			// Find the range of the first data colgroup
6021			var headerCell, i, iLen,
6022				calcStep = 1,
6023				rowRefValueCells = rowGroupHead[ referenceValuePosition ].elem.cells;
6024
6025			for ( i = 0, iLen = rowRefValueCells.length; i !== iLen; i += 1 ) {
6026
6027				headerCell = $( rowRefValueCells[ i ] ).data().tblparser;
6028
6029				if ( headerCell.colgroup && headerCell.colgroup.type === 3 ) {
6030
6031					// We only process the first column data group
6032					break;
6033				}
6034
6035				if ( headerCell.colpos >= dataColgroupStart && ( headerCell.type === 1 || headerCell.type === 7 ) ) {
6036					if ( headerCell.child.length !== 0 ) {
6037						calcStep = calcStep * headerCell.child.length * groupHeaderCalculateStepsRecursive( headerCell, 1 );
6038					}
6039				}
6040			}
6041
6042			return calcStep;
6043		}
6044
6045		/**
6046		 * @method groupHeaderCalculateStepsRecursive
6047		 * @param {object} headerCell - Header cell object from the table parser
6048		 * @param {number} refValue - Reference Value (Dénominateur) of headerCell
6049		 */
6050		function groupHeaderCalculateStepsRecursive( headerCell, refValue ) {
6051			var childLength = headerCell.child.length,
6052				calcStep = 1,
6053				kIndex, subRefValue, headerCellChild;
6054
6055			if ( childLength === 0 ) {
6056				return calcStep;
6057			}
6058
6059			subRefValue = childLength * refValue;
6060
6061			calcStep = calcStep * subRefValue;
6062
6063			for ( kIndex = 0; kIndex !== childLength; kIndex += 1 ) {
6064				headerCellChild = headerCell.child[ kIndex ];
6065				if ( headerCellChild.child.length !== 0 ) {
6066					calcStep = calcStep * groupHeaderCalculateStepsRecursive( headerCellChild, subRefValue );
6067				}
6068			}
6069			return calcStep;
6070		}
6071
6072		/**
6073		 * Set the inner step value (divisor) of an header cell and for his child
6074		 *
6075		 * @method setInnerStepValues
6076		 * @param {object} vectorHead - Group Header Object from the table parser
6077		 * @param {number} headerLevel - Hiearchical Level of heading
6078		 * @param {number} stepsValue - Step Value for the reference value vector
6079		 * @param {number} referenceValue - Reference Value Vector ID
6080		 * @param {number} dataColgroupStart - Column position where the column data group start
6081		 *
6082		 */
6083		function setInnerStepValues( vectorHead, headerLevel, stepsValue, referenceValue, dataColgroupStart ) {
6084			var i, iLength,
6085				headerCell,
6086				cumulativeValue = 0;
6087
6088			for ( i = 0, iLength = vectorHead.cell.length; i !== iLength; i += 1 ) {
6089				headerCell = vectorHead.cell[ i ];
6090				if ( i !== 0 && headerCell.uid === vectorHead.cell[ i - 1 ].uid || ( dataColgroupStart && headerCell.colpos < dataColgroupStart ) ) {
6091					continue;
6092				}
6093
6094				// Only process the first data group
6095				if ( !reverseTblParsing ) {
6096					if ( headerCell.colgroup && headerCell.colgroup.type === 3 ) {
6097						break;
6098					}
6099				} else {
6100					if ( headerCell.rowgroup && headerCell.rowgroup.type === 3 ) {
6101						break;
6102					}
6103				}
6104				if ( headerCell.child > 0 && headerLevel < referenceValue ) {
6105					headerCell.flotDelta = stepsValue * headerCell.child.length;
6106				} else {
6107					headerCell.flotDelta = stepsValue;
6108				}
6109				if ( headerCell.type === 1 || headerCell.type === 7  ) {
6110
6111					if ( !lowestFlotDelta || headerCell.flotDelta < lowestFlotDelta ) {
6112						lowestFlotDelta = headerCell.flotDelta;
6113					}
6114					headerCell.flotValue = cumulativeValue;
6115
6116					cumulativeValue = cumulativeValue + stepsValue;
6117
6118					if ( headerCell.child.length > 0 ) {
6119						setInnerStepValuesChildRecursive( headerCell, headerLevel, stepsValue, referenceValue );
6120					}
6121				}
6122			}
6123		}
6124
6125		/**
6126		 * Recursize - Set the inner step value (divisor) of an sub header cell
6127		 *
6128		 * @method setInnerStepValuesChildRecursive
6129		 * @param {object} headerCell - Header cell object from the table parser
6130		 * @param {number} headerLevel - Hiearchical Level of heading
6131		 * @param {number} stepsValue - Specific Step Value applied for current headerCell
6132		 * @param {number} referenceValue - Reference Value Vector ID
6133		 */
6134		function setInnerStepValuesChildRecursive( headerCell, headerLevel, stepsValue, referenceValue ) {
6135			var cumulativeValue = 0,
6136
6137				// Step Values for childs header in headerCell
6138				flotDelta,
6139				i, iLength,	currentHeaderCellChild;
6140
6141			headerLevel += 1;
6142			cumulativeValue = headerCell.flotValue;
6143			flotDelta = stepsValue / headerCell.child.length;
6144
6145			// Use to calculate the largest width for a bar in a bar chart
6146			if ( !lowestFlotDelta || flotDelta < lowestFlotDelta ) {
6147				lowestFlotDelta = flotDelta;
6148			}
6149
6150			for ( i = 0, iLength = headerCell.child.length; i !== iLength; i += 1 ) {
6151				currentHeaderCellChild = headerCell.child[ i ];
6152				if ( headerLevel < referenceValue ) {
6153					currentHeaderCellChild.flotDelta = flotDelta * currentHeaderCellChild.child.length;
6154				} else {
6155					currentHeaderCellChild.flotDelta = flotDelta;
6156				}
6157				currentHeaderCellChild.flotValue = cumulativeValue;
6158				if ( currentHeaderCellChild.child.length > 0 ) {
6159					setInnerStepValuesChildRecursive( currentHeaderCellChild, headerLevel, flotDelta, referenceValue );
6160				}
6161				cumulativeValue = cumulativeValue + flotDelta;
6162			}
6163		}
6164
6165		/**
6166		 * Set the header cell step value (flotDelta) for vector that regroup more than one reference
6167		 *
6168		 * @method setUpperStepValues
6169		 * @param {object} vectorHead - Group Header Object from the table parser
6170		 * @param {number} referenceValue - Reference Value Vector ID
6171		 */
6172		function setUpperStepValues( vectorHead, referenceValue ) {
6173			var i, k, m, kLen, mLen,
6174				cumulativeValue,
6175				currentCell,
6176				currentCellChild,
6177				currentVectorHead;
6178
6179			// Calculate upper-step for cells that are
6180			// less precise than the reference value vector
6181			for ( i = referenceValue - 1; i !== -1; i -= 1 ) {
6182				currentVectorHead = vectorHead[ i ];
6183
6184				for ( k = 0, kLen = currentVectorHead.cell.length; k !== kLen; k += 1 ) {
6185					currentCell = currentVectorHead.cell[ k ];
6186
6187					if ( currentCell.flotDelta || k > 0 &&
6188						currentCell.uid === currentVectorHead.cell[ k - 1 ].uid ) {
6189
6190						continue;
6191					}
6192
6193					if ( !( currentCell.type === 1 || currentCell.type === 7 ) ) {
6194						continue;
6195					}
6196
6197					cumulativeValue = 0;
6198					for ( m = 0, mLen = currentCell.child.length; m !== mLen; m += 1 ) {
6199						currentCellChild = currentCell.child[ m ];
6200
6201						cumulativeValue = currentCellChild.flotDelta;
6202						if ( !currentCell.flotValue ) {
6203							currentCell.flotValue = currentCellChild.flotValue;
6204						}
6205					}
6206					currentCell.flotDelta = cumulativeValue;
6207				}
6208			}
6209		}
6210
6211		/**
6212		 * Get labels for a specific vector
6213		 *
6214		 * @method getLabels
6215		 * @param {object} labelVector - Vector Header Object from the table parser
6216		 * @param {number} dataColgroupStart - Column position where the column data group start
6217		 */
6218		function getLabels( labelVector, dataColgroupStart ) {
6219			var labels = [],
6220				i, iLen, currentCell;
6221
6222			for ( i = 0, iLen = labelVector.cell.length; i !== iLen; i += 1 ) {
6223				currentCell = labelVector.cell[ i ];
6224
6225				if ( ( i !== 0 && currentCell.uid === labelVector.cell[ i - 1 ].uid ) ||
6226						( !( currentCell.type === 1 || currentCell.type === 7 ) ) ||
6227						( dataColgroupStart && currentCell.colpos < dataColgroupStart ) ) {
6228					continue;
6229				}
6230
6231				labels.push( [ currentCell.flotValue, $( currentCell.elem ).text() ] );
6232			}
6233			return labels;
6234		}
6235
6236		/**
6237		 * Get the vector that would be used for labelling x-axis
6238		 *
6239		 * @method getlabelsVectorPosition
6240		 * @param {object[]} arrVectorHeaders - Collection of vector headers
6241		 */
6242		function getlabelsVectorPosition( arrVectorHeaders ) {
6243			var labelPosition = optionsCharts.labelposition;
6244			return ( !labelPosition || ( labelPosition && labelPosition > arrVectorHeaders.length ) ?
6245				parsedData.theadRowStack.length : labelPosition ) - 1;
6246		}
6247
6248		/**
6249		 * Get the vertical label and set the appropriate header cell x-axis Value
6250		 *
6251		 * @method verticalLabels
6252		 * @param {object} parsedData - Generic object generated by the table parser
6253		 */
6254		function verticalLabels( parsedData ) {
6255
6256			// Get the appropriate ticks
6257			var headerlevel = 0,
6258				labelsVectorPosition, stepsValue, columnReferenceValue;
6259
6260			if ( !reverseTblParsing || ( reverseTblParsing && optionsCharts.referencevalue === false ) ) {
6261				columnReferenceValue = parsedData.colgrouphead.col.length;
6262			} else {
6263				columnReferenceValue = optionsCharts.referencevalue;
6264			}
6265
6266			columnReferenceValue = columnReferenceValue - 1;
6267
6268			stepsValue = getColumnGroupHeaderCalculateSteps( parsedData.colgrouphead, columnReferenceValue );
6269
6270			if ( !reverseTblParsing ) {
6271				labelsVectorPosition = parsedData.colgrouphead.col.length - 1;
6272			} else {
6273				labelsVectorPosition = getlabelsVectorPosition( parsedData.colgrouphead.col );
6274			}
6275
6276			headerlevel = columnReferenceValue;
6277
6278			// Calculate inner-step for cells that are more precise than the reference value vector
6279			setInnerStepValues( parsedData.colgrouphead.col[ columnReferenceValue ], headerlevel, stepsValue, columnReferenceValue );
6280
6281			// Calculate upper-step for cells that are less precise than the reference value vector
6282			setUpperStepValues( parsedData.colgrouphead.col, columnReferenceValue );
6283
6284			// Get the labelling
6285			return getLabels( parsedData.colgrouphead.col[ labelsVectorPosition ] );
6286		}
6287
6288		/**
6289		 * Get the horizontal label and set the appropriate header cell x-axis Value
6290		 *
6291		 * @method horizontalLabels
6292		 * @param {object} parsedData - Generic object generated by the table parser
6293		 */
6294		function horizontalLabels( parsedData ) {
6295
6296			// Find the range of the first data colgroup
6297			var dataColgroupStart = -1,
6298				headerlevel = 0,
6299				theadRowStack = parsedData.theadRowStack,
6300				i, iLength, labelsVectorPosition,
6301				stepsValue, rowReferenceValue;
6302
6303			if ( !theadRowStack ) {
6304				return;
6305			}
6306
6307			for ( i = 0, iLength = parsedData.colgroup.length; i !== iLength; i += 1 ) {
6308				if ( parsedData.colgroup[ i ].type === 2 ) {
6309					dataColgroupStart = parsedData.colgroup[ i ].start;
6310					break;
6311				}
6312			}
6313
6314			if ( ( !reverseTblParsing && optionsCharts.referencevalue === false ) || reverseTblParsing ) {
6315				rowReferenceValue = theadRowStack.length;
6316			} else {
6317				rowReferenceValue = optionsCharts.referencevalue;
6318			}
6319
6320			rowReferenceValue = rowReferenceValue - 1;
6321
6322			stepsValue = getRowGroupHeaderCalculateSteps( theadRowStack, rowReferenceValue, dataColgroupStart );
6323
6324			if ( !reverseTblParsing ) {
6325				labelsVectorPosition = getlabelsVectorPosition( theadRowStack );
6326			} else {
6327				labelsVectorPosition = theadRowStack.length - 1;
6328			}
6329
6330			headerlevel = rowReferenceValue;
6331
6332			// Calculate inner-step for cells that are more precise than the reference value vector
6333			setInnerStepValues( theadRowStack[ rowReferenceValue ], headerlevel, stepsValue, rowReferenceValue, dataColgroupStart );
6334
6335			// Calculate upper-step for cells that are less precise than the reference value vector
6336			setUpperStepValues( theadRowStack, rowReferenceValue );
6337
6338			// Get the labelling
6339			return getLabels( theadRowStack[ labelsVectorPosition ], dataColgroupStart );
6340
6341		}
6342
6343		/**
6344		 * Wrap the table into a smart details/summary element
6345		 *
6346		 * @method wrapTableIntoDetails
6347		 */
6348		function wrapTableIntoDetails() {
6349			var $summary;
6350
6351			if ( !captionHtml.length ) {
6352				return;
6353			}
6354
6355			$summary = $( "<summary>" + captionHtml + i18nText.tableMention + "</summary>" );
6356			$elm
6357				.wrap( "<details></details>" )
6358				.before( $summary );
6359
6360			$summary.trigger( "wb-init.wb-details" );
6361		}
6362
6363		function createContainer( withDimension ) {
6364			$elm
6365				.wrap( "<figure class='" + optionsCharts.graphclass + "'/>" )
6366				.before(
6367
6368					// Copy to the inner table caption
6369					( captionHtml.length ? "<figcaption>" + captionHtml + "</figcaption>" : "" ) +
6370
6371					// Image Container
6372					"<div role='img' aria-label='" + captionText + i18nText.tableFollowing + "'" +
6373
6374					// Add Dimension
6375					( withDimension ? "style='height:" + optionsCharts.height +
6376					"px; width:" + optionsCharts.width + "px'" : "" ) + "></div>"
6377				);
6378
6379			return $( "div:eq(0)", $elm.parent() );
6380		}
6381
6382		// Retrieve the parsed data
6383		parsedData = $elm.data().tblparser;
6384
6385		// Reverse table parsing
6386		reverseTblParsing = optionsCharts.reversettblparsing;
6387
6388		// first data row group
6389		currentRowGroup = parsedData.lstrowgroup[ 0 ];
6390
6391		if ( optionFlot.series && optionFlot.series.pie ) {
6392
6393			// WET Charts placeholder
6394			$wetChartContainer = createContainer( false );
6395
6396			// Flot pie chart placeholder
6397			htmlPlaceHolder = "<div style='height:" + optionsCharts.height +
6398				"px; width:" + optionsCharts.width + "px'></div>";
6399
6400			if ( !reverseTblParsing ) {
6401
6402				// If normal parsing
6403				dataGroup = parsedData.colgroup[ 0 ].type === 1 ?
6404					parsedData.colgroup[ 1 ] :
6405					parsedData.colgroup[ 0 ];
6406
6407				rIndex = currentRowGroup.row.length - 1;
6408			} else {
6409
6410				// If reverse parsing
6411				dataGroup = currentRowGroup;
6412				rIndex = ( parsedData.colgroup[ 0 ].type === 1 ?
6413					parsedData.colgroup[ 1 ].col.length :
6414					parsedData.colgroup[ 0 ].col.length ) - 1;
6415			}
6416
6417			for ( rIndex; rIndex >= 0; rIndex -= 1 ) {
6418
6419				dataGroupVector = !reverseTblParsing ? dataGroup.col : dataGroup.row;
6420
6421				// For each row or column
6422				for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) {
6423					dataSeries = [];
6424					valuePoint = 0;
6425					currentDataGroupVector = dataGroupVector[ i ];
6426
6427					// For each cells
6428					for ( j = 0, jLength = currentDataGroupVector.cell.length; j !== jLength; j += 1 ) {
6429
6430						dataCell = currentDataGroupVector.cell[ j ];
6431
6432						// Skip the column if
6433						if ( reverseTblParsing && dataCell.col.type === 1 ) {
6434							continue;
6435						}
6436
6437						previousDataCell = undefined;
6438						if ( j !== 0 ) {
6439							previousDataCell = currentDataGroupVector.cell[ j - 1 ];
6440						}
6441
6442						// Verify if the selected cell still in the scope of a data group in his another axes (eg. row/col)
6443						// Verify if we are still in the same datagroup as the previous data cell
6444						if ( ( !reverseTblParsing && ( dataCell.row.type !== 2  || ( previousDataCell &&
6445								previousDataCell.rowgroup.uid !== dataCell.rowgroup.uid ) ) ) ||
6446								( reverseTblParsing && ( dataCell.col.type !== 2 ) || ( previousDataCell &&
6447								previousDataCell.col.type !== 1 &&
6448								previousDataCell.col.groupstruct.uid !== dataCell.col.groupstruct.uid ) ) ) {
6449							break;
6450						}
6451
6452						// Gets the value
6453						header = !reverseTblParsing ? dataCell.row.header : dataCell.col.header;
6454
6455						cellValue = optionsCharts.getcellvalue( !reverseTblParsing ?
6456							currentDataGroupVector.cell[ rIndex ].elem :
6457							currentDataGroupVector.datacell[ rIndex ].elem );
6458
6459						dataSeries.push(
6460							[
6461								valuePoint,
6462								typeof cellValue === "object" ?
6463									cellValue[ 0 ] :
6464									cellValue
6465							]
6466						);
6467
6468						valuePoint += header[ header.length - 1 ].flotDelta;
6469
6470						break;
6471					}
6472
6473					pieQuaterFlotSeries = { };
6474
6475					// Get the setting from the associative cell header
6476					dataCell = !reverseTblParsing ?
6477						currentDataGroupVector.cell[ rIndex ] :
6478						currentDataGroupVector.datacell[ rIndex ];
6479					header = !reverseTblParsing ?
6480						dataCell.col.header :
6481						dataCell.row.header;
6482					header = header[ header.length - 1 ];
6483
6484					// Apply any preset
6485					pieQuaterFlotSeries = applyPreset( defaultsOptions.series, $( header.elem ), "flot" );
6486
6487					// Set the data issue from the table
6488					pieQuaterFlotSeries.data = dataSeries;
6489					pieQuaterFlotSeries.label = ( !reverseTblParsing ?
6490						$( currentDataGroupVector.dataheader[ currentDataGroupVector.dataheader.length - 1 ].elem ).text() :
6491						$( currentDataGroupVector.header[ currentDataGroupVector.header.length - 1 ].elem ).text() );
6492
6493					// Add the series
6494					allSeries.push( pieQuaterFlotSeries );
6495				}
6496
6497				// Create a sub Figure or use the main one
6498				if ( currentRowGroup.row.length === 1 &&
6499					( currentRowGroup.row[ 0 ].header[ 0 ].elem.innerHTML === captionHtml ||
6500					currentRowGroup.row[ 0 ].header.length === 0 ) ) {
6501
6502					$placeHolder = $wetChartContainer;
6503					$placeHolder.css( {
6504						height: optionsCharts.height,
6505						width: optionsCharts.width
6506					} );
6507
6508				} else {
6509
6510					header = !reverseTblParsing ? dataCell.row.header : dataCell.col.header;
6511
6512					figurehtml = "<figure><figcaption>" +
6513						header[ header.length - 1 ].elem.innerHTML +
6514						"</figcaption>" + htmlPlaceHolder + "</figure>";
6515
6516					$wetChartContainer.append( $( figurehtml ) );
6517
6518					$placeHolder = $( "div:last()", $wetChartContainer );
6519				}
6520
6521				// Create the graphic
6522				$.plot( $placeHolder, allSeries, optionFlot );
6523
6524				if ( !optionsCharts.legendinline ) {
6525
6526					// Move the legend under the graphic
6527					$( ".legend", $placeHolder ).appendTo( $wetChartContainer );
6528				}
6529
6530				allSeries = [];
6531			}
6532
6533			if ( optionsCharts.nolegend ) {
6534
6535				// Remove the legend
6536				$( ".legend", $wetChartContainer ).remove();
6537			}
6538			if ( !optionsCharts.legendinline ) {
6539
6540				// Fix the legend that appear under the graphic
6541				$( ".legend > div", $wetChartContainer ).remove();
6542				$( ".legend > table", $wetChartContainer ).removeAttr( "style" ).addClass( "font-small" );
6543				$( ".legend", $placeHolder ).appendTo( $imgContainer );
6544			}
6545
6546			// Remove any "pieLabel" ids set by the flotPie.js plugin at line #457
6547			$( ".pieLabel" ).removeAttr( "id" );
6548
6549			if ( !optionsCharts.noencapsulation ) {
6550				wrapTableIntoDetails();
6551			}
6552
6553			return;
6554		}
6555
6556		if ( !reverseTblParsing ) {
6557
6558			// If normal parsing
6559			dataGroup = currentRowGroup;
6560			rIndex = ( parsedData.colgroup[ 0 ].type === 1 ?
6561				parsedData.colgroup[ 1 ].col.length :
6562				parsedData.colgroup[ 0 ].col.length ) - 1;
6563			chartslabels = horizontalLabels( parsedData );
6564		} else {
6565
6566			// If reverse parsing
6567			dataGroup = parsedData.colgroup[ 0 ].type === 1 ?
6568				parsedData.colgroup[ 1 ] :
6569				parsedData.colgroup[ 0 ];
6570			rIndex = currentRowGroup.row.length - 1;
6571			chartslabels = verticalLabels( parsedData );
6572		}
6573
6574		// Add the labels at the Flot options
6575		optionFlot.xaxis.ticks = chartslabels;
6576
6577		dataGroupVector = !reverseTblParsing ? dataGroup.row : dataGroup.col;
6578
6579		// Count the number of bar charts,
6580		for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) {
6581			currentDataGroupVector = dataGroupVector[ i ];
6582			currDataVector = currentDataGroupVector.header[ currentDataGroupVector.header.length - 1 ];
6583
6584			// Apply any preset
6585			currVectorOptions = applyPreset( defaultsOptions.series, $( currDataVector.elem ), "flot" );
6586
6587			if ( currVectorOptions.bars || ( optionFlot.bars && !currVectorOptions.lines ) ) {
6588
6589				// Count number of bars, this number is use to calculate the bar width.
6590				nbBarChart += 1;
6591
6592				// Set a default setting specially for bar charts
6593				if ( !currVectorOptions.bars ) {
6594					currVectorOptions.bars = { show: true, barWidth: 0.9 };
6595				}
6596
6597				// Set a default order for orderBars flot plugin
6598				if ( !currVectorOptions.bars.order ) {
6599					currVectorOptions.bars.order = nbBarChart;
6600				}
6601			}
6602
6603			// cache the compiled setting
6604			currDataVector.chartOption = currVectorOptions;
6605		}
6606
6607		// First rowgroup assume is a data row group.
6608		// For all row....
6609		for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) {
6610			dataSeries = [];
6611			datacolgroupfound = 0;
6612			valuePoint = 0;
6613			currDataVector = dataGroupVector[ i ];
6614
6615			currVectorOptions = currDataVector.header[ currDataVector.header.length - 1 ].chartOption;
6616
6617			// For each cells
6618			for ( j = 0, jLength = currDataVector.cell.length; j !== jLength; j += 1 ) {
6619
6620				dataCell = currDataVector.cell[ j ];
6621
6622				if ( datacolgroupfound > 1 && dataCell.col.groupstruct.type !== 2 ) {
6623					break;
6624				}
6625
6626				if ( ( !reverseTblParsing && dataCell.col.groupstruct.type === 2 ) ||
6627						( reverseTblParsing && dataCell.row.rowgroup.type === 2 ) ) {
6628
6629					// Gets the value
6630					header = !reverseTblParsing ? dataCell.col.header : dataCell.row.header;
6631
6632					cellValue = optionsCharts.getcellvalue( dataCell.elem );
6633
6634					// Add the data point
6635					dataSeries.push(
6636						[
6637							valuePoint,
6638							typeof cellValue === "object" ?
6639								cellValue[ 0 ] :
6640								cellValue
6641						]
6642					);
6643					valuePoint += header[ header.length - 1 ].flotDelta;
6644					datacolgroupfound += 1;
6645				}
6646			}
6647
6648			currVectorOptions.data = dataSeries;
6649			currVectorOptions.label = $( currDataVector.header[ currDataVector.header.length - 1 ].elem ).text();
6650
6651			if ( currVectorOptions.bars ) {
6652
6653				// Adjust the bars width
6654				currVectorOptions.bars.barWidth = currVectorOptions.bars.barWidth * ( 1 / nbBarChart );
6655			}
6656
6657			allSeries.push( currVectorOptions );
6658
6659		}
6660
6661		if ( optionFlot.bars ) {
6662
6663			// Adjust the bars width
6664			optionFlot.bars.barWidth = optionFlot.bars.barWidth * ( 1 / nbBarChart );
6665		}
6666
6667		// WET Charts Placeholder
6668		$placeHolder = createContainer( true );
6669
6670		// Maximum width
6671		$placeHolder.css( "width", "100%" );
6672
6673		// Create the graphic
6674		$.plot( $placeHolder, allSeries, optionFlot );
6675
6676		if ( !optionsCharts.legendinline ) {
6677
6678			// Move the legend under the graphic
6679			$( ".legend > div", $placeHolder ).remove();
6680			$( ".legend > table", $placeHolder ).removeAttr( "style" ).addClass( "font-small" );
6681			$placeHolder.css( "height", "auto" );
6682		}
6683		if ( optionsCharts.nolegend ) {
6684
6685			// Remove the legend
6686			$( ".legend", $placeHolder ).remove();
6687		}
6688
6689		if ( !optionsCharts.noencapsulation ) {
6690			wrapTableIntoDetails();
6691		}
6692
6693		$( "canvas:eq(1)", $placeHolder ).css( "position", "static" );
6694		$( "canvas:eq(0)", $placeHolder ).css( "width", "100%" );
6695
6696		$elm.trigger( "wb-updated" + selector );
6697	},
6698
6699	/**
6700	 * @method init
6701	 * @param {jQuery Event} event Event that triggered this handler
6702	 */
6703	init = function( event ) {
6704
6705		// Start initialization
6706		// returns DOM object = proceed with init
6707		// returns undefined = do not proceed with init (e.g., already initialized)
6708		var elm = wb.init( event, componentName, selector ),
6709			settings = window[ componentName ],
6710			elmId, modeJS, deps;
6711
6712		if ( elm ) {
6713			elmId = elm.id;
6714			modeJS = wb.getMode() + ".js";
6715			deps = [
6716				"site!deps/jquery.flot" + modeJS,
6717				"site!deps/jquery.flot.pie" + modeJS,
6718				"site!deps/jquery.flot.canvas" + modeJS,
6719				"site!deps/jquery.flot.orderBars" + modeJS,
6720				"site!deps/tableparser" + modeJS
6721			];
6722
6723			//TODO: Revist this in the new plugin structure
6724			if ( settings && settings.plugins ) {
6725				deps = deps.concat( settings.plugins );
6726			}
6727
6728			// Only initialize the i18nText once
6729			if ( !i18nText ) {
6730				i18n = wb.i18n;
6731				i18nText = {
6732					tableMention: i18n( "hyphen" ) + i18n( "tbl-txt" ),
6733					tableFollowing: i18n( "hyphen" ) + i18n( "tbl-dtls" ),
6734					slicelegend: i18n( "chrt-cmbslc" )
6735				};
6736			}
6737
6738			// Load the required dependencies
6739			Modernizr.load( {
6740
6741				// For loading multiple dependencies
6742				load: deps,
6743				complete: function() {
6744					var $elm = $( "#" + elmId );
6745
6746					// Let's parse the table
6747					$elm.trigger( tableParsingEvent );
6748
6749					// Identify that initialization has completed
6750					wb.ready( $elm, componentName );
6751				}
6752			} );
6753		}
6754	};
6755
6756// Bind the init event of the plugin
6757$document.on( "timerpoke.wb " + initEvent + " " + tableParsingCompleteEvent, selector, function( event ) {
6758	var eventType = event.type,
6759		elm = event.target;
6760
6761	switch ( eventType ) {
6762
6763	/*
6764	 * Init
6765	 */
6766	case "timerpoke":
6767	case "wb-init":
6768		init( event );
6769		break;
6770
6771	/*
6772	 * Data table parsed
6773	 */
6774	case "parsecomplete":
6775		if ( event.currentTarget === elm ) {
6776			createCharts( $( elm ) );
6777		}
6778		break;
6779	}
6780
6781	/*
6782	 * Since we are working with events we want to ensure that we are being passive about our control,
6783	 * so returning true allows for events to always continue
6784	 */
6785	return true;
6786} );
6787
6788// Add the timer poke to initialize the plugin
6789wb.add( selector );
6790
6791} )( jQuery, window, document, wb );
6792
6793/**
6794 * @title WET-BOEW Collapsible alerts plugin
6795 * @overview Collapsible alerts (details/summary)
6796 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
6797 * @author WET community
6798 */
6799( function( $, window, wb ) {
6800"use strict";
6801
6802/*
6803 * Variable and function definitions.
6804 * These are global to the event - meaning that they will be initialized once per page,
6805 * not once per instance of event on the page.
6806 */
6807var componentName = "wb-collapsible",
6808	selector = "details.alert",
6809	initEvent = "wb-init." + componentName,
6810	$document = wb.doc,
6811	key,
6812
6813	/**
6814	 * @method init
6815	 * @param {jQuery Event} event Event that triggered the function call
6816	 */
6817	init = function( event ) {
6818
6819		// Start initialization
6820		// returns DOM object = proceed with init
6821		// returns undefined = do not proceed with init (e.g., already initialized)
6822		var details = wb.init( event, componentName, selector ),
6823			$details;
6824
6825		if ( details ) {
6826			$details = $( details );
6827
6828			key = "alert-collapsible-state-" + details.getAttribute( "id" );
6829
6830			try {
6831				if ( localStorage.getItem( key ) ) {
6832
6833					// Set open/closed state for existing localStorage keys
6834					if ( localStorage.getItem( key ) === "open" ) {
6835						details.setAttribute( "open", "open" );
6836						details.className += " open";
6837					} else if ( localStorage.getItem( key ) === "closed" ) {
6838						details.removeAttribute( "open" );
6839						details.className = details.className.replace( " open", "" );
6840					}
6841
6842				} else {
6843
6844					// Set new localStorage values
6845					if ( details.hasAttribute( "open" ) ) {
6846						localStorage.setItem( key, "open" );
6847					} else {
6848						localStorage.setItem( key, "closed" );
6849					}
6850
6851				}
6852			} catch ( e ) {
6853				/* swallow error */}
6854
6855			// Identify that initialization has completed
6856			wb.ready( $details, componentName );
6857		}
6858	};
6859
6860// Bind the init event of the plugin
6861$document.on( "timerpoke.wb " + initEvent, selector, init );
6862
6863// Do not bind events if details polyfill is active
6864if ( Modernizr.details ) {
6865
6866	// Bind the the event handlers of the plugin
6867	$document.on( "click keydown toggle." + componentName, selector + " summary", function( event ) {
6868		var which = event.which,
6869			currentTarget = event.currentTarget,
6870			isClosed,
6871			details;
6872
6873		// Ignore middle/right mouse buttons and wb-toggle enhanced summary elements (except for toggle)
6874		if ( ( !which || which === 1 ) &&
6875			( currentTarget.className.indexOf( "wb-toggle" ) === -1 ||
6876			( event.type === "toggle" && event.namespace === componentName ) ) ) {
6877
6878			details = currentTarget.parentNode;
6879			isClosed = details.getAttribute( "open" ) === null;
6880			key = "alert-collapsible-state-" + details.getAttribute( "id" );
6881
6882			if ( isClosed ) {
6883				try {
6884					localStorage.setItem( key, "open" );
6885				} catch ( e ) {
6886					/* swallow error */}
6887			} else {
6888				try {
6889					localStorage.setItem( key, "closed" );
6890				} catch ( e ) {
6891					/* swallow error */}
6892			}
6893		} else if ( which === 13 || which === 32 ) {
6894			event.preventDefault();
6895			$( currentTarget ).trigger( "click" );
6896		}
6897
6898		/*
6899		 * Since we are working with events we want to ensure that we are being passive about our control,
6900		 * so returning true allows for events to always continue
6901		 */
6902		return true;
6903	} );
6904}
6905
6906// Add the timer poke to initialize the plugin
6907wb.add( selector );
6908
6909} )( jQuery, window, wb );
6910
6911/**
6912 * @title WET-BOEW Country Content
6913 * @overview A basic AjaxLoader wrapper that inserts AJAXed in content based on a visitors country as resolved by freegeoip.app
6914 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
6915 * @author @nschonni
6916 */
6917( function( $, window, wb ) {
6918"use strict";
6919
6920/*
6921 * Variable and function definitions.
6922 * These are global to the plugin - meaning that they will be initialized once
6923 * per page, not once per instance of plugin on the page. So, this is a good
6924 * place to define variables that are common to all instances of the plugin on a
6925 * page.
6926 */
6927var componentName = "wb-ctrycnt",
6928	selector = "[data-ctrycnt]",
6929	initEvent = "wb-init." + componentName,
6930	$document = wb.doc,
6931
6932	/**
6933	 * @method init
6934	 * @param {jQuery Event} event Event that triggered this handler
6935	 */
6936	init = function( event ) {
6937
6938		// Start initialization
6939		// returns DOM object = proceed with init
6940		// returns undefined = do not proceed with init (e.g., already initialized)
6941		var elm = wb.init( event, componentName, selector ),
6942			$elm, url;
6943
6944		if ( elm ) {
6945			$elm = $( elm );
6946			url = $elm.data( "ctrycnt" );
6947
6948			$.when( getCountry() ).then( function( countryCode ) {
6949
6950				if ( countryCode === "" ) {
6951
6952					// Leave default content since we couldn't find the country
6953					return;
6954				} else {
6955
6956					// @TODO: Handle bad country values or any whitelist of countries.
6957				}
6958
6959				url = url.replace( "{country}", countryCode.toLowerCase() );
6960
6961				$elm.load( url, function() {
6962
6963					// Identify that initialization has completed
6964					wb.ready( $elm, componentName );
6965				} );
6966			} );
6967		}
6968	},
6969	getCountry = function() {
6970		var dfd = $.Deferred(),
6971			countryCode = localStorage.getItem( "countryCode" );
6972
6973		// Couldn"t find a value in the session
6974		if ( countryCode === null ) {
6975
6976			// From https://github.com/aFarkas/webshim/blob/master/src/shims/geolocation.js#L89-L127
6977			$.ajax( {
6978				url: "https://api.country.is/",
6979				dataType: "json",
6980				cache: true,
6981				success: function( data ) {
6982					if ( data ) {
6983						countryCode = data.country;
6984						try {
6985							localStorage.setItem( "countryCode", countryCode );
6986						} catch ( error ) {
6987
6988							/* swallow error */
6989						}
6990					}
6991
6992					dfd.resolve( countryCode );
6993				},
6994				error: function() {
6995					dfd.reject( "" );
6996				}
6997			} );
6998		} else {
6999			dfd.resolve( countryCode );
7000		}
7001
7002		return dfd.promise();
7003	};
7004
7005// Bind the init event of the plugin
7006$document.on( "timerpoke.wb " + initEvent, selector, init );
7007
7008// Add the timer poke to initialize the plugin
7009wb.add( selector );
7010
7011} )( jQuery, window, wb );
7012
7013/**
7014 * @title WET-BOEW Data Ajax [data-ajax-after], [data-ajax-append],
7015 * [data-ajax-before], [data-ajax-prepend] and [data-ajax-replace]
7016 * @overview A basic AjaxLoader wrapper that inserts AJAXed-in content
7017 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7018 * @author WET Community
7019 */
7020( function( $, window, wb ) {
7021"use strict";
7022
7023/*
7024 * Variable and function definitions.
7025 * These are global to the plugin - meaning that they will be initialized once
7026 * per page, not once per instance of plugin on the page. So, this is a good
7027 * place to define variables that are common to all instances of the plugin on a
7028 * page.
7029 */
7030var componentName = "wb-data-ajax",
7031	shortName = "wb-ajax",
7032	selectors = [
7033		"[data-ajax-after]",
7034		"[data-ajax-append]",
7035		"[data-ajax-before]",
7036		"[data-ajax-prepend]",
7037		"[data-ajax-replace]",
7038		"[data-" + shortName + "]"
7039	],
7040	ajaxTypes = [
7041		"before",
7042		"replace",
7043		"after",
7044		"append",
7045		"prepend"
7046	],
7047	selectorsLength = selectors.length,
7048	selector = selectors.join( "," ),
7049	initEvent = "wb-init." + componentName,
7050	updateEvent = "wb-update." + componentName,
7051	contentUpdatedEvent = "wb-contentupdated",
7052	$document = wb.doc,
7053	s,
7054
7055	/**
7056	 * @method init
7057	 * @param {jQuery Event} event Event that triggered this handler
7058	 * @param {string} ajaxType The type of AJAX operation, either after, append, before or replace
7059	 */
7060	init = function( event ) {
7061
7062		// Start initialization
7063		// returns DOM object = proceed with init
7064		// returns undefined = do not proceed with init (e.g., already initialized)
7065		var ajxInfo = getAjxInfo( event.target ),
7066			ajaxType = ajxInfo.type,
7067			elm = wb.init( event, componentName + "-" + ajaxType, selector );
7068
7069		if ( elm && ajxInfo.url ) {
7070
7071			ajax.call( this, event, ajxInfo );
7072
7073			// Identify that initialization has completed
7074			wb.ready( $( elm ), componentName, [ ajaxType ] );
7075		}
7076	},
7077
7078	ajax = function( event, ajxInfo ) {
7079		var elm = event.target,
7080			$elm = $( elm ),
7081			settings = window[ componentName ],
7082			url,
7083			fetchObj,
7084			urlParts;
7085
7086		if ( !ajxInfo ) {
7087			ajxInfo = getAjxInfo( elm );
7088		}
7089		url = ajxInfo.url;
7090		fetchObj = {
7091			url: url,
7092			nocache: ajxInfo.nocache,
7093			nocachekey: ajxInfo.nocachekey
7094		};
7095
7096		// Detect CORS requests
7097		if ( settings && ( url.substr( 0, 4 ) === "http" || url.substr( 0, 2 ) === "//" ) ) {
7098			urlParts = wb.getUrlParts( url );
7099			if ( ( wb.pageUrlParts.protocol !== urlParts.protocol || wb.pageUrlParts.host !== urlParts.host ) && ( !Modernizr.cors || settings.forceCorsFallback ) ) {
7100				if ( typeof settings.corsFallback === "function" ) {
7101					fetchObj.dataType = "jsonp";
7102					fetchObj.jsonp = "callback";
7103					fetchObj = settings.corsFallback( fetchObj );
7104				}
7105			}
7106		}
7107
7108		$elm.trigger( {
7109			type: "ajax-fetch.wb",
7110			fetch: fetchObj
7111		} );
7112	},
7113
7114	// Get Info and return { "url": "the/ajax/URL", "atype" }
7115	getAjxInfo = function( elm ) {
7116		var ajaxType,
7117			len = ajaxTypes.length,
7118			i, url, dtAttr, nocache, nocachekey;
7119
7120		for ( i = 0; i !== len; i += 1 ) {
7121			ajaxType = ajaxTypes[ i ];
7122			url = elm.getAttribute( "data-ajax-" + ajaxType );
7123			if ( url ) {
7124				break;
7125			}
7126		}
7127
7128		if ( !url ) {
7129			dtAttr = wb.getData( $( elm ), shortName );
7130
7131			// Abort the init when called on an invalid element (related to #8058)
7132			if ( !dtAttr ) {
7133				return {};
7134			}
7135
7136			ajaxType = dtAttr.type;
7137			if ( ajaxTypes.indexOf( ajaxType ) === -1 ) {
7138				throw "Invalid ajax type";
7139			}
7140
7141			url = getURL( dtAttr.url, dtAttr.httpref );
7142			if ( !url ) {
7143				return { "type": ajaxType };
7144			}
7145
7146			nocache = dtAttr.nocache;
7147			nocachekey = dtAttr.nocachekey;
7148		}
7149
7150		return {
7151			"url": url,
7152			"type": ajaxType,
7153			"nocache": nocache,
7154			"nocachekey": nocachekey
7155		};
7156	},
7157
7158	// Return url for conditional display if regexp match http refer
7159	getURL = function( url, referer ) {
7160		var refers, httpRef, regHttpRef,
7161			i, i_len;
7162
7163		if ( referer ) {
7164			if ( !$.isArray( referer ) ) {
7165				refers = [];
7166				refers.push( referer );
7167			} else {
7168				refers = referer;
7169			}
7170
7171			httpRef = window.document.referrer;
7172			i_len = refers.length;
7173			for ( i = 0; i !== i_len; i += 1 ) {
7174				regHttpRef = new RegExp( refers[ i ] );
7175				if ( regHttpRef.test( httpRef ) ) {
7176					if ( $.isArray( url ) && url.length === i_len ) {
7177						return url[ i ];
7178					} else {
7179						return url;
7180					}
7181				}
7182			}
7183		} else {
7184			return url;
7185		}
7186		return "";
7187	},
7188
7189	ajxFetched = function( elm, fetchObj ) {
7190		var $elm = $( elm ),
7191			ajxInfo = getAjxInfo( elm ),
7192			ajaxType = ajxInfo.type,
7193			content, jQueryCaching,
7194			settings = wb.getData( $( elm ), shortName ) || {},
7195			doEncode = settings.encode,
7196			hasSelector = fetchObj.hasSelector;
7197
7198		// ajax-fetched event
7199		content = fetchObj.response;
7200		if ( content &&  content.length > 0 ) {
7201
7202			// If the fetched content need to be encoded
7203			if ( doEncode && hasSelector ) {
7204				content = content.html().replaceAll( "<", "&lt;" );
7205			} else if ( doEncode && !hasSelector ) {
7206				content = fetchObj.xhr.responseText.replaceAll( "<", "&lt;" );
7207			}
7208
7209			//Prevents the force caching of nested resources
7210			jQueryCaching = jQuery.ajaxSettings.cache;
7211			jQuery.ajaxSettings.cache = true;
7212
7213			// "replace" is the only event that doesn't map to a jQuery function
7214			if ( ajaxType === "replace" ) {
7215				$elm.html( content );
7216			} else {
7217				$elm[ ajaxType ]( content );
7218			}
7219
7220			//Resets the initial jQuery caching setting
7221			jQuery.ajaxSettings.cache = jQueryCaching;
7222
7223			$elm.trigger( contentUpdatedEvent, { "ajax-type": ajaxType, "content": content } );
7224		}
7225	};
7226
7227$document.on( "timerpoke.wb " + initEvent + " " + updateEvent + " ajax-fetched.wb", selector, function( event ) {
7228	var eventTarget = event.target;
7229
7230	switch ( event.type ) {
7231
7232	case "timerpoke":
7233	case "wb-init":
7234		init( event );
7235		break;
7236	case "wb-update":
7237		ajax( event );
7238		break;
7239	default:
7240
7241		// Filter out any events triggered by descendants
7242		if ( event.currentTarget === eventTarget ) {
7243			ajxFetched( eventTarget, event.fetch );
7244		}
7245	}
7246
7247	/*
7248	 * Since we are working with events we want to ensure that we are being
7249	 * passive about our control, so returning true allows for events to always
7250	 * continue
7251	 */
7252	return true;
7253} );
7254
7255// Re-run WET for elements that have just been loaded if WET is already done initializing
7256$document.on( contentUpdatedEvent, function( event ) {
7257	if ( !wb.isDisabled ) {
7258		let updtElm = event.target;
7259
7260		$( updtElm )
7261			.find( wb.allSelectors )
7262			.addClass( "wb-init" )
7263			.filter( ":not(#" + updtElm.id + " .wb-init .wb-init)" )
7264			.trigger( "timerpoke.wb" );
7265	}
7266} );
7267
7268// Add the timerpoke to initialize the plugin
7269for ( s = 0; s !== selectorsLength; s += 1 ) {
7270	wb.add( selectors[ s ] );
7271}
7272
7273} )( jQuery, window, wb );
7274
7275/**
7276 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
7277 * @title Data Fusion Query
7278 * @overview Map a query parameter value into an input value
7279 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7280 * @author @duboisp
7281 *
7282 */
7283( function( document, $, wb ) {
7284"use strict";
7285
7286/**
7287 * Variable and function definitions.
7288 * These are global to the plugin - meaning that they will be initialized once per page,
7289 * not once per instance of plugin on the page. So, this is a good place to define
7290 * variables that are common to all instances of the plugin on a page.
7291 */
7292var componentName = "wb-data-fusion-query",
7293	selector = "[data-fusion-query][name]",
7294	initEvent = "wb-init." + componentName,
7295
7296
7297	/**
7298	 * @method init
7299	 * @param {jQuery Event} event Event that triggered the function call
7300	 */
7301	init = function( event ) {
7302
7303		// Start initialization
7304		// returns DOM object = proceed with init
7305		// returns undefined = do not proceed with init (e.g., already initialized)
7306		var elm = wb.init( event, componentName, selector ),
7307			$elm,
7308			inputName,
7309			queryParamValue;
7310
7311		if ( elm ) {
7312			$elm = $( elm );
7313
7314			// Retrieve the query parameter value and set it on the input
7315			inputName = $elm.attr( "name" );
7316			queryParamValue = wb.pageUrlParts.params[ inputName ];
7317			if ( queryParamValue ) {
7318				$elm.val( queryParamValue.replace( /\+/g, " " ) );
7319			}
7320
7321			// Identify that initialization has completed
7322			wb.ready( $elm, componentName );
7323		}
7324	};
7325
7326// Bind the init event of the plugin
7327wb.doc.on( "timerpoke.wb " + initEvent, selector, init );
7328
7329// Add the timer poke to initialize the plugin
7330wb.add( selector );
7331
7332} )( document, jQuery, wb );
7333
7334/**
7335 * @title WET-BOEW Data InView
7336 * @overview A simplified data-attribute driven plugin that responds to moving in and out of the viewport.
7337 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7338 * @author WET Community
7339 */
7340( function( $, window, wb ) {
7341"use strict";
7342
7343/*
7344 * Variable and function definitions.
7345 * These are global to the plugin - meaning that they will be initialized once per page,
7346 * not once per instance of plugin on the page. So, this is a good place to define
7347 * variables that are common to all instances of the plugin on a page.
7348 */
7349var componentName = "wb-inview",
7350	selector = "." + componentName,
7351	initEvent = "wb-init" + selector,
7352	scrollEvent = "scroll" + selector,
7353	$document = wb.doc,
7354	$window = wb.win,
7355	$elms = $(),
7356
7357	/**
7358	 * @method init
7359	 * @param {jQuery Event} event Event that triggered this handler
7360	 */
7361	init = function( event ) {
7362
7363		// Start initialization
7364		// returns DOM object = proceed with init
7365		// returns undefined = do not proceed with init (e.g., already initialized)
7366		var elm = wb.init( event, componentName, selector ),
7367			$elm;
7368
7369		if ( elm ) {
7370			$elm = $( elm );
7371			$elms = $elms.add( $elm );
7372
7373			// Allow other plugins to run first
7374			setTimeout( function() {
7375				onInView( $elm );
7376
7377				// Identify that initialization has completed
7378				wb.ready( $elm, componentName );
7379			}, 1 );
7380		}
7381	},
7382
7383	/**
7384	 * @method onInView
7385	 * @param {jQuery DOM element} $elm The plugin element
7386	 */
7387	onInView = function( $elm ) {
7388		var elementWidth = $elm.outerWidth(),
7389			elementHeight = $elm.outerHeight(),
7390			scrollTop = $window.scrollTop(),
7391			scrollBottom = scrollTop + $window.height(),
7392			scrollRight = $window.scrollLeft() + elementWidth,
7393			x1 = $elm.offset().left,
7394			x2 = x1 + elementWidth,
7395			y1 = $elm.offset().top,
7396			y2 = y1 + elementHeight,
7397			oldViewState = $elm.attr( "data-inviewstate" ),
7398			inView = ( scrollBottom < y1 || scrollTop > y2 ) || ( scrollRight < x1 || scrollRight > x2 ),
7399
7400			// this is a bit of a play on true/false to get the desired effect. In short this variable depicts
7401			// the view state of the element
7402			// all - the whole element is in the viewport
7403			// partial - part of the element is in the viewport
7404			// none - no part of the element is in the viewport
7405			viewState = ( scrollBottom > y2 && scrollTop < y1 ) ? "all" : inView ? "none" : "partial",
7406			$dataInView = $( "#" + $elm.attr( "data-inview" ) ),
7407			show;
7408
7409		// Remove any element that no longer exists in the DOM
7410		if ( elementWidth === 0 || elementHeight === 0 ) {
7411			$elms = $elms.not( $elm );
7412			$dataInView.addClass( "user-closed" );
7413			$dataInView.trigger( {
7414				type: ( "close" ),
7415				namespace: "wb-overlay",
7416				noFocus: true
7417			} );
7418
7419			return;
7420		}
7421
7422		// Link the overlay close button to the dismiss action if the inview content is dismissable
7423		if ( $elm.hasClass( "wb-dismissable" ) ) {
7424			if ( $dataInView.hasClass( "wb-overlay" ) ) {
7425				$dataInView.children( ".overlay-close" ).on( "click", function( event ) {
7426					var which = event.which;
7427
7428					// Ignore middle/right mouse buttons
7429					if ( !which || which === 1 ) {
7430						$elm.parent().siblings( ".content-dismiss" ).trigger( "click" );
7431					}
7432				} );
7433			}
7434		}
7435
7436		// Only if the view state has changed
7437		if ( viewState !== oldViewState ) {
7438
7439			// Show on "partial"/"none" (default) or just "none" (requires "show-none" class)
7440			show = inView || ( $elm.hasClass( "show-none" ) ? false : viewState === "partial" );
7441
7442			$elm.attr( "data-inviewstate", viewState );
7443
7444			if ( $dataInView.length !== 0 ) {
7445
7446				// Keep closed if the user closed the inView result
7447				if ( !$dataInView.hasClass( "user-closed" ) ) {
7448					if ( $dataInView.hasClass( "wb-overlay" ) ) {
7449						if ( !oldViewState ) {
7450							$dataInView.addClass( "outside-off" );
7451						}
7452						$dataInView.trigger( {
7453							type: ( show ? "open" : "close" ),
7454							namespace: "wb-overlay",
7455							noFocus: true
7456						} );
7457					} else {
7458						if ( !$dataInView.attr( "data-hasPlayed" ) ) {
7459							$dataInView.toggleClass( "in", !show );
7460							if ( !$dataInView.hasClass( "in-only" ) ) {
7461								$dataInView.toggleClass( "out", show );
7462							}
7463						}
7464						if ( wb.isReady && viewState === "all" && $dataInView.hasClass( "in-only" ) ) {
7465							$dataInView.attr( "data-hasPlayed", "true" );
7466						}
7467					}
7468				}
7469			}
7470
7471			// Trigger an event on the element identifying that the view state has changed
7472			// (e.g., "all.wb-inview", "partial.wb-inview", "none.wb-inview")
7473			$elm.trigger( viewState + selector );
7474		}
7475	};
7476
7477// Bind the init event of the plugin
7478$document.on( "timerpoke.wb " + initEvent + " " + scrollEvent, selector, function( event ) {
7479	var eventTarget = event.target,
7480		eventType = event.type;
7481
7482	switch ( eventType ) {
7483	case "timerpoke":
7484	case "wb-init":
7485		init( event );
7486		break;
7487
7488	case "scroll":
7489
7490		// Filter out any events triggered by descendants
7491		if ( event.currentTarget === eventTarget ) {
7492			onInView( $( eventTarget ) );
7493		}
7494		break;
7495	}
7496
7497	/*
7498	 * Since we are working with events we want to ensure that we are being passive about our control,
7499	 * so returning true allows for events to always continue
7500	 */
7501	return true;
7502} );
7503
7504$window.on( "scroll scrollstop", function() {
7505	$elms.trigger( scrollEvent );
7506} );
7507
7508$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb", function() {
7509	$elms.trigger( scrollEvent );
7510} );
7511
7512$document.on( "refresh.wb", function() {
7513	$elms.each( function() {
7514		onInView( $( this ) );
7515	} );
7516} );
7517
7518// Add the timer poke to initialize the plugin
7519wb.add( selector );
7520
7521} )( jQuery, window, wb );
7522
7523/**
7524 * @title WET-BOEW Data Picture
7525 * @overview Event driven port of the Picturefill library: https://github.com/scottjehl/picturefill
7526 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7527 * @author @patheard
7528 */
7529( function( $, window, wb ) {
7530"use strict";
7531
7532/*
7533 * Variable and function definitions.
7534 * These are global to the plugin - meaning that they will be initialized once per page,
7535 * not once per instance of plugin on the page. So, this is a good place to define
7536 * variables that are common to all instances of the plugin on a page.
7537 */
7538var imgClass,
7539	componentName = "wb-pic",
7540	selector = "[data-pic]",
7541	initEvent = "wb-init." + componentName,
7542	picturefillEvent = "picfill." + componentName,
7543	$document = wb.doc,
7544
7545	/**
7546	 * @method init
7547	 * @param {jQuery Event} event Event that triggered this handler
7548	 */
7549	init = function( event ) {
7550
7551		// Start initialization
7552		// returns DOM object = proceed with init
7553		// returns undefined = do not proceed with init (e.g., already initialized)
7554		var elm = wb.init( event, componentName, selector ),
7555			$elm;
7556
7557		if ( elm ) {
7558			$elm = $( elm );
7559
7560			// Store the class attribute of the plugin element.  It
7561			// will be added to the image created by the plugin.
7562			imgClass = $elm.data( "class" ) || "";
7563
7564			$elm.trigger( picturefillEvent );
7565
7566			// Identify that initialization has completed
7567			wb.ready( $elm, componentName );
7568		}
7569	},
7570
7571	/**
7572	 * Updates the image displayed according to media queries.
7573	 * This is the logic ported from Picturefill.
7574	 * @method picturefill
7575	 * @param {DOM element} elm The element containing the images to be updated
7576	 */
7577	picturefill = function( elm ) {
7578		var matches = [],
7579			img = elm.getElementsByTagName( "img" )[ 0 ],
7580			sources = elm.getElementsByTagName( "span" ),
7581			i, len, matchedElm, media;
7582
7583		// Loop over the data-media elements and find matching media queries
7584		for ( i = 0, len = sources.length; i !== len; i += 1 ) {
7585			media = sources[ i ].getAttribute( "data-media" );
7586			if ( !media || Modernizr.mq( media ) ) {
7587				matches.push( sources[ i ] );
7588			}
7589		}
7590
7591		// If a media query match was found, add the image to the page
7592		if ( matches.length !== 0 ) {
7593			matchedElm = matches.pop();
7594			if ( !img ) {
7595				img = $document[ 0 ].createElement( "img" );
7596				img.alt = elm.getAttribute( "data-alt" );
7597				img.className = imgClass;
7598			}
7599			img.src = matchedElm.getAttribute( "data-src" );
7600			matchedElm.appendChild( img );
7601
7602		// No match and an image exists: delete it
7603		} else if ( img ) {
7604			img.parentNode.removeChild( img );
7605		}
7606
7607		// Identify that the picture has been updated
7608		$( elm ).trigger( "wb-updated." + componentName );
7609	};
7610
7611// Bind the init event of the plugin
7612$document.on( "timerpoke.wb " + initEvent + " " + picturefillEvent, selector, function( event ) {
7613	var eventTarget = event.target,
7614		eventType = event.type;
7615
7616	switch ( eventType ) {
7617	case "timerpoke":
7618	case "wb-init":
7619		init( event );
7620		break;
7621
7622	case "picfill":
7623
7624		// Filter out any events triggered by descendants
7625		if ( event.currentTarget === eventTarget ) {
7626			picturefill( eventTarget );
7627		}
7628		break;
7629	}
7630} );
7631
7632// Handles window resize so images can be updated as new media queries match
7633$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb", function() {
7634	$( selector ).trigger( picturefillEvent );
7635} );
7636
7637// Add the timer poke to initialize the plugin
7638wb.add( selector );
7639
7640} )( jQuery, window, wb );
7641
7642/**
7643 * @title WET-BOEW Details closed on small screen
7644 * @overview Closes details on defined viewport and down if they were not engaged, default is small
7645 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7646 * @author @thomasgohard
7647 */
7648( function( $, wb ) {
7649"use strict";
7650
7651/*
7652* Variable and function definitions.
7653* These are global to the plugin - meaning that they will be initialized once per page,
7654* not once per instance of plugin on the page. So, this is a good place to define
7655* variables that are common to all instances of the plugin on a page.
7656*/
7657var componentName = "wb-details-close",
7658	selector = ".provisional." + componentName,
7659	initEvent = "wb-init" + selector,
7660	$document = wb.doc,
7661	views = [ "xxs", "xs", "sm", "md", "lg", "xl" ],
7662	viewsClass = [ "xxsmallview", "xsmallview", "smallview", "mediumview", "largeview", "xlargeview" ],
7663	breakpoint,
7664
7665	/**
7666	 * @method init
7667	 * @param {jQuery Event} event Event that triggered the function call
7668	 */
7669	init = function( event ) {
7670
7671		// Start initialization
7672		// returns DOM object = proceed with init
7673		// returns undefined = do not proceed with init (e.g., already initialized)
7674		var elm = wb.init( event, componentName, selector ),
7675			$elm, i;
7676
7677		if ( elm ) {
7678			$elm = $( elm );
7679
7680			// Get the plugin JSON configuration set on attribute data-wb-details-close
7681			// Will define one set settings for all .wb-details-close on the page
7682			breakpoint = $elm.data( "breakpoint" ) || "sm";
7683
7684			// reset breakpoint if config is passed
7685			if ( views.length === viewsClass.length ) {
7686				i = views.indexOf( breakpoint );
7687				viewsClass = viewsClass.slice( 0, i + 1 );
7688			}
7689
7690			hideOnBreakpoint();
7691
7692			// Identify that initialization has completed
7693			wb.ready( $elm, componentName );
7694		}
7695	},
7696
7697	/**
7698	 * Toggle details depending on breakpoint
7699	 * @method hideOnBreakpoint
7700	 * @param {jQuery DOM element | jQuery Event} $elm Element targetted by this plugin, which is the details
7701	 */
7702	hideOnBreakpoint = function() {
7703		var $elm = $( selector ),
7704			viewsSelector = "html." + viewsClass.join( ", html." );
7705
7706		// If within the targetted views, keep details closed
7707		if ( $( viewsSelector ).length ) {
7708			$elm.removeAttr( "open" );
7709		} else {
7710
7711			// If not, keep opened
7712			$elm.attr( "open", "" );
7713		}
7714	};
7715
7716// Bind the init event of the plugin
7717$document.on( "timerpoke.wb " + initEvent, selector, init );
7718
7719// Add the timer poke to initialize the plugin
7720wb.add( selector );
7721
7722} )( jQuery, wb );
7723
7724/**
7725 * @title WET-BOEW Dismissable content plugin
7726 * @overview Enables content to be dismissed
7727 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7728 * @author @thomasgohard
7729 */
7730( function( $, window, wb ) {
7731"use strict";
7732
7733/*
7734 * Variable and function definitions.
7735 * These are global to the event - meaning that they will be initialized once per page,
7736 * not once per instance of event on the page.
7737 */
7738var componentName = "wb-dismissable",
7739	selector = "." + componentName,
7740	initEvent = "wb-init." + componentName,
7741	containerClass = "wb-dismissable-container",
7742	wrapperClass = "wb-dismissable-wrapper",
7743	dismissClass = "content-dismiss",
7744	idKey = "dismissable-item-id",
7745	$document = wb.doc,
7746	i18n, i18nText,
7747
7748	/**
7749	 * @method init
7750	 * @param {jQuery Event} event Event that triggered the function call
7751	 */
7752	init = function( event ) {
7753
7754		// Start initialization
7755		// returns DOM object = proceed with init
7756		// returns undefined = do not proceed with init (e.g., already initialized)
7757		var elm = wb.init( event, componentName, selector ),
7758			itemId, dismissedState, contentContainer, contentWrapper, dismissButton;
7759
7760		if ( elm ) {
7761
7762			// Only initialize the i18nText once
7763			if ( !i18nText ) {
7764				i18n = wb.i18n;
7765				i18nText = {
7766					dismiss: i18n( "dismiss" )
7767				};
7768			}
7769
7770			// Give the dismissable element a unique ID
7771			if ( elm.hasAttribute( "id" ) ) {
7772				itemId = elm.getAttribute( "id" );
7773
7774				// Ignore an ID assigned by wb
7775				if ( itemId.indexOf( "wb-auto-" ) === 0 ) {
7776					itemId = undefined;
7777				}
7778			}
7779			if ( itemId === undefined ) {
7780				itemId = wb.hashString( wb.stripWhitespace( elm.innerHTML ) );
7781			}
7782
7783			dismissedState = getDismissedState( itemId );
7784
7785			if ( dismissedState === "true" ) {
7786
7787				// Remove the element if it has been dismissed
7788				if ( elm.parentNode ) {
7789					elm.parentNode.removeChild( elm );
7790				}
7791			} else {
7792				$( elm ).wrap( "<div class='" + wrapperClass + "'>" );
7793				contentWrapper = elm.parentNode;
7794
7795				$( contentWrapper ).wrap( "<div class='" + containerClass + "'>" );
7796				contentContainer = contentWrapper.parentNode;
7797
7798				dismissButton = "<button type='button' class='mfp-close " + dismissClass +
7799					"' title='" + i18nText.dismiss + "'>&#xd7;<span class='wb-inv'> " +
7800					i18nText.dismiss + "</span></button>";
7801				$( contentContainer ).append( dismissButton );
7802
7803				contentContainer.setAttribute( "data-" + idKey, itemId );
7804			}
7805
7806			// Identify that initialization has completed
7807			wb.ready( $document, componentName );
7808		}
7809	},
7810
7811	getDismissedState = function( id ) {
7812		var dismissState = localStorage.getItem( id );
7813
7814		if ( dismissState === null ) {
7815			return false;
7816		}
7817
7818		return dismissState;
7819	},
7820
7821	dismissContent = function( elm ) {
7822		localStorage.setItem( elm.getAttribute( "data-" + idKey ), true );
7823		elm.parentNode.removeChild( elm );
7824		$document.trigger( "refresh.wb" );
7825	};
7826
7827// Bind the init event of the plugin
7828$document.on( "timerpoke.wb " + initEvent, selector, init );
7829
7830// Handler for clicking on the dismiss button
7831$document.on( "click", "." + dismissClass, function( event ) {
7832	var elm = event.currentTarget,
7833		which = event.which;
7834
7835	// Ignore middle/right mouse buttons
7836	if ( !which || which === 1 ) {
7837		dismissContent( elm.parentNode );
7838	}
7839} );
7840
7841// Add the timer poke to initialize the plugin
7842wb.add( selector );
7843
7844} )( jQuery, window, wb );
7845
7846/**
7847 * @title eqht
7848 * @overview Provide ability to have equal height containers and nested containers inside a WET-BOEW grid
7849 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7850 * @author @luc-bertrand-hrsdc
7851 */
7852( function( $, window, wb ) {
7853"use strict";
7854
7855/*
7856 * Variable and function definitions.
7857 * These are global to the plugin - meaning that they will be initialized once per page,
7858 * not once per instance of plugin on the page. So, this is a good place to define
7859 * variables that are common to all instances of the plugin on a page.
7860 */
7861var componentName = "wb-eqht-grd",
7862	selector = "." + componentName + " .eqht-trgt",
7863	initEvent = "wb-init" + selector,
7864	$document = wb.doc,
7865
7866	/**
7867	 * @method init
7868	 * @param {jQuery Event} event Event that triggered the function call
7869	 */
7870	init = function( event ) {
7871
7872		// Start initialization
7873		// returns DOM object = proceed with init
7874		// returns undefined = do not proceed with init (e.g., already initialized)
7875		var elm = wb.init( event, componentName, selector ),
7876			$elm,
7877			$eqhtParents;
7878
7879		if ( elm ) {
7880			$elm = $( elm );
7881			$elm.addClass( "hght-inhrt" );
7882			$eqhtParents = $elm.parentsUntil( "[class*='" + componentName + "']" );
7883			$eqhtParents.addClass( "hght-inhrt" );
7884
7885			// Identify that initialization has completed
7886			wb.ready( $elm, componentName );
7887		}
7888	};
7889
7890// Bind the init event of the plugin
7891$document.on( "timerpoke.wb " + initEvent, selector, init );
7892
7893// Add the timer poke to initialize the plugin
7894wb.add( selector );
7895
7896} )( jQuery, window, wb );
7897
7898/**
7899 * @title WET-BOEW Responsive equal height
7900 * @overview Sets the same height for all elements in a container that are rendered on the same baseline (row). Adapted from https://codepen.io/micahgodbolt/pen/FgqLc.
7901 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
7902 * @author @thomasgohard
7903 */
7904( function( $, window, wb ) {
7905"use strict";
7906
7907/*
7908 * Variable and function definitions.
7909 * These are global to the plugin - meaning that they will be initialized once per page,
7910 * not once per instance of plugin on the page. So, this is a good place to define
7911 * variables that are common to all instances of the plugin on a page.
7912 */
7913var componentName = "wb-eqht",
7914	selector = "." + componentName,
7915	$document = wb.doc,
7916	eventTimerpoke = "timerpoke.wb",
7917	initEvent = "wb-init" + selector,
7918	vAlignCSS = "vertical-align",
7919	vAlignDefault = "top",
7920	contentUpdatedEvent = "wb-contentupdated",
7921	minHeightCSS = "min-height",
7922	minHeightDefault = "0",
7923	cssValueSeparator = ":",
7924	cssPropertySeparator = ";",
7925	regexCSSValue = " ?[^;]+",
7926	regexVAlign = new RegExp( vAlignCSS + cssValueSeparator + " ?" + regexCSSValue + cssPropertySeparator + "?", "i" ),
7927	regexMinHeight = new RegExp( minHeightCSS + cssValueSeparator + " ?" + regexCSSValue + cssPropertySeparator + "?", "i" ),
7928
7929	/**
7930	 * @method init
7931	 * @param {jQuery Event} event Event that triggered the function call
7932	 */
7933	init = function( event ) {
7934
7935		// Start initialization
7936		// returns DOM object = proceed with init
7937		// returns undefined = do not proceed with init (e.g., already initialized)
7938		var elm = wb.init( event, componentName, selector );
7939
7940		if ( elm ) {
7941
7942			// Remove the event handler since only want init fired once per page (not per element)
7943			$document.off( eventTimerpoke, selector );
7944
7945			onResize();
7946
7947			// Identify that initialization has completed
7948			wb.ready( $document, componentName );
7949		}
7950	},
7951
7952	/**
7953	 * Re-equalise any time the window/document or a child element of 'selector' is resized.
7954	 * @method onResize
7955	 */
7956	onResize = function() {
7957		var $elm, $children, $anchor, currentChild, childCSS, i, j,
7958			$elms = $( selector ),
7959			row = [],
7960			rowTop = -1,
7961			currentChildTop = -1,
7962			currentChildHeight = -1,
7963			tallestHeight = -1;
7964
7965		for ( i = $elms.length - 1; i !== -1; i -= 1 ) {
7966			$elm = $elms.eq( i );
7967			$children = $elm.find( ".eqht-trgt" );
7968			if ( !$children.length ) {
7969				$children = $elm.children();
7970			}
7971
7972			// Reinitialize the row at the beginning of each section of equal height
7973			row = [];
7974
7975			$anchor = detachElement( $elm );
7976			for ( j = $children.length - 1; j !== -1; j -= 1 ) {
7977				currentChild = $children[ j ];
7978				childCSS = currentChild.style.cssText.toLowerCase();
7979
7980				//Ensure the CSS string ends by a seperator
7981				if ( childCSS.length > 0 && childCSS.substr( childCSS.length - 1 ) !== cssPropertySeparator ) {
7982					childCSS += cssPropertySeparator;
7983				}
7984
7985				// Ensure all children that are on the same baseline have the same 'top' value.
7986				if ( childCSS.indexOf( vAlignCSS ) !== -1 ) {
7987					childCSS = childCSS.replace( regexVAlign, vAlignCSS + cssValueSeparator + vAlignDefault + cssPropertySeparator );
7988				} else {
7989					childCSS += " " + vAlignCSS + cssValueSeparator + vAlignDefault + cssPropertySeparator;
7990				}
7991
7992				// Remove any previously set min height
7993				if ( childCSS.indexOf( minHeightCSS ) !== -1 ) {
7994					childCSS = childCSS.replace( regexMinHeight, minHeightCSS + cssValueSeparator + minHeightDefault + cssPropertySeparator );
7995				} else {
7996					childCSS += " " + minHeightCSS + cssValueSeparator + minHeightDefault + cssPropertySeparator;
7997				}
7998
7999				currentChild.style.cssText = childCSS;
8000				$children.eq( j ).data( minHeightCSS, minHeightDefault );
8001			}
8002			$elm = reattachElement( $anchor );
8003
8004			// set the top offset and tallest height to the first element
8005			rowTop = $children[ 0 ] ? $children[ 0 ].getBoundingClientRect().top + window.pageYOffset : 0;
8006			tallestHeight = $children[ 0 ] ? $children[ 0 ].offsetHeight : 0;
8007
8008			// first, the loop MUST be from start to end to work.
8009			for ( j = 0; j < $children.length; j++ ) {
8010				currentChild = $children[ j ];
8011
8012				currentChildTop = currentChild.getBoundingClientRect().top + window.pageYOffset;
8013				currentChildHeight = currentChild.offsetHeight;
8014
8015				// if the current element is visible...
8016				// note: hidden elements need to be excluded since they have a different top offset than visible ones
8017				if ( currentChildHeight ) {
8018
8019					// as soon as we find an element not on this row (not the same top offset)...
8020					if ( currentChildTop !== rowTop ) {
8021
8022						// we need to equalize each item in that row to align the next row
8023						equalize( row, tallestHeight );
8024
8025						// since the elements of the previous row was equalized
8026						// we need to get the new top offset of the current element
8027						currentChildTop = currentChild.getBoundingClientRect().top + window.pageYOffset;
8028
8029						// reset the row, rowTop and tallestHeight
8030						row.length = 0;
8031						rowTop = currentChildTop;
8032						tallestHeight = currentChildHeight;
8033					}
8034
8035					tallestHeight = Math.max( currentChildHeight, tallestHeight );
8036					row.push( $children.eq( j ) );
8037				}
8038			}
8039
8040			// equalize the last row
8041			equalize( row, tallestHeight );
8042
8043			// Identify that the height equalization was updated
8044			$document.trigger( "wb-updated" + selector );
8045		}
8046	},
8047
8048	/**
8049	* @method equalize
8050	* @param {array} row the array of items to be equalized
8051	* @param {int} tallestHeight the talest height to use to equalize
8052	*/
8053	equalize = function( row, tallestHeight ) {
8054		for ( var i = 0; i < row.length; i++ ) {
8055
8056			// added a +1 because some floated element got stuck if the
8057			// shortest element was the last element in the row
8058			var minHeight = tallestHeight + 1;
8059			row[ i ][ 0 ].style.minHeight = minHeight + "px";
8060		}
8061	},
8062
8063	/**
8064	 * @method detachElement
8065	 * @param {jQuery object} $elm The element to detach
8066	 * @returns {object} The detached element
8067	 */
8068	detachElement = function( $elm ) {
8069		var $prev = $elm.prev(),
8070			$next = $elm.next(),
8071			$parent = $elm.parent();
8072
8073		if ( $prev.length ) {
8074			$elm.data( { anchor: $prev, anchorRel: "prev" } );
8075		} else if ( $next.length ) {
8076			$elm.data( { anchor: $next, anchorRel: "next" } );
8077		} else if ( $parent.length ) {
8078			$elm.data( { anchor: $parent, anchorRel: "parent" } );
8079		}
8080
8081		return $elm.detach();
8082	},
8083
8084	/**
8085	 * @method reattachElement
8086	 * @param {jQuery object} $elm The element to reattach
8087	 * @returns {object} The reattached element
8088	 */
8089	reattachElement = function( $elm ) {
8090		var $anchor = $elm.data( "anchor" ),
8091			anchorRel = $elm.data( "anchorRel" );
8092
8093		switch ( anchorRel ) {
8094		case "prev":
8095			$anchor.after( $elm );
8096			break;
8097		case "next":
8098			$anchor.before( $elm );
8099			break;
8100		case "parent":
8101			$anchor.append( $elm );
8102			break;
8103		}
8104
8105		return $elm;
8106	};
8107
8108// Bind the init event of the plugin
8109$document.on( eventTimerpoke + " " + initEvent, selector, init );
8110
8111// Handle text and window resizing
8112$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb " + contentUpdatedEvent + " wb-updated.wb-tables wb-update" + selector, onResize );
8113
8114// Add the timer poke to initialize the plugin
8115wb.add( selector );
8116
8117} )( jQuery, window, wb );
8118
8119/**
8120* @title WET-BOEW Exit script plugin
8121* @overview Plugin redirects users to non secure site
8122* @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
8123* @author @ipaksc
8124*/
8125( function( $, window, wb, crypto ) {
8126"use strict";
8127var componentName = "wb-exitscript",
8128	selector = "." + componentName,
8129	initEvent = "wb-init" + selector,
8130	$document = wb.doc,
8131	exiturlparam = componentName + "-urlparam",
8132	keyForKeyHolder = componentName + "key",
8133	moDalId = componentName + "-modal",
8134	i18n,
8135	i18nDict = {
8136		en: {
8137			"msgboxHeader": "Warning",
8138			"exitMsg": "You are about to leave a secure site, do you wish to continue?",
8139			"targetWarning": "The link will open in a new browser window.",
8140			"yesBtn": "Yes",
8141			"cancelBtn": "Cancel"
8142
8143		},
8144		fr: {
8145			"msgboxHeader": "Avertissement",
8146			"exitMsg": "Vous êtes sur le point de quitter un site sécurisé. Voulez-vous continuer?",
8147			"targetWarning": "Le lien s'ouvrira dans une nouvelle fenêtre de navigateur.",
8148			"yesBtn": "Oui",
8149			"cancelBtn": "Annuler"
8150
8151		}
8152	},
8153
8154	/**
8155	 * @method init
8156	 * @param {jQuery Event} event Event that triggered the function call
8157	 */
8158	init = function( event ) {
8159		var elm = wb.init( event, componentName, selector ),
8160			settings,
8161			queryString = window.location.search,
8162			urlParams = new URLSearchParams( queryString ),
8163			counterInUrl = wb.string.fromHexString( urlParams.get( "exturl" ) ),
8164			encryptedUrl = localStorage.getItem( componentName ),
8165			jwt = JSON.parse( localStorage.getItem( keyForKeyHolder ) ),
8166			$elm;
8167		if ( elm ) {
8168			$elm = $( elm );
8169			settings = $.extend(
8170				true,
8171				window[ componentName ],
8172				wb.getData( $elm, componentName )
8173
8174			);
8175
8176			$elm.data( componentName, settings );
8177
8178			if ( settings.url && crypto ) {
8179
8180				crypto.subtle.generateKey(
8181					{
8182						name: "AES-CTR",
8183						length: 256
8184					},
8185					true,
8186					[ "encrypt", "decrypt" ]
8187				).then( function( keyToEncryp ) {
8188
8189					var enc, messageEncoded, counter;
8190
8191					// Save the key in the anchor
8192					crypto.subtle.exportKey( "jwk", keyToEncryp )
8193						.then( function( exportedJwtKey ) {
8194							elm[ keyForKeyHolder ] = exportedJwtKey;
8195						} );
8196
8197					// Encrypt the URL
8198					enc = new TextEncoder();
8199					messageEncoded = enc.encode( elm.href );
8200					counter = crypto.getRandomValues( new Uint8Array( 16 ) );
8201					crypto.subtle.encrypt(
8202						{
8203							name: "AES-CTR",
8204							counter: counter,
8205							length: 64
8206						},
8207						keyToEncryp,
8208						messageEncoded
8209					).then( function( ciphertext ) {
8210						elm[ componentName ] = ciphertext;
8211					} );
8212
8213					// Change the link URL by passing the counter as a key
8214					$elm.attr( "href", settings.url + "?exturl=" + wb.string.toHexString( counter ) );
8215				} );
8216
8217			}
8218
8219			i18n = i18nDict[ wb.lang || "en" ];
8220
8221			// This conditional statement for a middle static exit page, to retrieve the URL to the non-secure site.
8222			if ( $elm.hasClass( exiturlparam ) && encryptedUrl !== null && jwt !== null ) {
8223
8224				crypto.subtle.importKey(
8225					"jwk",
8226					jwt,
8227					{
8228						name: "AES-CTR",
8229						length: 256
8230					},
8231					true,
8232					[ "decrypt" ]
8233				).then( function( key ) {
8234
8235					crypto.subtle.decrypt(
8236						{
8237							name: "AES-CTR",
8238							counter: counterInUrl,
8239							length: 64
8240						},
8241						key,
8242						wb.string.base64ToArrayBuffer( encryptedUrl )
8243					).then( function( decrypted ) {
8244
8245						var dec = new TextDecoder(),
8246							urlToRedirect = dec.decode( decrypted );
8247
8248						// Check if the decrypted message is an valid URL and silently fail if the pattern don't match
8249						if ( urlToRedirect.match( /^(http|https):\/\//g ) ) {
8250							elm.outerHTML = "<a href='" + urlToRedirect + "'>" + urlToRedirect + "</a>";
8251						}
8252
8253					} );
8254				} );
8255
8256			}
8257
8258			// Remove the plugin data and ensure it is removed from the localstorage
8259			localStorage.removeItem( componentName );
8260			localStorage.removeItem( keyForKeyHolder );
8261
8262			wb.ready( $elm, componentName );
8263		}
8264	};
8265
8266$document.on( "click", selector, function( event ) {
8267
8268	var elm = event.currentTarget,
8269		$elm = $( elm ),
8270		wrapper,
8271		targetAttribute = "",
8272		moDal = document.createDocumentFragment(),
8273		tpl = document.createElement( "div" ),
8274		settings =  $elm.data( componentName ),
8275		msgboxHeader = i18n.msgboxHeader,
8276		yesBtn = i18n.yesBtn,
8277		cancelBtn = i18n.cancelBtn,
8278		exitMsg = i18n.exitMsg,
8279		targetWarning = i18n.targetWarning;
8280
8281	if ( settings.i18n ) {
8282		msgboxHeader =  settings.i18n.msgboxHeader || i18n.msgboxHeader;
8283		yesBtn = settings.i18n.yesBtn || i18n.yesBtn;
8284		cancelBtn = settings.i18n.cancelBtn || i18n.cancelBtn;
8285		exitMsg = settings.i18n.exitMsg || i18n.exitMsg;
8286		targetWarning = settings.i18n.targetWarning || i18n.targetWarning;
8287	}
8288
8289	if ( !settings.url ) {
8290
8291		event.preventDefault();
8292	}
8293
8294	if ( this.hasAttribute( "target" ) ) {
8295		targetAttribute = "target='" + this.getAttribute( "target" ) + "'";
8296	} else {
8297		targetAttribute = "target='" + targetAttribute + "'";
8298	}
8299
8300	if ( this.getAttribute( "target" ) === "_blank" ) {
8301		exitMsg = exitMsg  + " " + targetWarning;
8302	}
8303
8304	if ( document.getElementById( moDalId ) ) {
8305		document.getElementById( moDalId ).remove();
8306
8307	}
8308
8309	if ( !settings.url ) {
8310		tpl.innerHTML = "<section id='" + moDalId + "' " + "class='mfp-hide modal-dialog modal-content overlay-def'>" +
8311			"<header class='modal-header'><h2 class='modal-title'>" + msgboxHeader + "</h2></header>" +
8312			"<div class='modal-body'>" +
8313			"<p>" + exitMsg + "</p>" +
8314			"</div>" +
8315			"<div class='modal-footer'>" +
8316			"<ul class='list-inline text-center'>" +
8317			"<li><a class='btn btn-default pull-right popup-modal-dismiss'" + targetAttribute + " href='" + this.getAttribute( "href" ) + "'>" + yesBtn + "</a></li>" +
8318			"<li><button class='btn btn-primary popup-modal-dismiss pull-left'>" + cancelBtn + "</button></li>" +
8319			"</ul></div></section>";
8320		moDal.appendChild( tpl );
8321		wrapper = moDal.firstChild;
8322		wrapper = wrapper.firstChild;
8323		document.body.appendChild( wrapper );
8324
8325		$( wrapper ).trigger( "open.wb-lbx", [
8326			[ {
8327				src: "#" + moDalId,
8328				type: "inline"
8329			} ],
8330
8331			true
8332
8333		] );
8334
8335	} else if ( crypto && this[ componentName ] ) {
8336
8337		// Save to localstorage, the plugin init will ensure this data is only used once
8338		localStorage.setItem( componentName, wb.string.arrayBufferToBase64( this[ componentName ] ) );
8339		localStorage.setItem( keyForKeyHolder, JSON.stringify( this[ keyForKeyHolder ] ) );
8340	}
8341
8342} );
8343
8344// Bind the init event of the plugin
8345$document.on( "timerpoke.wb " + initEvent, selector, init );
8346
8347// Add the timer poke to initialize the plugin
8348wb.add( selector );
8349
8350} )( jQuery, window, wb, crypto );
8351
8352/**
8353* @title WET-BOEW Facebook embedded page
8354* @overview Helps with implementing Facebook embedded pages.
8355* @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
8356* @author @pjackson28
8357*/
8358( function( $, window, wb ) {
8359"use strict";
8360
8361/*
8362* Variable and function definitions.
8363* These are global to the plugin - meaning that they will be initialized once per page,
8364* not once per instance of plugin on the page. So, this is a good place to define
8365* variables that are common to all instances of the plugin on a page.
8366*/
8367var componentName = "wb-facebook",
8368	selector = "." + componentName,
8369	initEvent = "wb-init" + selector,
8370	$document = wb.doc,
8371	fbinited = false,
8372
8373	/**
8374	* @method init
8375	* @param {jQuery Event} event Event that triggered the function call
8376	*/
8377	init = function( event ) {
8378
8379		// Start initialization
8380		// returns DOM object = proceed with init
8381		// returns undefined = do not proceed with init (e.g., already initialized)
8382		var ele = wb.init( event, componentName, selector ),
8383			protocol = wb.pageUrlParts.protocol;
8384
8385		if ( ele ) {
8386			Modernizr.load(
8387				{
8388					load: [ ( protocol.indexOf( "http" ) === -1 ? "http:" : protocol ) + "//connect.facebook.net/" + wb.lang + "_US/sdk.js" ],
8389					complete: function() {
8390						if ( !fbinited ) {
8391							window.FB.init( {
8392								version: "v2.4"
8393							} );
8394							fbinited = true;
8395						}
8396
8397						window.FB.XFBML.parse( ele[ 0 ] );
8398						wb.ready( $( ele ), componentName );
8399					}
8400				}
8401			);
8402		}
8403	};
8404
8405$document.on( "timerpoke.wb " + initEvent, selector, init );
8406
8407// Add the timer poke to initialize the plugin
8408wb.add( selector );
8409
8410} )( jQuery, window, wb );
8411
8412/**
8413 * @title WET-BOEW Favicon Plugin
8414 * @overview Provides the ability to add and update a page's favicons
8415 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
8416 * @author @patheard
8417 *
8418 * This plugin provides the ability to add and update the favicon's on a web page. Its default behaviour is to add a mobile favicon to web pages that have a favicon defined by a `<link rel='icon'>` element.
8419 *
8420 * The mobile favicon's file name, rel, path and sizes can be set with data attributes on the `<link rel='icon'/>`:
8421 *
8422 * -**data-filename:** filename of the mobile favicon (defaults to "favicon-mobile.png"). This will be appended to the favicon's path.
8423 * -**data-path:** path to the mobile favicon (defaults to using the same path as the shortcut icon).
8424 * -**data-rel:** rel attribute of the mobile favicon (defaults to "apple-touch-icon").
8425 * -**data-sizes:** sizes attribute of the mobile favicon (defaults to "57x57 72x72 114x114 144x144 150x150").
8426 *
8427 * For example, the following overides the rel and file name attributes of the mobile favicon:
8428 *
8429 *     <link href="favion.ico" rel='icon' data-rel="apple-touch-icon-precomposed" data-filename="my-mobile-favicon.ico"/>
8430 */
8431( function( $, document, wb ) {
8432"use strict";
8433
8434/*
8435 * Variable and function definitions.
8436 * These are global to the plugin - meaning that they will be initialized once per page,
8437 * not once per instance of plugin on the page. So, this is a good place to define
8438 * variables that are common to all instances of the plugin on a page.
8439 */
8440var componentName = "wb-favicon",
8441	selector = "link[rel='icon']",
8442	initEvent = "wb-init." + componentName,
8443	updatedEvent = "wb-updated." + componentName,
8444	mobileEvent = "mobile." + componentName,
8445	iconEvent = "icon." + componentName,
8446	$document = wb.doc,
8447
8448	/*
8449	 * Plugin users can override these defaults by setting attributes on the html elements that the
8450	 * selector matches.
8451	 * For example, adding the attribute data-option1="false", will override option1 for that plugin instance.
8452	 */
8453	defaults = {
8454		filename: "favicon-mobile.png",
8455		path: null,
8456		rel: "apple-touch-icon",
8457		sizes: "57x57 72x72 114x114 144x144 150x150"
8458	},
8459
8460	/**
8461	 * @method init
8462	 * @param {jQuery Event} event Event that triggered the function call
8463	 */
8464	init = function( event ) {
8465
8466		// Start initialization
8467		// returns DOM object = proceed with init
8468		// returns undefined = do not proceed with init (e.g., already initialized)
8469		var elm = wb.init( event, componentName, selector, true ),
8470			$favicon, settings;
8471
8472		if ( elm ) {
8473			$favicon = $( elm );
8474
8475			// Merge default settings with overrides from the selected plugin element.
8476			settings = $.extend( {}, defaults, $favicon.data() );
8477
8478			$favicon.trigger( mobileEvent, settings );
8479
8480			// Identify that initialization has completed
8481			wb.ready( $document, componentName );
8482		}
8483	},
8484
8485	/**
8486	 * Adds, or updates, the mobile favicon on a page. Mobile favicons are identified by the
8487	 * `apple` prefix in the `<link>` elements rel attribute.
8488	 * @method mobile
8489	 * @param {DOM element} favicon Favicon element
8490	 * @param {jQuery Event} event Event that triggered this handler
8491	 * @param {Object} data Key-value data object passed with the event
8492	 */
8493	mobile = function( favicon, event, data ) {
8494		var faviconPath,
8495			faviconMobile = $( "link[rel^='apple']" ),
8496			isFaviconMobile = faviconMobile.length !== 0;
8497
8498		// Create the mobile favicon if it doesn't exist
8499		if ( !isFaviconMobile ) {
8500			var lnk = document.createElement( "link" );
8501			lnk.setAttribute( "rel", data.rel  );
8502			lnk.setAttribute( "sizes", data.sizes );
8503			lnk.setAttribute( "class", componentName );
8504
8505			document.head.appendChild( lnk );
8506
8507			faviconMobile = $( lnk );
8508		}
8509
8510		// Only add/update a mobile favicon that was created by the plugin
8511		if ( faviconMobile.hasClass( componentName ) ) {
8512			faviconPath = data.path !== null ? data.path : getPath( favicon.getAttribute( "href" ) );
8513			faviconMobile.attr( "href", faviconPath + data.filename );
8514
8515			if ( !isFaviconMobile ) {
8516				favicon.parentNode.insertBefore( faviconMobile[ 0 ], favicon );
8517			}
8518		}
8519
8520		$document.trigger( updatedEvent, [ "mobile" ] );
8521	},
8522
8523	/**
8524	 * Updates the the page's shortcut icon
8525	 * @method icon
8526	 * @param {DOM element} favicon Favicon element
8527	 * @param {jQuery Event} event Event that triggered this handler
8528	 * @param {Object} data Key-value data object passed with the event
8529	 */
8530	icon = function( favicon, event, data ) {
8531		var faviconPath = data.path !== null ? data.path : getPath( favicon.getAttribute( "href" ) );
8532		favicon.setAttribute( "href", faviconPath + data.filename );
8533
8534		$document.trigger( updatedEvent, [ "icon" ] );
8535	},
8536
8537	/**
8538	 * Given a full file path, returns the path without the filename
8539	 * @method getPath
8540	 * @param {string} filepath The full path to file, including filename
8541	 * @returns {string} The path to the file
8542	 */
8543	getPath = function( filepath ) {
8544		return filepath.substring( 0, filepath.lastIndexOf( "/" ) + 1 );
8545	};
8546
8547// Bind the init event
8548$document.on( "timerpoke.wb " + initEvent, selector, init );
8549
8550// Bind the mobile and icon events
8551$document.on( mobileEvent + " " + iconEvent, selector, function( event, data ) {
8552	var eventTarget = event.target;
8553
8554	// Filter out any events triggered by descendants
8555	if ( event.currentTarget === eventTarget ) {
8556		switch ( event.type ) {
8557		case "mobile":
8558			mobile( eventTarget, event, data );
8559			break;
8560
8561		case "icon":
8562			icon( eventTarget, event, data );
8563			break;
8564		}
8565	}
8566
8567	/*
8568	 * Since we are working with events we want to ensure that we are being passive about our control,
8569	 * so returning true allows for events to always continue
8570	 */
8571	return true;
8572} );
8573
8574// Add the timer poke to initialize the plugin
8575wb.add( selector );
8576
8577} )( jQuery, document, wb );
8578
8579/**
8580 * @title WET-BOEW Feeds
8581 * @overview Aggregates and displays entries from one or more Web feeds.
8582 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
8583 * @author @pjackson28
8584 */
8585( function( $, window, wb, undef ) {
8586"use strict";
8587
8588/*
8589 * Variable and function definitions.
8590 * These are global to the plugin - meaning that they will be initialized once per page,
8591 * not once per instance of plugin on the page. So, this is a good place to define
8592 * variables that are common to all instances of the plugin on a page.
8593 */
8594var componentName = "wb-feeds",
8595	selector = "." + componentName,
8596	feedLinkSelector = "li > a",
8597	initEvent = "wb-init" + selector,
8598	$document = wb.doc,
8599	patt = /\\u([\d\w]{4})/g,
8600
8601	/**
8602	 * @object Templates
8603	 * @properties {function}
8604	 * @param {object} requires a entry object of various ATOM based properties
8605	 * @returns {string} modified string with appropiate markup/format for a entry object
8606	 */
8607	Templates = {
8608
8609		/**
8610		 * [fickr template]
8611		 * @param  {entry object} data
8612		 * @return {string}	HTML string for creating a photowall effect
8613		 */
8614		flickr: function( data ) {
8615			var media = data.media.m,
8616				flickrData = {
8617					title: data.title,
8618					thumbnail: media.replace( "_m.", "_s." ),
8619					image: media.replace( "_m", "" ),
8620					description: data.description.replace( /^\s*<p>(.*?)<\/p>\s*<p>(.*?)<\/p>/i, "" )
8621				};
8622
8623			// due to CORS we cannot default to simple ajax pulls of the image. We have to inline the content box
8624			return "<li><a class='feed-flickr' href='#' data-flickr='" +
8625				wb.escapeAttribute( JSON.stringify( flickrData ) ) + "'><img src='" + flickrData.thumbnail + "' alt='" +
8626				wb.escapeAttribute( flickrData.title ) + "' title='" + wb.escapeAttribute( flickrData.title ) +
8627				"' class='img-responsive'/></a></li>";
8628		},
8629
8630		/**
8631		 * [Youtube template]
8632		 * @param  {entry object} data
8633		 * @return {string}	HTML string for creating a photowall effect
8634		 */
8635		youtube: function( data ) {
8636			var youtubeDate = {
8637				title: data.title,
8638				videoId: data.id
8639			};
8640
8641			// Due to CORS we cannot default to simple ajax pulls of the image. We have to inline the content box
8642			return "<li class='col-md-4 col-sm-6'><button class='btn btn-lnk feed-youtube' data-youtube='" +
8643				wb.escapeAttribute( JSON.stringify( youtubeDate ) ) + "'><img src='" +
8644				wb.pageUrlParts.protocol + "//img.youtube.com/vi/" + youtubeDate.videoId + "/mqdefault.jpg' alt='" +
8645				wb.escapeAttribute( youtubeDate.title ) + "' title='" + wb.escapeAttribute( youtubeDate.title ) +
8646				"' class='img-responsive' /></button></li>";
8647		},
8648
8649		/**
8650		 * [pinterest template]
8651		 * @param  {entry object}    data
8652		 * @return {string}    HTML string of formatted using a simple list / anchor view
8653		 */
8654		pinterest: function( data ) {
8655			var content = fromCharCode( data.description ).replace( /<a href="\/pin[^"]*"><img ([^>]*)><\/a>([^<]*)(<a .*)?/, "<a href='" +
8656				data.link + "'><img alt='' class='center-block' $1><br/>$2</a>$3" );
8657			return "<li class='media'>" + content +
8658			( data.publishedDate !== "" ? " <small class='small feeds-date'><time>" +
8659			wb.date.toDateISO( data.publishedDate, true ) + "</time></small>" : "" ) + "</li>";
8660		},
8661
8662		/**
8663		 * [generic template]
8664		 * @param  {entry object}	data
8665		 * @return {string}	HTML string of formatted using a simple list / anchor view
8666		 */
8667		generic: function( data ) {
8668			var title = data.title;
8669
8670			if ( typeof( title ) === "object" ) {
8671				if ( title.content ) {
8672					title = title.content;
8673				} else if ( title.type === "xhtml" && title.div ) {
8674					title = title.div.content;
8675				}
8676			}
8677			return "<li><a href='" + data.link + "'>" + title + "</a><br />" +
8678				( data.publishedDate !== "" ? " <small class='feeds-date'><time>" +
8679				wb.date.toDateISO( data.publishedDate, true ) + "</time></small>" : "" ) + "</li>";
8680		}
8681	},
8682
8683	/**
8684	 * Helper function that returns the string representaion of a unicode character
8685	 * @method decode
8686	 * @param  {regex} match  unicode pattern
8687	 * @param  {string} code  string where unicode is needed to be converted
8688	 * @return {string}	unicode string character
8689	 */
8690	decode = function( match, code ) {
8691		return String.fromCharCode( parseInt( code, 16 ) );
8692	},
8693
8694	/**
8695	 * Helper wrapper function that performs unicode decodes on a string
8696	 * @method fromCharCode
8697	 * @param  {string} s string to sanitize with escaped unicode characters
8698	 * @return {string}	sanitized string
8699	 */
8700	fromCharCode = function( s ) {
8701		return s.replace( patt, decode );
8702	},
8703
8704	/**
8705	 * Helper function that returns a class-based set limit on plugin instances
8706	 * @method getLimit
8707	 * @param {DOM object} elm The element to search for a class of the form limit-5
8708	 * @return {number} 0 if none found, which means the plugin default
8709	 */
8710	getLimit = function( elm ) {
8711		var count = elm.className.match( /\blimit-\d+/ );
8712		if ( !count ) {
8713			return 0;
8714		}
8715		return Number( count[ 0 ].replace( /limit-/i, "" ) );
8716	},
8717
8718	/**
8719	 * @method init
8720	 * @param {jQuery Event} event Event that triggered the function call
8721	 */
8722	init = function( event ) {
8723
8724		// Start initialization
8725		// returns DOM object = proceed with init
8726		// returns undefined = do not proceed with init (e.g., already initialized)
8727		var elm = wb.init( event, componentName, selector ),
8728			fetch, url, $content, limit, feeds, fType, last, i, callback, fElem, fIcon, youtubeData, $elm;
8729		if ( elm ) {
8730			$elm = $( elm );
8731			$content = $elm.find( ".feeds-cont" );
8732			limit = getLimit( elm );
8733			feeds = $content.find( feedLinkSelector );
8734			last = feeds.length - 1;
8735
8736			// Lets bind some variables to the node to ensure safe ajax thread counting
8737
8738			$content.data( "toProcess", feeds.length )
8739				.data( "feedLimit", limit )
8740				.data( "entries", [] );
8741
8742			for ( i = last; i !== -1; i -= 1 ) {
8743				fElem = feeds.eq( i );
8744				fIcon = fElem.find( "> img" );
8745
8746				fetch = {
8747					dataType: "jsonp",
8748					timeout: 10000
8749				};
8750
8751				if ( fElem.attr( "data-ajax" ) ) {
8752
8753					if ( fElem.attr( "href" ).indexOf( "flickr" ) !== -1 ) {
8754						fType =  "flickr";
8755						callback = "jsoncallback";
8756						$content.data( componentName + "-postProcess", [ ".wb-lbx" ] );
8757					} else {
8758						fType = "generic";
8759						callback = false;
8760						fetch.dataType = "json";
8761					}
8762
8763					// We need a Gallery so lets add another plugin
8764					// #TODO: Lightbox review for more abstraction we should not have to add a wb.add() for overlaying
8765					fetch.url = fElem.attr( "data-ajax" );
8766					fetch.jsonp = callback;
8767				} else if ( fElem.attr( "data-youtube" ) ) {
8768					youtubeData = wb.getData( fElem, "youtube" );
8769
8770					$content.data( componentName + "-postProcess", [ ".wb-lbx", ".wb-mltmd" ] );
8771
8772					if ( youtubeData.playlist ) {
8773						fElem.trigger( {
8774							type: "data-ready.wb-feeds",
8775							feedsData: youtubeData.playlist
8776						}, {
8777							feedType: "youtube",
8778							_content: $content
8779						} );
8780					}
8781
8782				} else {
8783
8784					url = fElem.attr( "href" );
8785					fetch.dataType = "xml";
8786					fetch.url = url;
8787
8788					// Let's bind the template to the Entries
8789					if ( url.indexOf( "pinterest.com" ) > -1  ) {
8790						fType = "pinterest";
8791					} else {
8792						fType = "generic";
8793					}
8794				}
8795
8796				fetch.jsonp = callback;
8797
8798				fetch.context = {
8799					fIcon: ( fIcon.length !== 0 ) ? fIcon.attr( "src" ) : "",
8800					feedType: fType,
8801					_content: $content
8802				};
8803
8804				fElem.trigger( {
8805					type: "ajax-fetch.wb",
8806					fetch: fetch
8807				} );
8808			}
8809		}
8810	},
8811
8812	/**
8813	 * Process Feed/JSON Entries for CORS Enabled
8814	 * @method corsEntry
8815	 */
8816	corsEntry = function( xmlDoc, limit ) {
8817		var entries = xmlDoc.getElementsByTagName( "entry" ).length,
8818			arr_entry = [],
8819			corsObj = {},
8820			jsonString = JSON.stringify( xmlToJson( xmlDoc ) ),
8821			jsonObj = JSON.parse( jsonString ),
8822			i, iCache;
8823		if ( limit > entries || limit === 0 || limit === null ) {
8824			limit = entries;
8825		}
8826		if ( entries === 1 ) {
8827			iCache = jsonObj.feed.entry;
8828			corsObj = {
8829				title: iCache.title[ "#text" ],
8830				link: ( iCache.link ? iCache.link[ "@attributes" ].href : iCache.id[ "#text" ] ),
8831				updated: iCache.updated[ "#text" ]
8832			};
8833			arr_entry.push( corsObj );
8834		} else if ( entries ) {
8835			for ( i = 0; i < limit; i++ ) {
8836				iCache = jsonObj.feed.entry[ i ];
8837				corsObj = {
8838					title: iCache.title[ "#text" ],
8839					link: ( iCache.link ? iCache.link[ "@attributes" ].href : iCache.id[ "#text" ] ),
8840					updated: iCache.updated[ "#text" ]
8841				};
8842				arr_entry.push( corsObj );
8843			}
8844		}
8845		return arr_entry;
8846	},
8847
8848	/**
8849	 * Process XML to JSON
8850	 * @method xmlToJson
8851	 * @param  {xml}
8852	 */
8853	xmlToJson = function( xml ) {
8854
8855		var obj = {},
8856			i, iCache, nodeName, old,
8857			xmlAttributes, xmlChildNodes,
8858			xmlNodeType = xml.nodeType;
8859
8860		if ( xmlNodeType === 1 ) {
8861			xmlAttributes = xml.attributes;
8862			if ( xmlAttributes.length ) {
8863				obj[ "@attributes" ] = {};
8864				for ( i = 0; i < xmlAttributes.length; i++ ) {
8865					iCache = xmlAttributes.item( i );
8866					obj[ "@attributes" ][ iCache.nodeName ] = iCache.nodeValue;
8867				}
8868			}
8869		} else if ( xmlNodeType === 3 ) {
8870			obj = xml.nodeValue;
8871		}
8872
8873		if ( xml.hasChildNodes() ) {
8874			xmlChildNodes = xml.childNodes;
8875			for ( i = 0; i < xmlChildNodes.length; i++ ) {
8876				iCache = xmlChildNodes.item( i );
8877				nodeName = iCache.nodeName;
8878				if ( typeof( obj[ nodeName ] ) === "undefined" ) {
8879					obj[ nodeName ] = xmlToJson( iCache );
8880				} else {
8881					if ( typeof( obj[ nodeName ].push ) === "undefined" ) {
8882						old = obj[ nodeName ];
8883						obj[ nodeName ] = [];
8884						obj[ nodeName ].push( old );
8885					}
8886					obj[ nodeName ].push( xmlToJson( iCache ) );
8887				}
8888			}
8889		}
8890		return obj;
8891	},
8892
8893	/**
8894	 * Process Feed/JSON Entries
8895	 * @method processEntries
8896	 * @param  {data} JSON formatted data to process
8897	 * @return {string} of HTML output
8898	 */
8899	processEntries = function( data ) {
8900		var items = data,
8901			entries = [],
8902			icon = this.fIcon,
8903			$content = this._content,
8904			toProcess = $content.data( "toProcess" ),
8905			i, len;
8906
8907		len = items.length;
8908		for ( i = 0; i !== len; i += 1 ) {
8909			items[ i ].fIcon =  icon;
8910
8911			if ( items[ i ].publishedDate === undef ) {
8912				items[ i ].publishedDate = ( items[ i ].published || items[ i ].pubDate || items[ i ].updated || "" );
8913			}
8914
8915			var link = items[ i ].link;
8916
8917			if ( link && link.href ) {
8918				items[ i ].link = link.href;
8919			}
8920
8921			entries.push( items[ i ] );
8922		}
8923
8924		// lets merge with latest entries
8925		entries = $.merge( entries, $content.data( "entries" ) );
8926
8927		if ( toProcess === 1 ) {
8928			parseEntries( entries, $content.data( "feedLimit" ), $content, this.feedType );
8929			return 0;
8930		}
8931
8932		toProcess -= 1;
8933		$content.data( {
8934			"toProcess": toProcess,
8935			"entries": entries
8936		} );
8937
8938		return toProcess;
8939	},
8940
8941	/**
8942	 * Parses the results from a JSON request and appends to an element
8943	 * @method parseEntries
8944	 * @param {object} entries Results from a JSON request.
8945	 * @param {integer} limit Limit on the number of results to append to the element.
8946	 * @param {jQuery DOM element} $elm Element to which the elements will be appended.
8947	 * @return {url} The URL for the JSON request
8948	 */
8949	parseEntries = function( entries, limit, $elm, feedtype ) {
8950		var cap = ( limit > 0 && limit < entries.length ? limit : entries.length ),
8951			result = "",
8952			compare = wb.date.compare,
8953			$details = $elm.closest( "details" ),
8954			activate = true,
8955			feedContSelector = ".feeds-cont",
8956			hasVisibilityHandler = "vis-handler",
8957			i, sorted, sortedEntry, $tabs;
8958
8959		if ( feedtype !== "youtube" ) {
8960			sorted = entries.sort( function( a, b ) {
8961				return compare( b.publishedDate, a.publishedDate );
8962			} );
8963		} else {
8964			sorted = entries;
8965		}
8966
8967		for ( i = 0; i !== cap; i += 1 ) {
8968			sortedEntry = sorted[ i ];
8969			result += Templates[ feedtype ]( sortedEntry );
8970		}
8971		$elm.data( componentName + "-result", result );
8972
8973		// Check to see if feed should be activated (only if visible)
8974		// and add handler to determine visibility
8975		if ( $details.length !== 0 ) {
8976			if ( $details.attr( "role" ) === "tabpanel" ) {
8977				if ( $details.attr( "aria-hidden" ) === "true" ) {
8978					activate = false;
8979					$elm.empty().addClass( "waiting" );
8980					$tabs = $details.closest( ".wb-tabs" );
8981					if ( !$tabs.hasClass( hasVisibilityHandler ) ) {
8982						$tabs
8983							.on( "wb-updated.wb-tabs", function( event, $newPanel ) {
8984								var $feedCont = $newPanel.find( feedContSelector );
8985								if ( !$feedCont.hasClass( "feed-active" ) ) {
8986									activateFeed( $feedCont );
8987								}
8988							} )
8989							.addClass( hasVisibilityHandler );
8990					}
8991				}
8992			} else if ( !$details.attr( "open" ) ) {
8993				activate = false;
8994				$elm.empty().addClass( "waiting" );
8995				$details
8996					.children( "summary" )
8997					.on( "click.wb-feeds", function( event ) {
8998						var $summary = $( event.currentTarget ).off( "click.wb-feeds" );
8999						activateFeed( $summary.parent().find( feedContSelector ) );
9000					} );
9001			}
9002		}
9003
9004		if ( activate ) {
9005			activateFeed( $elm );
9006		}
9007
9008		return true;
9009	},
9010
9011	/**
9012	 * Activates feed results view
9013	 * @method activateFeed
9014	 * @param = {jQuery object} $elm Feed container
9015	 */
9016	activateFeed = function( $elm ) {
9017		var result = $elm.data( componentName + "-result" ),
9018			postProcess = $elm.data( componentName + "-postProcess" ),
9019			i, postProcessSelector;
9020
9021		if ( !result ) {
9022			return;
9023		}
9024
9025		$elm.empty()
9026			.removeClass( "waiting" )
9027			.addClass( "feed-active" )
9028			.append( result );
9029
9030		if ( postProcess ) {
9031			for ( i = postProcess.length - 1; i !== -1; i -= 1 ) {
9032				postProcessSelector = postProcess[ i ];
9033				$elm.find( postProcessSelector )
9034					.trigger( "wb-init" + postProcessSelector );
9035			}
9036		}
9037
9038		// Identify that the feed has now been displayed
9039		$elm.trigger( "wb-feed-ready" + selector );
9040	};
9041
9042$document.on( "ajax-fetched.wb data-ready.wb-feeds", selector + " " + feedLinkSelector, function( event, context ) {
9043	var eventTarget = event.target,
9044		data, response, responseRaw,
9045		$emlRss, limit, results;
9046
9047	// Filter out any events triggered by descendants
9048	if ( event.currentTarget === eventTarget ) {
9049		$emlRss = $( eventTarget ).parentsUntil( selector ).parent();
9050		switch ( event.type ) {
9051		case "ajax-fetched":
9052			responseRaw = event.fetch.response;
9053			if ( typeof responseRaw === "string" ) {
9054				response = JSON.parse( responseRaw ); // Assuming we have fetch a JSON document, try to parse it.
9055			} else {
9056				response = responseRaw.get( 0 ); // fetched an HTML or XML document which has been parsed by jQuery and sanitized by DomPurify
9057			}
9058			if ( response.documentElement ) {
9059				limit = getLimit( $emlRss[ Object.keys( $emlRss )[ 0 ] ] );
9060				data = corsEntry( response, limit );
9061			} else if ( response.query ) {
9062				results = response.query.results;
9063				if ( !results ) {
9064					data = results.item; // Flicker feeds
9065					if ( !Array.isArray( data ) ) {
9066						data = [ data ];
9067					}
9068				} else {
9069					data = [];
9070				}
9071			} else {
9072				data = ( response.responseData ) ? response.responseData.feed.entries : response.items || response.feed.entry;
9073			}
9074			break;
9075		default:
9076			data = event.feedsData;
9077		}
9078
9079		// Identify that initialization has completed
9080		// if there are no entries left to process
9081		if ( processEntries.apply( context, [ data ] ) === 0 ) {
9082			wb.ready( $( eventTarget ).closest( selector ), componentName );
9083		}
9084	}
9085} );
9086
9087$document.on( "click", selector + " .feed-youtube", function( event ) {
9088	var youTubeOverlaySelector  = "#wb-feeds-youtube-lbx",
9089		$youTubeOverlay = $( youTubeOverlaySelector ),
9090		youtubeData = wb.getData( event.currentTarget, "youtube" ),
9091		videoUrl = wb.pageUrlParts.protocol + "//www.youtube.com/watch?v=" + youtubeData.videoId,
9092		videoSource = "<figure class='wb-mltmd'><video title='" + youtubeData.title + "'>" +
9093			"<source type='video/youtube' src='" + videoUrl + "'></source>" +
9094			"</video><figcaption><p>" +  youtubeData.title + "</p>" +
9095			"</figcaption></figure>";
9096
9097	if ( $youTubeOverlay.length === 0 ) {
9098		$youTubeOverlay = $( "<section id='wb-feeds-youtube-lbx' class='mfp-hide modal-dialog modal-content overlay-def'>" +
9099			"<header class='modal-header'><h2 class='modal-title'>" + youtubeData.title + "</h2></header>" +
9100			"<div class='modal-body'>" +
9101			videoSource +
9102			"</div></section>" ).insertAfter( "main" );
9103	} else {
9104
9105		//Modify lightbox
9106		$youTubeOverlay.find( ".modal-title" ).text( youtubeData.title );
9107		$youTubeOverlay.find( ".modal-body" ).empty().append( videoSource );
9108	}
9109
9110	//Temporary fix until lightbox auto initialize the multimedia player
9111	$youTubeOverlay.find( ".wb-mltmd" ).trigger( "wb-init.wb-mltmd" );
9112
9113	$( document ).trigger( "open.wb-lbx", [ {
9114		src: youTubeOverlaySelector,
9115		type: "inline"
9116	} ] );
9117} );
9118
9119$document.on( "click", selector + " .feed-flickr", function( event ) {
9120	var flickrOverlaySelector  = "#wb-feeds-flick-lbx",
9121		$flickrOverlay = $( flickrOverlaySelector ),
9122		flickrData = wb.getData( event.currentTarget, "flickr" ),
9123		body = "<img src='" + flickrData.image + "' class='thumbnail center-block' alt='" + flickrData.title + "' /><span>" +
9124			flickrData.description + "</span>";
9125
9126	if ( $flickrOverlay.length === 0 ) {
9127		$flickrOverlay = $( "<section id='wb-feeds-flick-lbx' class='mfp-hide modal-dialog modal-content overlay-def'>" +
9128			"<header class='modal-header'><h2 class='modal-title'>" + flickrData.title + "</h2></header>" +
9129			"<div class='modal-body'>" + body + "</div></section>" ).insertAfter( "main" );
9130	} else {
9131
9132		//Modify lightbox
9133		$flickrOverlay.find( ".modal-title" ).text( flickrData.title );
9134		$flickrOverlay.find( ".modal-body" ).empty().append( body );
9135	}
9136
9137	$( document ).trigger( "open.wb-lbx", [ {
9138		src: flickrOverlaySelector,
9139		type: "inline"
9140	} ] );
9141} );
9142
9143// Bind the init event to the plugin
9144$document.on( "timerpoke.wb " + initEvent, selector, init );
9145
9146// Add the timer poke to initialize the plugin
9147wb.add( selector );
9148
9149} )( jQuery, window, wb );
9150
9151( function( $, window, wb ) {
9152"use strict";
9153
9154var componentName = "wb-filter",
9155	selector = "." + componentName,
9156	initEvent = "wb-init" + selector,
9157	$document = wb.doc,
9158	filterClass = "wb-fltr-out",
9159	tagFilterClass = "wb-tgfltr-out",
9160	notFilterClassSel = ":not(." + filterClass + "):not(." + tagFilterClass + ")",
9161	inputClass = "wb-fltr-inpt",
9162	dtNameFltrArea = "wbfltrid",
9163	selectorInput = "." + inputClass,
9164	defaults = {
9165		std: {
9166			selector: "li"
9167		},
9168		grp: {
9169			selector: "li",
9170			section: ">section"
9171		},
9172		tbl: {
9173			selector: "tr",
9174			section: ">tbody"
9175		},
9176		tblgrp: {
9177			selector: " th:not([scope])" + notFilterClassSel,
9178			hdnparentuntil: "tbody",
9179			section: ">tbody"
9180		}
9181	},
9182	i18n, i18nText,
9183	wait,
9184
9185	init = function( event ) {
9186		var elm = wb.init( event, componentName, selector ),
9187			$elm, elmTagName, filterUI, prependUI,
9188			settings, setDefault,
9189			itemsObserver,
9190			inptId, totalEntries,
9191			secSelector,
9192			uiTemplate, uiInpt, uiInfo,
9193			uiNbItems, uiTotal, uiInfoID;
9194
9195		if ( elm ) {
9196			$elm = $( elm );
9197			elmTagName = elm.nodeName;
9198			uiInfoID = elm.id + "-info";
9199
9200			if ( [ "DIV", "SECTION", "ARTICLE" ].indexOf( elmTagName ) >= 0 ) {
9201				setDefault = defaults.grp;
9202				prependUI = true;
9203			} else if ( elmTagName === "TABLE" ) {
9204				if ( $elm.find( "tbody" ).length > 1 ) {
9205					setDefault = defaults.tblgrp;
9206				} else {
9207					setDefault = defaults.tbl;
9208				}
9209			} else {
9210				setDefault = defaults.std;
9211			}
9212
9213			settings = $.extend( true, {}, setDefault, window[ componentName ], wb.getData( $elm, componentName ) );
9214			$elm.data( settings );
9215
9216			if ( !i18nText ) {
9217				i18n = wb.i18n;
9218				i18nText = {
9219					filter_label: i18n( "fltr-lbl" ),
9220					fltr_info: i18n( "fltr-info" )
9221				};
9222			}
9223
9224			Modernizr.addTest( "stringnormalize", "normalize" in String );
9225			Modernizr.load( {
9226				test: Modernizr.stringnormalize,
9227				nope: [
9228					"site!deps/unorm" + wb.getMode() + ".js"
9229				]
9230			} );
9231
9232			if ( !elm.id ) {
9233				elm.id = wb.getId();
9234			}
9235
9236			if ( settings.uiTemplate ) {
9237				uiTemplate = document.querySelector( settings.uiTemplate );
9238				uiInpt = uiTemplate.querySelector( "input[type=search]" );
9239
9240				if ( uiInpt ) {
9241					uiInfo = uiTemplate.querySelector( ".wb-fltr-info" );
9242
9243					uiInpt.classList.add( inputClass );
9244					uiInpt.setAttribute( "data-" + dtNameFltrArea, elm.id );
9245					uiInpt.setAttribute( "aria-controls", elm.id );
9246
9247					if ( uiInfo ) {
9248						uiInfoID = uiInfo.id || uiInfoID;
9249						uiInfo.id = uiInfoID;
9250						uiInfo.setAttribute( "role", "status" );
9251					}
9252				} else {
9253					console.error( componentName + ": " + "an <input type=\"search\"> is required in your UI template." );
9254				}
9255
9256				if ( settings.source ) {
9257					console.warn( componentName + ": " + "the 'source' option is not compatible with the 'uiTemplate' option. If both options are defined, only 'uiTemplate' will be registered." );
9258				}
9259			} else {
9260				inptId = elm.id + "-inpt";
9261				filterUI = $( "<div class=\"input-group\">" +
9262					"<label for=\"" + inptId + "\" class=\"input-group-addon\"><span class=\"glyphicon glyphicon-filter\" aria-hidden=\"true\"></span> " + i18nText.filter_label + "</label>" +
9263					"<input id=\"" + inptId + "\" class=\"form-control " + inputClass + "\" data-" + dtNameFltrArea + "=\"" + elm.id + "\" aria-controls=\"" + elm.id + "\" type=\"search\">" +
9264					"</div>" +
9265					"<p role=\"status\" id=\"" + uiInfoID + "\">" + i18nText.fltr_info + "</p>" );
9266
9267				if ( settings.source ) {
9268					$( settings.source ).prepend( filterUI );
9269				} else if ( prependUI ) {
9270					$elm.prepend( filterUI );
9271				} else {
9272					$elm.before( filterUI );
9273				}
9274			}
9275
9276			secSelector = ( settings.section || "" ) + " ";
9277			totalEntries = $elm.find( secSelector + settings.selector ).length;
9278			uiNbItems = document.querySelector( "#" + uiInfoID + " [data-nbitem]" );
9279			uiTotal = document.querySelector( "#" + uiInfoID + " [data-total]" );
9280
9281			if ( uiNbItems ) {
9282				uiNbItems.textContent = totalEntries;
9283
9284				itemsObserver = new MutationObserver( function() {
9285					uiNbItems.textContent = $elm.find( secSelector + settings.selector + notFilterClassSel ).length;
9286				} );
9287
9288				itemsObserver.observe( elm, { attributes: true, subtree: true } );
9289			}
9290
9291			if ( uiTotal ) {
9292				uiTotal.textContent = totalEntries;
9293			}
9294
9295			wb.ready( $elm, componentName );
9296		}
9297	},
9298
9299	/*
9300	 * Takes in the text from the filter box
9301	 * Returns:
9302	 *  An array of search words
9303	 *      Special characters are escaped
9304	 *      Double and single quotes removed
9305	 */
9306	filterQueryParser = function( filter ) {
9307
9308		// Pattern to seperate the filter text into "words"
9309		var pattern = /[^\s"]+|"([^"]*)"/gi;
9310
9311		// Make strings safe again for regex
9312		// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
9313		filter = filter.replace( /[.*+?^${}()|[\]\\]/g, "\\$&" );
9314
9315		// Apply the word match pattern and return
9316		return filter.match( pattern );
9317	},
9318
9319	/**
9320	 * Build the Regular Expression that we are going
9321	 * to use to filter content
9322	 * This involves identifying the type of search being
9323	 * applied and then breaking up the search string into
9324	 * words
9325	 */
9326	buildSearchFilterRegularExp =  function( filterType, filter ) {
9327
9328		var words, wordRegExFilter = filter,
9329			i, i_len;
9330
9331		switch ( filterType ) {
9332
9333		case "and":
9334			words = filterQueryParser( filter );
9335			if ( words ) {
9336				wordRegExFilter = ".*";
9337				i_len = words.length;
9338				for ( i = 0; i < i_len; i++ ) {
9339					wordRegExFilter = wordRegExFilter + ( "(?=.*" + words[ i ] + ")" );
9340				}
9341			}
9342			break;
9343
9344		case "or": // If one word fall back on default
9345			words = filterQueryParser( filter );
9346			if ( words ) {
9347				wordRegExFilter =  words.join( "|" );
9348			}
9349			break;
9350
9351		default:
9352			break;
9353
9354		}
9355
9356		return new RegExp( wordRegExFilter, "i" );
9357
9358	},
9359
9360	filter = function( $field, $elm, settings ) {
9361		var unAccent = function( str ) {
9362				return str.normalize( "NFD" ).replace( /[\u0300-\u036f]/g, "" );
9363			},
9364			filter = unAccent( $field.val().trim() ),
9365			fCallBack = settings.filterCallback,
9366			secSelector = ( settings.section || "" )  + " ",
9367			hndParentSelector = settings.hdnparentuntil,
9368			$items = $elm.find( secSelector + settings.selector ),
9369			itemsLength = $items.length,
9370			i, $item, text, searchFilterRegularExp;
9371
9372		$elm.find( "." + filterClass ).removeClass( filterClass );
9373
9374		searchFilterRegularExp = buildSearchFilterRegularExp( settings.filterType, filter );
9375
9376		for ( i = 0; i < itemsLength; i += 1 ) {
9377			$item = $items.eq( i );
9378			text = unAccent( $item.text() );
9379
9380			if ( !searchFilterRegularExp.test( text ) ) {
9381				if ( hndParentSelector ) {
9382					$item = $item.parentsUntil( hndParentSelector );
9383				}
9384				$item.addClass( filterClass );
9385			}
9386		}
9387
9388		if ( !fCallBack || typeof fCallBack !== "function"  ) {
9389			fCallBack = filterCallback;
9390		}
9391		fCallBack.apply( this, arguments );
9392
9393		$elm.trigger( "wb-contentupdated" );
9394	},
9395	filterCallback = function( $field, $elm, settings ) {
9396		var $sections =	$elm.find( settings.section ),
9397			sectionsLength = $sections.length,
9398			fndSelector = notFilterClassSel + settings.selector,
9399			s, $section;
9400
9401		for ( s = 0; s < sectionsLength; s += 1 ) {
9402			$section = $sections.eq( s );
9403			if ( $section.find( fndSelector ).length === 0 ) {
9404				$section.addClass( filterClass );
9405			}
9406		}
9407	};
9408
9409$document.on( "keyup", selectorInput, function( event ) {
9410	var target = event.target,
9411		$input = $( target ),
9412		$elm = $( "#" + $input.data( dtNameFltrArea ) );
9413
9414	if ( wait ) {
9415		clearTimeout( wait );
9416	}
9417	wait = setTimeout( filter.bind( this, $input, $elm, $elm.data() ), 250 );
9418
9419} );
9420
9421$document.on( "timerpoke.wb " + initEvent, selector, init );
9422
9423wb.add( selector );
9424} )( jQuery, window, wb );
9425
9426/**
9427 * @title WET-BOEW Footnotes
9428 * @overview Provides a consistent, accessible way of handling footnotes across websites.
9429 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
9430 * @author @EricDunsworth
9431 */
9432( function( $, window, wb ) {
9433"use strict";
9434
9435/*
9436 * Variable and function definitions.
9437 * These are global to the plugin - meaning that they will be initialized once per page,
9438 * not once per instance of plugin on the page. So, this is a good place to define
9439 * variables that are common to all instances of the plugin on a page.
9440 */
9441var componentName = "wb-fnote",
9442	selector = "." + componentName,
9443	modFlag = "data-" + componentName,
9444	initEvent = "wb-init" + selector,
9445	setFocusEvent = "setfocus.wb",
9446	$document = wb.doc,
9447
9448	/**
9449	 * @method init
9450	 * @param {jQuery Event} event Event that triggered the function call
9451	 */
9452	init = function( event ) {
9453
9454		// Start initialization
9455		// returns DOM object = proceed with init
9456		// returns undefined = do not proceed with init (e.g., already initialized)
9457		var elm = wb.init( event, componentName, selector ),
9458			$elm, footnoteDd, footnoteDt, i, len, dd, dt;
9459
9460		if ( elm ) {
9461			$elm = $( elm );
9462			footnoteDd = elm.getElementsByTagName( "dd" );
9463			footnoteDt = elm.getElementsByTagName( "dt" );
9464
9465			// Set initial event handlers for return to referrer links
9466			len = footnoteDd.length;
9467			for ( i = 0; i !== len; i += 1 ) {
9468				dd = footnoteDd[ i ];
9469				dt = footnoteDt[ i ];
9470				dd.setAttribute( "tabindex", "-1" );
9471				dt.id = dd.id + "-dt";
9472			}
9473
9474			// Remove "first/premier/etc"-style text from certain footnote return links (via the child spans that hold those bits of text)
9475			$elm.find( "dd p.fn-rtn a span span" ).remove();
9476
9477			// Listen for footnote reference links that get clicked
9478			$document.on( "click", "main :not(" + selector + ") sup a.fn-lnk", function( event ) {
9479				var eventTarget = event.target,
9480					which = event.which,
9481					refId, $refLinkDest;
9482
9483				// Ignore middle/right mouse button
9484				if ( !which || which === 1 ) {
9485					refId = "#" + wb.jqEscape( eventTarget.getAttribute( "href" ).substring( 1 ) );
9486					$refLinkDest = $document.find( refId );
9487
9488					$refLinkDest.find( "p.fn-rtn a" )
9489						.attr( "href", "#" + eventTarget.parentNode.id )
9490						.attr( modFlag, true );
9491
9492					// Assign focus to $refLinkDest
9493					$refLinkDest.trigger( setFocusEvent );
9494					return false;
9495				}
9496			} );
9497
9498			// Listen for footnote return links that get clicked
9499			$document.on( "click", selector + " dd p.fn-rtn a", function( event ) {
9500				var which = event.which,
9501					$elmTarget = $( event.target ),
9502					ref,
9503					refId,
9504					refIdSrc,
9505					refIdDashIdx,
9506					searchRefId;
9507
9508				// Ignore middle/right mouse button
9509				if ( !which || which === 1 ) {
9510					ref = $elmTarget.attr( "href" );
9511
9512					// Focus on associated referrer link (if the return link points to an ID)
9513					if ( ref.charAt( 0 ) === "#" ) {
9514						refId = wb.jqEscape( ref.substring( 1 ) );
9515
9516						// When first clicked, ensure we send the user on the first instance when the id follow the recommend pattern
9517						refIdDashIdx = refId.indexOf( "-" );
9518						if ( refIdDashIdx !== -1 && !$elmTarget.attr( modFlag ) ) {
9519							searchRefId = refId.substring( 0, refIdDashIdx + 1 );
9520							refIdSrc = wb.jqEscape( $( "sup[id^='" + searchRefId + "']:first()" ).attr( "id" ) );
9521							if ( !refIdSrc || refId !== refIdSrc ) {
9522								console.warn( componentName + " - Relink first reference of " + ref + " for #" + refIdSrc );
9523								refId = refIdSrc;
9524								$elmTarget
9525									.attr( "href", "#" + refId )
9526									.attr( modFlag, true );
9527							}
9528						}
9529
9530						// Assign focus to the link
9531						$document.find( "#" + refId + " a" ).trigger( setFocusEvent );
9532						return false;
9533					}
9534				}
9535			} );
9536
9537			// Identify that initialization has completed
9538			wb.ready( $elm, componentName );
9539		}
9540	};
9541
9542// Bind the init event of the plugin
9543$document.on( "timerpoke.wb " + initEvent, selector, init );
9544
9545// Add the timer poke to initialize the plugin
9546wb.add( selector );
9547
9548} )( jQuery, window, wb );
9549
9550/**
9551 * @title WET-BOEW Form validation
9552 * @overview Provides generic validation and error message handling for Web forms.
9553 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
9554 * @author @pjackson28
9555 */
9556( function( $, window, document, wb ) {
9557"use strict";
9558
9559/*
9560* Variable and function definitions.
9561* These are global to the plugin - meaning that they will be initialized once per page,
9562* not once per instance of plugin on the page. So, this is a good place to define
9563* variables that are common to all instances of the plugin on a page.
9564*/
9565var componentName = "wb-frmvld",
9566	selector = "." + componentName,
9567	initEvent = "wb-init" + selector,
9568	setFocusEvent = "setfocus.wb",
9569	$document = wb.doc,
9570	idCount = 0,
9571	i18n, i18nText,
9572
9573	defaults = {
9574		hdLvl: "h2",
9575		ignore: ":hidden"
9576	},
9577
9578	/**
9579	 * @method init
9580	 * @param {jQuery Event} event Event that triggered the function call
9581	 */
9582	init = function( event ) {
9583
9584		// Start initialization
9585		// returns DOM object = proceed with init
9586		// returns undefined = do not proceed with init (e.g., already initialized)
9587		var eventTarget = wb.init( event, componentName, selector ),
9588			elmId, modeJS;
9589
9590		if ( eventTarget ) {
9591			elmId = eventTarget.id;
9592
9593			// Ensure there is a unique id on the element
9594			if ( !elmId ) {
9595				elmId = componentName + "-id-" + idCount;
9596				idCount += 1;
9597				eventTarget.id = elmId;
9598			}
9599
9600			// Read the selector node for parameters
9601			modeJS = wb.getMode() + ".js";
9602
9603			// Only initialize the i18nText once
9604			if ( !i18nText ) {
9605				i18n = wb.i18n;
9606				i18nText = {
9607					colon: i18n( "colon" ),
9608					hyphen: i18n( "hyphen" ),
9609					error: i18n( "err" ),
9610					errorFound: i18n( "err-fnd" ),
9611					errorsFound: i18n( "errs-fnd" ),
9612					formNotSubmitted: i18n( "frm-nosubmit" ),
9613					errorCorrect: i18n( "err-correct" )
9614				};
9615			}
9616
9617			Modernizr.load( {
9618				load: [ "site!deps/jquery.validate" + modeJS ],
9619				testReady: function() {
9620					return ( $.validator );
9621				},
9622				complete: function() {
9623					Modernizr.load( {
9624
9625						// For loading multiple dependencies
9626						load: [ "site!deps/additional-methods" + modeJS ],
9627						testReady: function() {
9628							return ( $.validator.methods.bic );
9629						},
9630						complete: function() {
9631							var $elm = $( "#" + elmId ),
9632								$form = $elm.find( "form" ),
9633								formDOM = $form.get( 0 ),
9634								formId = $form.attr( "id" ),
9635								labels = formDOM.getElementsByTagName( "label" ),
9636								submitted = false,
9637								errorFormId = "errors-" + ( !formId ? "default" : formId ),
9638								settings = $.extend(
9639									true,
9640									{},
9641									defaults,
9642									window[ componentName ],
9643									wb.getData( $elm, componentName )
9644								),
9645								summaryHeading = settings.hdLvl,
9646								i, len, validator;
9647
9648							if ( wb.lang === "fr" ) {
9649
9650								// alphanumeric regex is changed to allow french characters;
9651								$.validator.addMethod( "alphanumeric", function( value, element ) {
9652									return this.optional( element ) || /^[a-zàâçéèêëîïôûùüÿæœ0-9_]+$/i.test( value );
9653								}, "Letters, numbers, and underscores only please." );
9654
9655								// error french text is adjusted to remove the word "spaces"
9656								$.extend( $.validator.messages, {
9657									alphanumeric: "Veuillez fournir seulement des lettres, nombres et soulignages."
9658								} );
9659							}
9660
9661							// Append the aria-live region (for provide message updates to screen readers)
9662							$elm.append( "<div class='arialive wb-inv' aria-live='polite' aria-relevant='all'></div>" );
9663
9664							// Add space to the end of the labels (so separation between label and error when CSS turned off)
9665							len = labels.length;
9666							for ( i = 0; i !== len; i += 1 ) {
9667								labels[ i ].innerHTML += " ";
9668							}
9669
9670							// Hide "required" label text in older forms from screen readers
9671							// Prevents redundant "required" announcements on semantically-required fields whose labels mention they're required
9672							$form.find( "strong.required:not([aria-hidden='true'])" ).each( function() {
9673								const $requiredText = $( this ),
9674									$label = $requiredText.closest( "label" ),
9675									fieldId = $label.attr( "for" );
9676								let $field = fieldId ? $( "#" + fieldId ) : $label.find( ":input" ).first();
9677
9678								// If the label's field has yet to be found, look for fields that refer to the label via aria-labelledby
9679								if ( !$field.length ) {
9680									const labelId = $label.attr( "id" ) || $requiredText.closest( "id" );
9681									$field = $form.find( "[aria-labelledby~='" + labelId + "']:input" ).first();
9682								}
9683
9684								// Hide the "required" text if its field is semantically-required
9685								if ( $field.is( "[required], [aria-required='true']" ) ) {
9686									$requiredText.attr( "aria-hidden", "true" );
9687								}
9688							} );
9689
9690							// The jQuery validation plug-in in action
9691							validator = $form.validate( {
9692								meta: "validate",
9693								focusInvalid: false,
9694								ignore: settings.ignore,
9695
9696								// Set the element which will wrap the inline error messages
9697								errorElement: "strong",
9698
9699								// Location for the inline error messages
9700								// In this case we will place them in the associated label element
9701								errorPlacement: function( $error, $element ) {
9702									var type = $element.attr( "type" ),
9703										group = $element.attr( "data-rule-require_from_group" ),
9704										$fieldset, $legend;
9705
9706									$error.data( "element-id", $element.attr( "id" ) );
9707									if ( type ) {
9708										type = type.toLowerCase();
9709										if ( type === "radio" || type === "checkbox" ) {
9710											$fieldset = $element.closest( "fieldset" );
9711											if ( $fieldset.length !== 0 ) {
9712												$legend = $fieldset.find( "legend" ).first();
9713												if ( $legend.length !== 0 && $fieldset.find( "input[name='" + $element.attr( "name" ) + "']" ) !== 1 ) {
9714													$error.appendTo( $legend );
9715													return;
9716												}
9717											}
9718										}
9719									}
9720
9721									if ( group ) {
9722										$fieldset = $element.closest( "fieldset" );
9723										if ( $fieldset.length !== 0 ) {
9724											$legend = $fieldset.find( "legend" ).first();
9725											if ( $legend.length !== 0 && $fieldset.find( "input[name='" + $element.attr( "name" ) + "']" ) !== 1 ) {
9726												var $strong = $legend.find( "strong.error" ),
9727													id = $legend.attr( "id" );
9728
9729												if ( $strong.length > 0 ) {
9730													$strong.remove();
9731												}
9732
9733												if ( !id ) {
9734													id = "required-group-" + idCount;
9735													idCount += 1;
9736
9737													$legend.attr( "id", id );
9738												}
9739
9740												$error.data( "element-id", id );
9741												$error.attr( "id", id );
9742												$error.appendTo( $legend );
9743
9744												return;
9745											}
9746										}
9747									}
9748
9749									//Std If we have a label and the input field is inside the label
9750									// need to add a css-implicite-input
9751									if ( $form.find( "label" ).find( ".wb-server-error + input.css-implicite-input[name='" + $element.attr( "name" ) + "']" ).length > 0 ) {
9752										$error.insertBefore( $form.find( ".wb-server-error + input.css-implicite-input[name='" + $element.attr( "name" ) + "']" ) );
9753										return;
9754									}
9755
9756									$error.appendTo( $form.find( "label[for='" + $element.attr( "id" ) + "']" ) );
9757									return;
9758								},
9759
9760								// Create our error summary that will appear before the form
9761								showErrors: function( errorMap ) {
9762									this.defaultShowErrors();
9763									var $errors = $form.find( ".wb-server-error, strong.error" ).filter( ":not(:hidden)" ),
9764										$errorfields = $form.find( "input.error, select.error, textarea.error" ),
9765										prefixStart = "<span class='prefix'>" + i18nText.error + "&#160;",
9766										prefixEnd = i18nText.colon + " </span>",
9767										separator = i18nText.hyphen,
9768										ariaLive = $form.closest( ".wb-frmvld" ).find( ".arialive" )[ 0 ],
9769										$summaryContainer, summary, key, i, len, $error, prefix, $fieldName, $f
9769ieldset, label, labelString;
9770
9771									// Correct the colouring of fields that are no longer invalid
9772									$form
9773										.find( ".has-error [aria-invalid=false]" )
9774										.closest( ".has-error" )
9775										.removeClass( "has-error" );
9776
9777									if ( $errors.length !== 0 ) {
9778
9779										// Post process
9780										summary = "<" + summaryHeading + ">" +
9781											i18nText.formNotSubmitted + $errors.length +
9782											(
9783												$errors.length !== 1 ?
9784													i18nText.errorsFound :
9785													i18nText.errorFound
9786											) + "</" + summaryHeading + "><ul>";
9787										$errorfields
9788											.closest( ".form-group" )
9789											.addClass( "has-error" );
9790										len = $errors.length;
9791										for ( i = 0; i !== len; i += 1 ) {
9792											$error = $errors.eq( i );
9793											prefix = prefixStart + ( i + 1 ) + prefixEnd;
9794											$fieldName = $error.closest( "label" ).find( ".field-name" );
9795
9796											// Try to find the field name in the legend (if one exists)
9797											if ( $fieldName.length === 0 ) {
9798												$fieldset = $error.closest( "fieldset" );
9799												if ( $fieldset.length !== 0 ) {
9800													$fieldName = $fieldset.find( "legend .field-name" );
9801												}
9802											}
9803
9804											$error.find( "span.prefix" ).detach();
9805
9806											//Verify if it is a wb-server-error
9807											if ( $errors[ i ].classList.contains( "wb-server-error" ) ) {
9808												if ( $errors[ i ].id ) {
9809													var myParent = document.getElementById( $errors[ i ].id ).parentElement;
9810													if ( myParent === null ) {
9811														summary += "<li><a>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9812													} else {
9813														if ( myParent.hasAttribute( "for" ) && myParent.getAttribute( "for" ).length > 0 ) {
9814															summary += "<li><a href='#" + myParent.getAttribute( "for" ) + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9815														} else {
9816															if ( myParent.getElementsByTagName( "input" )[ 0 ] && myParent.getElementsByTagName( "input" )[ 0 ].name.length > 0 ) {
9817																summary += "<li><a href='#" + myParent.getElementsByTagName( "input" )[ 0 ].id + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9818															} else {
9819																if ( myParent.tagName === ( "LEGEND" ) && ( myParent.parentElement.getElementsByTagName( "input" )[ 0 ].type === "checkbox" || myParent.parentElement.getElementsByTagName( "input" )[ 0 ].type === "radio" && myParent.parentElement.getElementsByTagName( "input" )[ 0 ].name.length > 0 ) ) {
9820																	summary += "<li><a href='#" + myParent.parentElement.getElementsByTagName( "input" )[ 0 ].id + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9821																} else {
9822																	summary += "<li><a>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>";
9823																}
9824															}
9825														}
9826													}
9827													$error.html( "<strong>" + prefix + $error.text() + "</strong>" );
9828												}
9829											} else {
9830												summary += "<li><a href='#" + $error.data( "element-id" ) + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + "</a></li>";
9831												$error.html( "<span class='label label-danger'>" + prefix + $error.text() + "</span>" );
9832											}
9833										}
9834										summary += "</ul>";
9835
9836										if ( !submitted ) {
9837
9838											// Update the aria-live region as necessary
9839											i = 0;
9840											for ( key in errorMap ) {
9841												if ( Object.prototype.hasOwnProperty.call( errorMap, key ) ) {
9842													i += 1;
9843													break;
9844												}
9845											}
9846											if ( i !== 0 ) {
9847												len = $errors.length;
9848												for ( i = 0; i !== len; i += 1 ) {
9849													label = $errors[ i ].parentNode;
9850													if ( label.getAttribute( "for" ) === key ) {
9851														labelString = label.innerHTML;
9852														if ( labelString !== ariaLive.innerHTML ) {
9853															ariaLive.innerHTML = labelString;
9854														}
9855														break;
9856													}
9857												}
9858											} else if ( ariaLive.innerHTML.length !== 0 ) {
9859												ariaLive.innerHTML = "";
9860											}
9861										}
9862
9863										// Delay updating the summary container in case a summary link was clicked
9864										setTimeout( function() {
9865											$summaryContainer = $form.find( "#" + errorFormId );
9866
9867											// Output our error summary and place it in the error container
9868											// Create our container if one doesn't already exist
9869											if ( $summaryContainer.length === 0 ) {
9870												$summaryContainer = $( "<section id='" + errorFormId + "' class='alert alert-danger' tabindex='-1'>
9870" + summary + "</section>" ).prependTo( $form );
9871											} else {
9872												$summaryContainer.empty().append( summary );
9873											}
9874
9875											// Put focus on the error if the errors are generated by an attempted form submission
9876											if ( submitted ) {
9877
9878												// Assign focus to $summaryContainer
9879												$summaryContainer.trigger( setFocusEvent );
9880												submitted = false;
9881											}
9882										}, 100 );
9883									} else {
9884
9885										// Update the aria-live region as necessary
9886										if ( ariaLive.innerHTML.length !== 0 ) {
9887											ariaLive.innerHTML = "";
9888										}
9889										$form.find( "#" + errorFormId ).detach();
9890									}
9891								},
9892
9893								/* End of showErrors() */
9894
9895								invalidHandler: function() {
9896									submitted = true;
9897								}
9898
9899							} ); /* end of validate() */
9900
9901							/* fixes validation issue (see PR #7913) */
9902							$form.on( "change", "input[type=date], input[type=file], select", function() {
9903								$form.validate().element( this );
9904							} );
9905
9906							// Clear the form and remove error messages on reset
9907							$document.on( "click", selector + " input[type=reset]", function( event ) {
9908								var which = event.which,
9909									ariaLive;
9910
9911								// Ignore middle/right mouse buttons
9912								if ( !which || which === 1 ) {
9913									validator.resetForm();
9914									$( "#" + errorFormId ).detach();
9915
9916									ariaLive = $form.closest( ".wb-frmvld" ).find( ".arialive" )[ 0 ];
9917									if ( ariaLive.innerHTML.length !== 0 ) {
9918										ariaLive.innerHTML = "";
9919									}
9920
9921									// Correct the colouring of fields that are no longer invalid
9922									$form.find( ".has-error" ).removeClass( "has-error" );
9923								}
9924							} );
9925
9926							//Trigger validation on wb-server-error
9927							$form.find( ".wb-server-error" ).filter( ":not( :hidden )" ).parent().each( function() {
9928								if ( this.attributes.for && this.attributes.for.value.length > 0 ) {
9929									$form.validate().element( $( "[id =" + this.attributes.for.value + "]" ) );
9930								} else if ( $( this ).find( "input" )[ 0 ] ) {
9931									$form.validate().element( $( this ).find( "input" )[ 0 ] );
9932								} else if ( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ] ) {
9933									if ( $( this ).find( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ].id ) ) {
9934										$form.validate().element( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ] );
9935									}
9936								} else if ( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ] ) {
9937									if ( $( this ).find( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ].id ) ) {
9938										$form.validate().element( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ] );
9939									}
9940								}
9941							} );
9942
9943							// Tell the i18n file to execute to run any $.validator extends
9944							$form.trigger( "formLanguages.wb" );
9945
9946							// Identify that initialization has completed
9947							wb.ready( $( eventTarget ), componentName );
9948						}
9949					} );
9950				}
9951			} );
9952		}
9953	};
9954
9955// Bind the init event of the plugin
9956$document.on( "timerpoke.wb " + initEvent, selector, init );
9957
9958// Add the timer poke to initialize the plugin
9959wb.add( selector );
9960
9961} )( jQuery, window, document, wb );
9962
9963/*
9964 * @title WET-BOEW Geomap
9965 * @overview Displays a dynamic map over which information from additional sources can be overlaid.
9966 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
9967 * @author @pjackson28
9968 */
9969( function( $, wb ) {
9970"use strict";
9971
9972var componentName = "wb-geomap",
9973	selector = "." + componentName,
9974	initEvent = "wb-init" + selector,
9975	$document = wb.doc,
9976
9977	/**
9978	 * @method init
9979	 * @param {jQuery Event} event Event that triggered the function call
9980	 */
9981	init = function( event ) {
9982
9983		// Start initialization
9984		// returns DOM object = proceed with init
9985		// returns undefined = do not proceed with init (e.g., already initialized)
9986		var elm = wb.init( event, componentName, selector ),
9987			$elm, $map, $leg, modeJS;
9988
9989		if ( elm ) {
9990			$elm = $( elm );
9991			$map = $elm.find( ".wb-geomap-map" );
9992			$leg = $elm.find( ".wb-geomap-legend" );
9993
9994			// Initialize the map height for the loader
9995			$map.height( $map.width() * 0.8 );
9996			$map.append( "<div class='geomap-progress'><span class='wb-inv'>" + wb.i18n( "load" ) + "</span></div>" );
9997			$leg.append( "<div class='skeleton-lgnd-1'><span class='skeleton-lgnd-3'></span></div>" +
9998					"<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" +
9999					"<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" +
10000					"<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" +
10001					"<div  class='skeleton-lgnd-1'><span class='skeleton-lgnd-3'></span></div>" );
10002
10003			modeJS = wb.getMode() + ".js";
10004
10005			Modernizr.load( [ {
10006
10007				// For loading multiple dependencies
10008				both: [
10009					"site!deps/proj4" + modeJS,
10010					"site!deps/ol" + modeJS,
10011					"site!deps/geomap-lib" + modeJS
10012				],
10013				complete: function() {
10014					$elm.trigger( "geomap.wb" );
10015				}
10016			} ] );
10017		}
10018	};
10019
10020// Bind the init function to the timerpoke event
10021$document.on( "timerpoke.wb " + initEvent, selector, init );
10022
10023// Add the timer poke to initialize the plugin
10024wb.add( selector );
10025
10026} )( jQuery, wb );
10027
10028/**
10029 * @title WET-BOEW JSON Fetch [ json-fetch ]
10030 * @overview Load and filter data from a JSON file
10031 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
10032 * @author @duboisp
10033 */
10034/*global jsonpointer */
10035( function( $, wb, DOMPurify ) {
10036"use strict";
10037
10038/*
10039 * Variable and function definitions.
10040 * These are global to the plugin - meaning that they will be initialized once per page,
10041 * not once per instance of plugin on the page. So, this is a good place to define
10042 * variables that are common to all instances of the plugin on a page.
10043 */
10044var $document = wb.doc,
10045	component = "json-fetch",
10046	fetchEvent = component + ".wb",
10047	jsonCache = { },
10048	jsonCacheBacklog = { },
10049	completeJsonFetch = function( callerId, refId, response, status, xhr, selector, fetchedOpts ) {
10050		if ( !window.jsonpointer ) {
10051
10052			// JSON pointer library is loaded but not executed in memory yet, we need to wait a tick before to continue
10053			setTimeout( function() {
10054				completeJsonFetch( callerId, refId, response, status, xhr, selector, fetchedOpts );
10055			}, 100 );
10056			return false;
10057		}
10058		if ( selector ) {
10059			try {
10060				response = jsonpointer.get( response, selector );
10061			} catch ( ex ) {
10062				console.error( "JSON fetch - Bad JSON selector: " + selector );
10063				console.error( response );
10064				console.error( $( "#" + callerId ).get( 0 ) );
10065			}
10066		}
10067		$( "#" + callerId ).trigger( {
10068			type: "json-fetched.wb",
10069			fetch: {
10070				response: response,
10071				status: status,
10072				xhr: xhr,
10073				refId: refId,
10074				fetchedOpts: fetchedOpts
10075			}
10076		}, this );
10077	};
10078
10079// Event binding
10080$document.on( fetchEvent, function( event ) {
10081
10082	var caller = event.element || event.target,
10083		fetchOpts = event.fetch || { url: "" },
10084		urlParts = fetchOpts.url.split( "#" ),
10085		url = urlParts[ 0 ],
10086		fetchNoCache = fetchOpts.nocache,
10087		fetchNoCacheKey = fetchOpts.nocachekey || wb.cacheBustKey || "wbCacheBust",
10088		fetchNoCacheValue,
10089		fetchCacheURL,
10090		hashPart,
10091		datasetName,
10092		selector = urlParts[ 1 ] || false,
10093		callerId, refId = fetchOpts.refId,
10094		cachedResponse;
10095
10096	// Filter out any events triggered by descendants
10097	if ( caller === event.target || event.currentTarget === event.target ) {
10098
10099		if ( !caller.id ) {
10100			caller.id = wb.getId();
10101		}
10102		callerId = caller.id;
10103
10104		if ( selector ) {
10105
10106			// If a Dataset Name exist let it managed by wb-jsonpatch plugin
10107			hashPart = selector.split( "/" );
10108			datasetName = hashPart[ 0 ];
10109
10110			// A dataset name must start with "[" character, if it is a letter, then follow JSON Schema (to be implemented)
10111			if ( datasetName.charCodeAt( 0 ) === 91 ) {
10112
10113				// Let the wb-jsonpatch plugin to manage it
10114				$( "#" + callerId ).trigger( {
10115					type: "postpone.wb-jsonmanager",
10116					postpone: {
10117						callerId: callerId,
10118						refId: refId,
10119						dsname: datasetName,
10120						selector: selector.substring( datasetName.length )
10121					}
10122				} );
10123				return;
10124			}
10125			fetchOpts.url = url;
10126		}
10127
10128		if ( fetchNoCache ) {
10129			if ( fetchNoCache === "nocache" ) {
10130				fetchNoCacheValue = wb.guid();
10131			} else {
10132				fetchNoCacheValue = wb.sessionGUID();
10133			}
10134			fetchCacheURL = fetchNoCacheKey + "=" + fetchNoCacheValue;
10135
10136			if ( url.indexOf( "?" ) !== -1 ) {
10137				url = url + "&" + fetchCacheURL;
10138			} else {
10139				url = url + "?" + fetchCacheURL;
10140			}
10141			fetchOpts.url = url;
10142		}
10143
10144		Modernizr.load( {
10145			load: "site!deps/jsonpointer" + wb.getMode() + ".js",
10146			complete: function() {
10147
10148				// Ensure this fetch has an URL. There is no URL when only using dataset name (a virtual JSON file).
10149				if ( !url ) {
10150					return;
10151				}
10152
10153				if ( !fetchOpts.nocache ) {
10154					cachedResponse = jsonCache[ url ];
10155
10156					if ( cachedResponse ) {
10157						completeJsonFetch( callerId, refId, cachedResponse, "success", undefined, selector, fetchOpts );
10158						return;
10159					} else {
10160						if ( !jsonCacheBacklog[ url ] ) {
10161							jsonCacheBacklog[ url ] = [ ];
10162						} else {
10163							jsonCacheBacklog[ url ].push( {
10164								"callerId": callerId,
10165								"refId": refId,
10166								"selector": selector
10167							} );
10168							return;
10169						}
10170					}
10171				}
10172
10173				// Ensure we only receive JSON data and don't allow jsonp
10174				// jQuery will raise an error if other data format is received
10175				fetchOpts.dataType = "json";
10176				if ( fetchOpts.jsonp ) {
10177					fetchOpts.jsonp = false;
10178				}
10179
10180				// Sending Data
10181				if ( fetchOpts.data ) {
10182					try {
10183						fetchOpts.data = ( typeof fetchOpts.data === "string" ? fetchOpts.data : JSON.stringify( fetchOpts.data ) );
10184					} catch ( err ) {
10185						throw "JSON fetch - Data being sent to server - " + err;
10186					}
10187
10188					fetchOpts.method = fetchOpts.method || "POST";
10189					fetchOpts.contentType = fetchOpts.contentType || "application/json";
10190				}
10191
10192				$.ajax( fetchOpts )
10193					.done( function( response, status, xhr ) {
10194						var i, i_len, i_cache, backlog;
10195
10196						if ( !fetchOpts.nocache ) {
10197							try {
10198								jsonCache[ url ] = response;
10199							} catch ( error ) {
10200								return;
10201							}
10202						}
10203
10204						completeJsonFetch( callerId, refId, response, status, xhr, selector, fetchOpts );
10205
10206						if ( jsonCacheBacklog[ url ] ) {
10207							backlog = jsonCacheBacklog[ url ];
10208
10209							i_len = backlog.length;
10210
10211							for ( i = 0; i !== i_len; i += 1 ) {
10212								i_cache = backlog[ i ];
10213								completeJsonFetch( i_cache.callerId, i_cache.refId, response, status, xhr, i_cache.selector, fetchOpts );
10214							}
10215						}
10216
10217					} )
10218					.fail( function( xhr, status, error ) {
10219						xhr.responseText = DOMPurify.sanitize( xhr.responseText );
10220						$( "#" + callerId ).trigger( {
10221							type: "json-failed.wb",
10222							fetch: {
10223								xhr: xhr,
10224								status: status,
10225								error: error,
10226								refId: refId,
10227								fetchOpts: fetchOpts
10228							}
10229						}, this );
10230					}, this );
10231			}
10232		} );
10233	}
10234} );
10235
10236} )( jQuery, wb, DOMPurify );
10237
10238/**
10239 * @title WET-BOEW Lightbox
10240 * @overview Helps build a photo gallery on a web page.
10241 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
10242 * @author @pjackson28
10243 */
10244( function( $, DOMPurify, window, document, wb, undef ) {
10245"use strict";
10246
10247/*
10248 * Variable and function definitions.
10249 * These are global to the plugin - meaning that they will be initialized once per page,
10250 * not once per instance of plugin on the page. So, this is a good place to define
10251 * variables that are common to all instances of the plugin on a page.
10252 */
10253var componentName = "wb-lbx",
10254	selector = "." + componentName,
10255	initEvent = "wb-init" + selector,
10256	setFocusEvent = "setfocus.wb",
10257	dependenciesLoadedEvent = "deps-loaded" + selector,
10258	modalHideSelector = "#wb-tphp, body > header, body > main, body > footer",
10259	$document = wb.doc,
10260	callbacks, i18n, i18nText,
10261
10262	/**
10263	 * @method init
10264	 * @param {jQuery Event} event Event that triggered the function call
10265	 */
10266	init = function( event ) {
10267
10268		// Start initialization
10269		// returns DOM object = proceed with init
10270		// returns undefined = do not proceed with init (e.g., already initialized)
10271		var elm = wb.init( event, componentName, selector ),
10272			elmId;
10273
10274		if ( elm ) {
10275			elmId = elm.id;
10276
10277			// Ensure the dependencies are loaded first
10278			$document.one( dependenciesLoadedEvent, function() {
10279				var elm = document.getElementById( elmId ),
10280					$elm = $( elm ),
10281					settings = {},
10282					firstLink;
10283
10284				if ( !elm ) {
10285					return;
10286				}
10287
10288				// TODO: Add swipe support
10289
10290				settings.callbacks = callbacks;
10291
10292				if ( elm.nodeName.toLowerCase() !== "a" ) {
10293					settings.delegate = "a:not(" + selector + "-skip)";
10294					firstLink = elm.getElementsByTagName( "a" )[ 0 ];
10295
10296					// Is the element a gallery?
10297					if ( elm.className.indexOf( "-gal" ) !== -1 ) {
10298						settings.gallery = {
10299							enabled: true
10300						};
10301					}
10302				} else {
10303					firstLink = elm;
10304				}
10305
10306				if ( firstLink ) {
10307					if ( firstLink.getAttribute( "href" ).charAt( 0 ) === "#" ) {
10308						settings.type = "inline";
10309					} else if ( firstLink.className.indexOf( "lbx-iframe" ) !== -1 ) {
10310						settings.type = "iframe";
10311					} else if ( firstLink.getElementsByTagName( "img" ).length === 0 ) {
10312						settings.type = "ajax";
10313					} else {
10314						settings.type = "image";
10315					}
10316
10317					if ( elm.className.indexOf( "lbx-modal" ) !== -1 ) {
10318						settings.modal = true;
10319					}
10320					if ( elm.className.indexOf( "lbx-ajax" ) !== -1 ) {
10321						settings.type = "ajax";
10322					}
10323					if ( elm.className.indexOf( "lbx-image" ) !== -1 ) {
10324						settings.type = "image";
10325					}
10326					if ( elm.className.indexOf( "lbx-inline" ) !== -1 ) {
10327						settings.type = "inline";
10328					}
10329
10330					// Extend the settings with window[ "wb-lbx" ] then data-wb-lbx
10331					settings = $.extend(
10332						true,
10333						settings,
10334						window[ componentName ],
10335						wb.getData( $elm, componentName )
10336					);
10337					$elm.magnificPopup(
10338						settings
10339					).data( "wbLbxFilter", settings.filter );
10340				}
10341
10342				// Identify that initialization has completed
10343				wb.ready( $elm, componentName );
10344			} );
10345
10346			// Load dependencies as needed
10347			setup();
10348		}
10349	},
10350
10351	/**
10352	 * @method setup
10353	 */
10354	setup = function() {
10355
10356		// Only initialize the i18nText and callbacks once
10357		if ( !i18nText ) {
10358			i18n = wb.i18n;
10359			i18nText = {
10360				close: i18n( "close" ),
10361				oClose: i18n( "overlay-close" ),
10362				tClose: i18n( "overlay-close" ) + i18n( "space" ) + i18n( "esc-key" ),
10363				tLoading: i18n( "load" ),
10364				gallery: {
10365					tPrev: i18n( "prv-l" ),
10366					tNext: i18n( "nxt-r" ),
10367					tCounter: i18n( "lb-curr" )
10368				},
10369				image: {
10370					tError: i18n( "lb-img-err" ) + " (<a href=\"url%\">)"
10371				},
10372				ajax: {
10373					tError: i18n( "lb-xhr-err" ) + " (<a href=\"url%\">)"
10374				}
10375			};
10376
10377			callbacks = {
10378				open: function() {
10379
10380					// TODO: Better if dealt with upstream by Magnific popup
10381					var $item = this.currItem,
10382						$content = this.contentContainer,
10383						$wrap = this.wrap,
10384						$container = $wrap.find( ".mfp-container" ),
10385						$containerParent = $container.parent(),
10386						$modal = $wrap.find( ".modal-dialog" ),
10387						$buttons = $wrap.find( ".mfp-close, .mfp-arrow" ),
10388						len = $buttons.length,
10389						i, button;
10390
10391					createCloseButton( $modal );
10392
10393					$document.find( "body" ).addClass( "wb-modal" );
10394					$document.find( modalHideSelector ).attr( "aria-hidden", "true" );
10395					for ( i = 0; i !== len; i += 1 ) {
10396						button = $buttons[ i ];
10397						button.innerHTML += "<span class='wb-inv'> " + button.title + "</span>";
10398					}
10399
10400					if ( $item.type === "image" ) {
10401						$content.find( ".mfp-bottom-bar" ).attr( "id", "lbx-title" );
10402					} else {
10403						$content.attr( "role", "document" );
10404					}
10405
10406					this.contentContainer.attr( "data-pgtitle", document.getElementsByTagName( "H1" )[ 0 ].textContent );
10407
10408					trapTabbing( $wrap );
10409
10410					if ( !$containerParent.is( "dialog" ) ) {
10411						$container.wrap( "<dialog class='mfp-container' open='open'></dialog>" );
10412					} else {
10413						$containerParent.attr( "open", "open" );
10414					}
10415				},
10416				close: function() {
10417					$document.find( "body" ).removeClass( "wb-modal" );
10418					$document.find( modalHideSelector ).removeAttr( "aria-hidden" );
10419					this.wrap.find( "dialog" ).removeAttr( "open" );
10420
10421				},
10422				change: function() {
10423					var $item = this.currItem,
10424						$content = this.contentContainer,
10425						$el, $bottomBar, $source, $target,
10426						description, altTitleId, altTitle;
10427
10428					if ( $item.type === "image" ) {
10429						$el = $item.el;
10430						$target = $item.img;
10431						$bottomBar = $content.find( ".mfp-bottom-bar" );
10432
10433						if ( $el ) {
10434							$source = $el.find( "img" );
10435							$target.attr( "alt", $source.attr( "alt" ) );
10436
10437							// Replicate aria-describedby if it exists
10438							description = $source.attr( "aria-describedby" );
10439							if ( description ) {
10440								$target.attr( "aria-describedby", description );
10441							}
10442
10443							// Replicate longdesc if it exists
10444							description = $source.attr( "longdesc" );
10445							if ( description ) {
10446								$target.attr( "longdesc", description );
10447							}
10448
10449							// Handle alternate titles
10450							altTitleId = $el.attr( "data-title" );
10451							if ( altTitleId ) {
10452								altTitle = document.getElementById( altTitleId );
10453								if ( altTitle !== null ) {
10454									$bottomBar.find( ".mfp-title" ).html( altTitle.innerHTML );
10455								}
10456							}
10457						} else {
10458							$target.attr( "alt", $bottomBar.find( ".mfp-title" ).html() );
10459						}
10460					} else {
10461						$content
10462							.find( ".modal-title, h1" )
10463							.first()
10464							.attr( "id", "lbx-title" );
10465					}
10466
10467					$content.attr( "aria-labelledby", "lbx-title" );
10468				},
10469				parseAjax: function( mfpResponse ) {
10470					var currItem = this.currItem,
10471						currEl = currItem.el,
10472						urlHash = currItem.src.split( "#" )[ 1 ],
10473						filter = currEl ? currEl.data( "wbLbxFilter" ) : undef,
10474						selector = filter || ( urlHash ? "#" + urlHash : false ),
10475						$response;
10476
10477					// Sanitize the response
10478					mfpResponse.data = DOMPurify.sanitize( mfpResponse.data );
10479
10480					// Provide the ability to filter the AJAX response HTML
10481					// by the URL hash or a selector
10482					// TODO: Should be dealt with upstream by Magnific Popup
10483					if ( selector ) {
10484						$response = $( "<div>" + mfpResponse.data + "</div>" ).find( selector );
10485					} else {
10486						$response = $( mfpResponse.data );
10487					}
10488					createCloseButton( $response );
10489
10490					$response
10491						.find( ".modal-title, h1" )
10492						.first()
10493						.attr( "id", "lbx-title" );
10494
10495					mfpResponse.data = $response;
10496				},
10497				ajaxContentAdded: function() {
10498					trapTabbing( this.wrap );
10499				}
10500			};
10501		}
10502
10503		// Load Magnific Popup dependency and bind the init event handler
10504		Modernizr.load( {
10505			load: "site!deps/jquery.magnific-popup" + wb.getMode() + ".js",
10506			testReady: function() {
10507				return $.magnificPopup;
10508			},
10509			complete: function() {
10510
10511				// Set the dependency i18nText only once
10512				$.extend( true, $.magnificPopup.defaults, i18nText );
10513
10514				$document.trigger( dependenciesLoadedEvent );
10515			}
10516		} );
10517	},
10518	createCloseButton = function( $modal ) {
10519		if ( $modal !== null && $modal.hasClass( "modal-dialog" ) ) {
10520			var footer = $modal.find( ".modal-footer" ).first(),
10521				hasFooter = footer.length,
10522				hasButton = hasFooter && $( footer ).find( ".popup-modal-dismiss" ).length !== 0,
10523				closeClassFtr = "popup-modal-dismiss",
10524				closeTextFtr = i18nText.close,
10525				spanTextFtr = i18nText.oClose,
10526				overlayCloseFtr;
10527
10528			if ( !hasButton ) {
10529				if ( !hasFooter ) {
10530					footer = document.createElement( "div" );
10531					footer.setAttribute( "class", "modal-footer" );
10532				}
10533
10534				overlayCloseFtr = "<button type='button' class='btn btn-sm btn-primary pull-left " + closeClassFtr +
10535					"' title='" + spanTextFtr + "'>" +
10536					closeTextFtr +
10537					"<span class='wb-inv'>" + spanTextFtr + "</span></button>";
10538
10539				$( footer ).append( overlayCloseFtr );
10540				if ( !hasFooter ) {
10541					$( footer ).insertAfter( $modal.find( ".modal-body" ) );
10542				}
10543			}
10544		}
10545	},
10546	trapTabbing = function( $wrap ) {
10547
10548		$wrap.on( "keydown", function( e ) {
10549			if ( e.which === 9 ) {
10550				var tabbable = $wrap.find( ".mfp-container :tabbable:visible" ),
10551					firstTabbable = tabbable.first()[ 0 ],
10552					lastTabbable = tabbable.last()[ 0 ],
10553					currentFocus = $( document.activeElement )[ 0 ];
10554
10555				if ( !e.shiftKey && currentFocus === lastTabbable ) {
10556					e.preventDefault();
10557					firstTabbable.focus();
10558				} else if ( e.shiftKey && ( currentFocus === firstTabbable || currentFocus === $wrap[ 0 ] ) ) {
10559					e.preventDefault();
10560					lastTabbable.focus();
10561				}
10562			}
10563		} );
10564	};
10565
10566// Bind the init event of the plugin
10567$document.on( "timerpoke.wb " + initEvent, selector, init );
10568
10569// Handler for clicking on a same page link within the overlay to outside the overlay
10570$document.on( "click", ".mfp-wrap a[href^='#']", function( event ) {
10571	var which = event.which,
10572		eventTarget = event.currentTarget,
10573		$lightbox, linkTarget;
10574
10575	// Ignore middle/right mouse buttons
10576	if ( !which || which === 1 ) {
10577		$lightbox = $( eventTarget ).closest( ".mfp-wrap" );
10578		linkTarget = document.getElementById( eventTarget.getAttribute( "href" ).substring( 1 ) );
10579
10580		// Ignore same page links to within the overlay
10581		if ( linkTarget && !$.contains( $lightbox[ 0 ], linkTarget ) ) {
10582
10583			// Stop propagation of the click event
10584			if ( event.stopPropagation ) {
10585				event.stopImmediatePropagation();
10586			} else {
10587				event.cancelBubble = true;
10588			}
10589
10590			// Close the overlay and set focus to the same page link
10591			$.magnificPopup.close();
10592			$( linkTarget ).trigger( setFocusEvent );
10593		}
10594	}
10595} );
10596
10597// Event handler for closing a modal popup
10598$( document ).on( "click", ".popup-modal-dismiss", function( event ) {
10599	if ( !this.hasAttribute( "target" ) ) {
10600		event.preventDefault();
10601	}
10602
10603	$.magnificPopup.close();
10604} );
10605
10606// Event handler for opening a popup without a link
10607$( document ).on( "open" + selector, function( event, items, modal, title, ajax ) {
10608	if ( event.namespace === componentName ) {
10609		var isGallery = items.length > 1,
10610			isModal = modal && !isGallery ? modal : false,
10611			titleSrc = title ? function() {
10612				return title[ $.magnificPopup.instance.index ];
10613			} : "title";
10614
10615		event.preventDefault();
10616
10617		// Ensure the dependencies are loaded first
10618		$document.one( dependenciesLoadedEvent, function() {
10619			$.magnificPopup.open( {
10620				items: items,
10621				modal: isModal,
10622				gallery: {
10623					enabled: isGallery
10624				},
10625				image: {
10626					titleSrc: titleSrc
10627				},
10628				callbacks: callbacks,
10629				ajax: ajax
10630			} );
10631
10632			wb.ready( undef, componentName );
10633		} );
10634
10635		// Load dependencies as needed
10636		setup();
10637	}
10638} );
10639
10640// Add the timer poke to initialize the plugin
10641wb.add( selector );
10642
10643} )( jQuery, DOMPurify, window, document, wb );
10644
10645/**
10646 * @title WET-BOEW Menu plugin
10647 * @overview A Menu plugin for WET
10648 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
10649 * @author WET community
10650 */
10651( function( $, window, document, wb ) {
10652"use strict";
10653
10654/*
10655 * Variable and function definitions.
10656 * These are global to the plugin - meaning that they will be initialized once per page,
10657 * not once per instance of plugin on the page. So, this is a good place to define
10658 * variables that are common to all instances of the plugin on a page.
10659 */
10660var componentName = "wb-menu",
10661	selector = "." + componentName,
10662	initEvent = "wb-init" + selector,
10663	breadcrumb = document.getElementById( "wb-bc" ),
10664	navCurrentEvent = "navcurr.wb",
10665	focusEvent = "setfocus.wb",
10666	detailsInitEvent = "wb-init.wb-details",
10667	menuItemSelector = "> a, > details > summary",
10668	$document = wb.doc,
10669
10670	// Used for half second delay on showing/hiding menus because of mouse hover
10671	hoverDelay = 500,
10672	menuCount = 0,
10673	globalTimeout,
10674
10675	/**
10676	 * @method init
10677	 * @param {jQuery Event} event Event that triggered the function call
10678	 */
10679	init = function( event ) {
10680
10681		// Start initialization
10682		// returns DOM object = proceed with init
10683		// returns undefined = do not proceed with init (e.g., already initialized)
10684		var elm = wb.init( event, componentName, selector ),
10685			$elm, ajaxFetch;
10686
10687		if ( elm ) {
10688			$elm = $( elm );
10689
10690			// Ensure the container has an id attribute
10691			if ( !$elm.attr( "id" ) ) {
10692				$elm.attr( "id", componentName + "-" + menuCount );
10693			}
10694			menuCount += 1;
10695
10696			// Lets test to see if we have any menus to fetch
10697			// This is required for backwards compatibility. In previous versions, the menu was not integrated witht he data ajax plugin.
10698			ajaxFetch = $elm.data( "ajax-fetch" );
10699			if ( ajaxFetch ) {
10700				$elm.trigger( {
10701					type: "ajax-fetch.wb",
10702					fetch: {
10703						url: ajaxFetch
10704					}
10705				} );
10706			} else {
10707
10708				//Enhance menus that don't rely on the data-ajax plugin
10709				ajaxFetch = $elm.data( "ajax-replace" ) || $elm.data( "ajax-append" ) || $elm.data( "ajax-prepend" );
10710				if ( !ajaxFetch ) {
10711					onAjaxLoaded( $elm, $elm );
10712				}
10713			}
10714		}
10715	},
10716
10717	/**
10718	 * Lets set some aria states and attributes
10719	 * @method drizzleAria
10720	 * @param {jQuery DOM elements} $elements The collection of elements
10721	 */
10722	drizzleAria = function( $elements ) {
10723		var length = $elements.length,
10724			$elm, $subMenu, i;
10725
10726		// Lets tweak for aria
10727		for ( i = 0; i !== length; i += 1 ) {
10728			$elm = $elements.eq( i );
10729			$subMenu = $elm.siblings( "ul" );
10730
10731			$elm.attr( {
10732				"aria-posinset": ( i + 1 ),
10733				"aria-setsize": length,
10734				role: "menuitem"
10735			} );
10736
10737			// if there is a submenu lets put in the aria for it
10738			if ( $subMenu.length !== 0 ) {
10739
10740				$elm.attr( "aria-haspopup", "true" );
10741
10742				$subMenu.attr( {
10743					"aria-expanded": "false",
10744					"aria-hidden": "true"
10745				} );
10746
10747				// recurse into submenu
10748				drizzleAria( $subMenu.children( "li" ).find( menuItemSelector ) );
10749			}
10750		}
10751	},
10752
10753	/**
10754	 * @method createCollapsibleSection
10755	 * @return {string}
10756	 */
10757	createCollapsibleSection = function( section, sectionIndex, sectionsLength, $items, itemsLength ) {
10758
10759		// Use details/summary for the collapsible mechanism
10760		var k, $elm, elm, $item, $subItems, subItemsLength,
10761			$section = $( section ),
10762			posinset = "' aria-posinset='",
10763			menuitem = " role='menuitem' aria-setsize='",
10764			sectionHtml = "<li><details>" + "<summary class='mb-item" +
10765				( $section.hasClass( "wb-navcurr" ) || $section.children( ".wb-navcurr" ).length !== 0 ? " wb-navcurr'" : "'" ) +
10766				menuitem + sectionsLength + posinset + ( sectionIndex + 1 ) +
10767				"' aria-haspopup='true'>" + $section.text() + "</summary>" +
10768				"<ul class='list-unstyled mb-sm' role='menu' aria-expanded='false' aria-hidden='true'>";
10769
10770		// Convert each of the list items into WAI-ARIA menuitems
10771		for ( k = 0; k !== itemsLength; k += 1 ) {
10772			$item = $items.eq( k );
10773			$elm = $item.find( menuItemSelector );
10774			elm = $elm[ 0 ];
10775			$subItems = $elm.parent().find( "> ul > li" );
10776			subItemsLength = $subItems.length;
10777
10778			if ( elm && subItemsLength === 0 && elm.nodeName.toLowerCase() === "a" ) {
10779				sectionHtml += "<li>" + $item[ 0 ].innerHTML.replace(
10780					/(<a\s)/,
10781					"$1" + menuitem + itemsLength +
10782							posinset + ( k + 1 ) +
10783							"' tabindex='-1' "
10784				) + "</li>";
10785			} else {
10786				sectionHtml += createCollapsibleSection( elm, k, itemsLength, $subItems, $subItems.length );
10787			}
10788		}
10789
10790		return sectionHtml + "</ul></details></li>";
10791	},
10792
10793	/**
10794	 * @method createMobilePanelMenu
10795	 * @param {array} allProperties Properties used to build the menu system
10796	 * @return {string}
10797	 */
10798	createMobilePanelMenu = function( allProperties ) {
10799		var panel = "",
10800			sectionHtml, properties, sections, section, parent, $items,
10801			linkHtml, i, j, len, sectionsLength, itemsLength;
10802
10803		// Process the secondary and site menus
10804		len = allProperties.length;
10805		for ( i = 0; i !== len; i += 1 ) {
10806			properties = allProperties[ i ];
10807			sectionHtml = "";
10808			sections = properties[ 0 ];
10809			sectionsLength = sections.length;
10810			for ( j = 0; j !== sectionsLength; j += 1 ) {
10811				section = sections[ j ];
10812				$items = $( section.parentNode ).find( "> ul > li" );
10813				itemsLength = $items.length;
10814
10815				// Collapsible section
10816				if ( itemsLength !== 0 ) {
10817					sectionHtml += createCollapsibleSection( section, j, sectionsLength, $items, itemsLength );
10818				} else {
10819					parent = section.parentNode;
10820
10821					// Menu item without a section
10822					if ( parent.nodeName.toLowerCase() === "li" ) {
10823						linkHtml = parent.innerHTML;
10824
10825					// Non-list menu items without a section and that contain their own link
10826					} else if ( parent.getElementsByTagName( "a" )[ 0 ] === section.getElementsByTagName( "a" )[ 0 ] ) {
10827						linkHtml = section.innerHTML;
10828
10829					// Non-list menu item without a section and whose siblings contain a link
10830					} else {
10831						linkHtml = "<a href='" +
10832							parent.getElementsByTagName( "a" )[ 0 ].href + "'>" +
10833							section.innerHTML + "</a>";
10834					}
10835
10836					// Convert the list item to a WAI-ARIA menuitem
10837					sectionHtml += "<li class='no-sect'>" +
10838						linkHtml.replace(
10839							/(<a\s)/,
10840							"$1 class='mb-item' " + "role='menuitem' aria-setsize='" +
10841								sectionsLength + "' aria-posinset='" + ( j + 1 ) +
10842								"' tabindex='-1' "
10843						) + "</li>";
10844				}
10845			}
10846
10847			// Create the panel section
10848			panel += "<nav role='navigation' typeof='SiteNavigationElement' id='" +
10849				properties[ 1 ] + "' class='" + properties[ 1 ] + " wb-menu wb-menu-inited'>" +
10850				"<h3>" + properties[ 2 ] + "</h3>" +
10851				"<ul class='list-unstyled mb-menu' role='menu'>" +
10852				sectionHtml + "</ul></nav>";
10853		}
10854
10855		return panel.replace( /['"]?list-group-item['"]?/gi, "\"\"" );
10856	},
10857
10858	/**
10859	 * @method onAjaxLoaded
10860	 * @param {jQuery DOM element} $elm The plugin element
10861	 * @param {jQuery DOM element}
10861 $ajaxResult The AJAXed in menu content to import
10862	 */
10863	onAjaxLoaded = function( $elm, $ajaxResult ) {
10864		var $info = $( "#wb-info" ),
10865			inner = function() {
10866				var $ajaxed = $ajaxResult && $ajaxResult.attr( "data-type" ) === "string" ? $ajaxResult : $elm,
10867					$menubar = $ajaxed.find( ".menu" ),
10868					$menu = $menubar.find( "> li > a" ),
10869					target = $elm.data( "trgt" ),
10870					$secnav = $( "#wb-sec" ),
10871					$language = $( "#wb-lng" ),
10872					search = document.getElementById( "wb-srch" ),
10873					panel = "",
10874					panelDOM = document.getElementById( target ),
10875					$panel = $( panelDOM ),
10876					allProperties = [],
10877					$navCurr, $menuItem, $langItems, len, i;
10878
10879				/*
10880				 * Build the mobile panel
10881				 */
10882
10883				// Add search
10884				if ( search !== null ) {
10885					panel += "<section class='srch-pnl'>" +
10886						search.innerHTML
10887							.replace( /h2>/i, "h3>" )
10888							.replace( /(for|id)="([^"]+)"/gi, "$1='$2-imprt'" ) +
10889						"</section>";
10890				}
10891
10892				// Add active language offer
10893				if ( $language.length !== 0 ) {
10894					$langItems = $language.find( "li:not(.curr)" );
10895					len = $langItems.length;
10896					panel += "<section class='lng-ofr'>" +
10897						"<h3>" + $language.children( "h2" ).html() + "</h3>" +
10898						"<ul class='list-inline'>";
10899					for ( i = 0; i !== len; i += 1 ) {
10900						panel += $langItems[ i ].innerHTML
10901							.replace( /(<a\s.*<\/a>?)/, "<li>$1</li>" );
10902					}
10903					panel += "</ul></section>";
10904				}
10905
10906				// Create menu system
10907				if ( $secnav.length !== 0 || $menubar.length !== 0 || $info.length !== 0 ) {
10908
10909					// Add the secondary menu
10910					if ( $secnav.length !== 0 ) {
10911						allProperties.push( [
10912							$secnav.find( "ul" ).filter( ":not(li > ul)" ).find( " > li > *:first-child" ).get(),
10913							"sec-pnl",
10914							$secnav.find( "h2" ).html()
10915						] );
10916
10917						if ( $secnav.find( ".wb-navcurr" ).length === 0 ) {
10918
10919							// Trigger the navcurrent plugin
10920							$secnav.trigger( navCurrentEvent, breadcrumb );
10921						}
10922					}
10923
10924					// Add the site menu
10925					if ( $menubar.length !== 0 ) {
10926
10927						// Add the menubar role if it is missing
10928						if ( !$menubar.attr( "role" ) ) {
10929							$menubar.attr( "role", "menubar" );
10930						}
10931
10932						allProperties.push( [
10933							$menu.get(),
10934							"sm-pnl",
10935							$ajaxed.find( "h2" ).html()
10936						] );
10937					}
10938
10939					// Add the site information
10940					if ( $info.length !== 0 ) {
10941						allProperties.push( [
10942							$info.find( "h3, a" ).not( "section a" ),
10943							"info-pnl",
10944							$info.find( "h2" ).html()
10945						] );
10946
10947						if ( $info.find( ".wb-navcurr" ).length === 0 ) {
10948
10949							// Trigger the navcurrent plugin
10950							$info.trigger( navCurrentEvent, breadcrumb );
10951						}
10952					}
10953
10954					panel += createMobilePanelMenu( allProperties );
10955				}
10956
10957				// Let's now populate the DOM since we have done all the work in a documentFragment
10958				panelDOM.innerHTML = "<header class='modal-header'><div class='modal-title'>" +
10959						document.getElementById( "wb-glb-mn" )
10960							.getElementsByTagName( "h2" )[ 0 ]
10961							.innerHTML +
10962						"</div></header><div class='modal-body'>" + panel + "</div>";
10963				panelDOM.className += " wb-overlay modal-content overlay-def wb-panel-r";
10964
10965				// fix #8241
10966				if ( $.active > 0 ) {
10967					$( document ).ajaxStop( function() {
10968						initOverlay( $panel );
10969					} );
10970				} else {
10971					initOverlay( $panel );
10972				}
10973
10974				/*
10975				 * Build the regular mega menu
10976				 */
10977				$ajaxed
10978					.find( ":discoverable" )
10979					.attr( "tabindex", "-1" );
10980
10981				if ( $menu.length !== 0 ) {
10982					$menu[ 0 ].setAttribute( "tabindex", "0" );
10983					drizzleAria( $menu );
10984					$menu
10985						.filter( "[aria-haspopup=true]" )
10986						.append( "<span class='expicon glyphicon glyphicon-chevron-down'></span>" );
10987				}
10988
10989				// Replace elements
10990				$elm.html( $ajaxed.html() );
10991
10992				// Trigger the navcurrent plugin
10993				setTimeout( function() {
10994					$elm.trigger( navCurrentEvent, breadcrumb );
10995					$panel.find( "#sm-pnl" ).trigger( navCurrentEvent, breadcrumb );
10996
10997					// Ensure that wb-navcurr is reflected in the top level
10998					$navCurr = $panel.find( ".wb-navcurr" );
10999					len = $navCurr.length;
11000					for ( i = 0; i !== len; i += 1 ) {
11001						$menuItem = $navCurr.eq( i );
11002
11003						// If not at the top level, then add wb-navcurr to the top level
11004						if ( !$menuItem.hasClass( ".mb-item" ) ) {
11005							$menuItem = $menuItem
11006								.closest( "details" )
11007								.children( "summary" )
11008								.addClass( "wb-navcurr" );
11009						}
11010					}
11011
11012					// Open up the secondary menu if it has wb-navcurr and has a submenu
11013					$menuItem = $panel.find( "#sec-pnl .wb-navcurr.mb-item" );
11014					if ( $menuItem.attr( "aria-haspopup" ) === "true" ) {
11015						$menuItem
11016							.trigger( "click" )
11017							.parent()
11018							.prop( "open", "open" );
11019					}
11020
11021					// Identify that initialization has completed
11022					wb.ready( $elm, componentName );
11023				}, 1 );
11024			},
11025			$footerAjax  = $info.find( "[data-ajax-replace],[data-ajax-append],[data-ajax-prepend]" ),
11026			footerAjaxLength = $footerAjax.length,
11027			ajaxCount = 0;
11028
11029		//Delay the execution the menu until any ajaxed footer content is in
11030		if ( footerAjaxLength === 0 ) {
11031			inner();
11032		} else {
11033			$info.on( "wb-contentupdated ajax-failed.wb", function() {
11034				ajaxCount += 1;
11035				if ( ajaxCount === footerAjaxLength ) {
11036					inner();
11037				}
11038			} );
11039		}
11040	},
11041
11042	// fix #8517
11043	/**
11044	 * @method initOverlay
11045	 * @param {jQuery object} $panel Current panel
11046	 */
11047	initOverlay = function( $panel ) {
11048		$panel
11049			.trigger( "wb-init.wb-overlay" )
11050			.find( "summary" )
11051			.attr( "tabindex", "-1" )
11052			.trigger( detailsInitEvent );
11053		$panel
11054			.find( ".mb-menu > li:first-child" )
11055			.find( ".mb-item" )
11056			.attr( "tabindex", "0" );
11057	},
11058
11059	/**
11060	 * @method menuIncrement
11061	 * @param {jQuery object} $menuItems Collection of of menu items to move between
11062	 * @param {jQuery object} $current Current menu item
11063	 * @param {integer} indexChange Requested relative change to the menu item index
11064	 */
11065	menuIncrement = function( $menuItems, $current, indexChange ) {
11066		var menuItemsLength = $menuItems.length,
11067			index = $menuItems.index( $current ) + indexChange;
11068
11069		// Correct out-of-range indexes
11070		index = index === menuItemsLength ? 0 : index === -1 ? menuItemsLength - 1 : index;
11071
11072		// Move to the new menu item
11073		$menuItems.eq( index ).trigger( focusEvent );
11074	},
11075
11076	/**
11077	 * @method menuClose
11078	 * @param {jQuery DOM element} $elm Parent of the element to close
11079	 * @param {boolean} removeActive Whether or not to keep the active class
11080	 */
11081	menuClose = function( $elm, removeActive ) {
11082		$elm
11083			.removeClass( "sm-open" )
11084			.children( ".open" )
11085			.removeClass( "open" )
11086			.attr( {
11087				"aria-hidden": "true",
11088				"aria-expanded": "false"
11089			} )
11090
11091		// Close nested submenus
11092			.find( "details" )
11093			.removeAttr( "open" )
11094			.children( "ul" )
11095			.attr( {
11096				"aria-hidden": "true",
11097				"aria-expanded": "false"
11098			} );
11099
11100		if ( removeActive ) {
11101			$elm.removeClass( "active" );
11102		}
11103	},
11104
11105	/**
11106	 * @method menuDisplay
11107	 * @param {jQuery DOM element} $elm The plugin element
11108	 * @param {jQuery event} menu The menu to display
11109	 */
11110	menuDisplay = function( $elm, menu ) {
11111		var menuLink = menu.children( "a" );
11112
11113		menuClose( $elm.find( ".active" ), true );
11114
11115		menu.addClass( "active" );
11116
11117		// Ignore if doesn't have a submenu
11118		if ( menuLink.attr( "aria-haspopup" ) === "true" ) {
11119
11120			// Add the open state classes
11121			menu
11122				.addClass( "sm-open" )
11123				.children( ".sm" )
11124				.addClass( "open" )
11125				.attr( {
11126					"aria-hidden": "false",
11127					"aria-expanded": "true"
11128				} );
11129		}
11130	},
11131
11132	/**
11133	 * Searches for the next link that has link text starting with a specific letter
11134	 * @method selectByLetter
11135	 * @param {integer} charCode The charCode of the letter to search for
11136	 * @param {DOM elements} links Collection of links to search
11137	 */
11138	selectByLetter = function( charCode, links ) {
11139		var len = links.length,
11140			keyChar = String.fromCharCode( charCode ),
11141			link, i;
11142
11143		for ( i = 0; i !== len; i += 1 ) {
11144			link = links[ i ];
11145			if ( link.innerHTML.charAt( 0 ) === keyChar ) {
11146				$( link ).trigger( focusEvent );
11147				return true;
11148			}
11149		}
11150
11151		return false;
11152	};
11153
11154// Bind the events of the plugin
11155$document.on( "timerpoke.wb " + initEvent + " ajax-fetched.wb ajax-failed.wb", selector, function( event ) {
11156
11157	var eventType = event.type,
11158		elm, $elm;
11159
11160	switch ( eventType ) {
11161	case "ajax-fetched":
11162	case "ajax-failed":
11163		elm = event.target;
11164
11165		// Filter out any events triggered by descendants
11166		if ( event.currentTarget === elm ) {
11167			$elm = $( elm );
11168
11169			// Only replace the menu if there isn't an error
11170			onAjaxLoaded(
11171				$elm,
11172				eventType === "ajax-fetched" ? event.fetch.pointer : $elm
11173			);
11174		}
11175		return false;
11176
11177	case "timerpoke":
11178	case "wb-init":
11179		init( event );
11180		break;
11181	}
11182
11183	/*
11184	 * Since we are working with events we want to ensure that we are being passive about our control,
11185	 * so returning true allows for events to always continue
11186	 */
11187	return true;
11188} );
11189
11190$document.on( "mouseleave", selector + " .menu", function( event ) {
11191	var $currentTarget = $( event.currentTarget );
11192
11193	// Clear the timeout for open/closing menus
11194	clearTimeout( globalTimeout );
11195
11196	globalTimeout = setTimeout( function() {
11197		menuClose( $currentTarget.find( ".active" ), true );
11198	}, hoverDelay );
11199} );
11200
11201// Prevent opening another menu if mouse re-enters already opened menu
11202$document.on( "mouseenter", selector + " .sm", function() {
11203	if ( $( this ).attr( "aria-expanded" ) === "true" ) {
11204		clearTimeout( globalTimeout );
11205	}
11206} );
11207
11208// Touchscreen "touches" on menubar items should close the submenu if it is open
11209$document.on( "click", selector + " .item[aria-haspopup=true]", function( event ) {
11210	var which = event.which,
11211		$this, $parent;
11212
11213	// Ignore middle and right mouse buttons
11214	if ( !which || which === 1 ) {
11215		event.preventDefault();
11216		$this = $( this );
11217		$parent = $this.parent();
11218
11219		// Open the submenu if it is closed
11220		if ( !$parent.hasClass( "sm-open" ) ) {
11221			$this.trigger( "focusin" );
11222		}
11223	}
11224} );
11225
11226// Click on menu items with submenus should open and close those submenus
11227$document.on( "click", selector + " [role=menu] [aria-haspopup=true]", function( event ) {
11228	var menuItem = event.currentTarget,
11229		parent = menuItem.parentNode,
11230		submenu = parent.getElementsByTagName( "ul" )[ 0 ],
11231		isOpen = submenu.getAttribute( "aria-hidden" ) === "false",
11232		menuItemOffsetTop, menuContainer;
11233
11234	// Close any other open menus
11235	if ( !isOpen ) {
11236		$( parent )
11237			.closest( "[role^='menu']" )
11238			.find( "[aria-hidden=false]" )
11239			.parent()
11240			.find( "[aria-haspopup=true]" )
11241			.not( menuItem )
11242			.trigger( "click" );
11243
11244		// Ensure the opened menu is in view if in a mobile panel
11245		menuContainer = document.getElementById( "mb-pnl" );
11246		menuItemOffsetTop = menuItem.offsetTop;
11247		if ( $.contains( menuContainer, menuItem ) &&
11248			menuItemOffsetTop < menuContainer.scrollTop ) {
11249
11250			menuContainer.scrollTop = menuItemOffsetTop;
11251		}
11252	}
11253
11254	submenu.setAttribute( "aria-expanded", !isOpen );
11255	submenu.setAttribute( "aria-hidden", isOpen );
11256} );
11257
11258// Clicks and touches outside of menus should close any open menus
11259$document.on( "click", function( event ) {
11260	var $openMenus,
11261		which = event.which;
11262
11263	// Ignore middle and right mouse buttons
11264	if ( event.type === "" || ( !which || which === 1 ) ) {
11265		$openMenus = $( selector + " .sm-open" );
11266		if ( $openMenus.length !== 0 &&
11267			$( event.target ).closest( selector ).length === 0 ) {
11268
11269			menuClose( $openMenus, true );
11270		}
11271	}
11272} );
11273
11274$document.on( "mouseover focusin", selector + " .item", function( event ) {
11275	var $elm = $( event.currentTarget ),
11276		$parent = $elm.parent(),
11277		$container = $parent.closest( selector );
11278
11279	// Clear the timeout for open/closing menus
11280	clearTimeout( globalTimeout );
11281
11282	if ( event.type === "focusin" ) {
11283		menuDisplay( $container, $parent );
11284	} else {
11285		globalTimeout = setTimeout( function() {
11286			menuDisplay( $container, $parent );
11287		}, hoverDelay );
11288	}
11289} );
11290
11291/*
11292 * Keyboard bindings
11293 */
11294$document.on( "keydown", selector + " [role=menuitem]", function( event ) {
11295	var menuItem = event.currentTarget,
11296		which = event.which,
11297		$menuItem = $( menuItem ),
11298		hasPopup = $menuItem.attr( "aria-haspopup" ) === "true",
11299		$menu = $menuItem.parent().closest( "[role^='menu']" ),
11300		inMenuBar = $menu.attr( "role" ) === "menubar",
11301		$menuLink, $parentMenu, $parent, $subMenu, result,
11302		menuitemSelector, isOpen, menuItemOffsetTop, menuContainer;
11303
11304	// Define keycodes. (Make const when WET supports ES6)
11305	var TAB_KC = 9,
11306		ENTER_KC = 13,
11307		ESC_KC = 27,
11308		LEFT_KC = 37,
11309		UP_KC = 38,
11310		RIGHT_KC = 39,
11311		DOWN_KC = 40,
11312		SPACE_KC = 32;
11313
11314	if ( !( event.ctrlKey || event.altKey || event.metaKey ) ) {
11315
11316		// Tab key = Hide all sub-menus
11317		if ( which === TAB_KC ) {
11318			menuClose( $( selector + " .active" ), true );
11319
11320		//Enter or spacebar on a link = follow the link and close menus
11321		} else if ( menuItem.nodeName === "A" && menuItem.hasAttribute( "href" ) &&
11322			( which === ENTER_KC || which === SPACE_KC ) ) {
11323
11324			event.preventDefault();
11325			menuItem.click();
11326			menuClose( $( selector + " .active" ), true );
11327
11328		// Menu item is within a menu bar
11329		} else if ( inMenuBar ) {
11330
11331			// Left / right arrow = Previous / next menu item
11332			if ( which === LEFT_KC || which === RIGHT_KC ) {
11333				event.preventDefault();
11334				menuIncrement(
11335					$menu.find( "> li > a" ),
11336					$menuItem,
11337					which === LEFT_KC ? -1 : 1
11338				);
11339
11340			// Enter sub-menu
11341			} else if ( hasPopup && ( which === ENTER_KC || which === SPACE_KC || which === UP_KC || which === DOWN_KC ) ) {
11342				event.preventDefault();
11343				$parent = $menuItem.parent();
11344				$subMenu = $parent.find( ".sm" );
11345
11346				// Open the submenu if it is not already open
11347				if ( !$subMenu.hasClass( "open" ) ) {
11348					menuDisplay( $menu.closest( selector ), $parent );
11349				}
11350
11351				// Set focus on the first submenu item
11352				$subMenu.children( "li" ).eq( 0 ).find( menuItemSelector ).trigger( focusEvent );
11353
11354			// Hide sub-menus and set focus
11355			} else if ( which === ESC_KC ) {
11356				event.preventDefault();
11357				menuClose( $menu.closest( selector ).find( ".active" ), false );
11358
11359			// Letters only
11360			} else if ( which > 64 && which < 91 ) {
11361				event.preventDefault();
11362				selectByLetter(
11363					which,
11364					$menuItem.parent().find( "> ul > li > a, > ul > li > details > summary" ).get()
11365				);
11366			}
11367
11368		// Menu item is not within a menu bar
11369		} else {
11370			menuitemSelector = menuItemSelector;
11371
11372			// Up / down arrow = Previous / next menu item
11373			if ( which === UP_KC || which === DOWN_KC ) {
11374				event.preventDefault();
11375				menuIncrement(
11376					$menu.children( "li" ).find( menuitemSelector ),
11377					$menuItem,
11378					which === UP_KC ? -1 : 1
11379				);
11380
11381			// Enter, space, or right arrow with a submenu
11382			} else if ( hasPopup && ( which === ENTER_KC || which === SPACE_KC || which === RIGHT_KC ) ) {
11383				$parent = $menuItem.parent();
11384
11385				// Prevent handling by details.js polyfill
11386				event.stopImmediatePropagation();
11387				event.preventDefault();
11388
11389				// If the menu item is a summary element
11390				if ( menuItem.nodeName.toLowerCase( "summary" ) ) {
11391					isOpen = !!$parent.attr( "open" );
11392
11393					// Close any other open menus
11394					if ( !isOpen ) {
11395						$( parent )
11396							.closest( "[role^='menu']" )
11397							.find( "[aria-hidden=false]" )
11398							.parent()
11399							.find( "[aria-haspopup=true]" )
11400							.not( menuItem )
11401							.trigger( "click" );
11402
11403						// Ensure the opened menu is in view if in a mobile panel
11404						menuContainer = document.getElementById( "mb-pnl" );
11405						menuItemOffsetTop = menuItem.offsetTop;
11406						if ( $.contains( menuContainer, menuItem ) &&
11407							menuItemOffsetTop < menuContainer.scrollTop ) {
11408
11409							menuContainer.scrollTop = menuItemOffsetTop;
11410						}
11411
11412						// Ensure the menu is opened or stays open
11413						$menuItem.trigger( "click" );
11414					}
11415
11416					// Update the WAI-ARIA states and move focus to
11417					// the first submenu item
11418					$parent.children( "ul" )
11419						.attr( {
11420							"aria-expanded": "true",
11421							"aria-hidden": "false"
11422						} )
11423						.find( "[role=menuitem]:first" )
11424						.trigger( "setfocus.wb" );
11425				}
11426
11427			// Escape, left / right arrow without a submenu
11428			} else if ( which === ESC_KC || which === LEFT_KC || which === RIGHT_KC ) {
11429				$parent = $menu.parent();
11430				$parentMenu = $parent.closest( "[role^='menu']" );
11431				if ( which === LEFT_KC || which === RIGHT_KC ) {
11432					event.preventDefault();
11433				}
11434
11435				// If the parent menu is a menubar
11436				if ( $parentMenu.attr( "role" ) === "menubar" ) {
11437					$menuLink = $menu.siblings( "a" );
11438
11439					// Escape key = Close menu and return to menu bar item
11440					if ( which === ESC_KC ) {
11441						event.preventDefault();
11442						$menuLink.trigger( focusEvent );
11443
11444						// Close the menu but keep the referring link active
11445						setTimeout( function() {
11446							menuClose( $menuLink.parent(), false );
11447						}, 100 );
11448
11449					// Left / right key = Next / previous menu bar item
11450					} else if ( $parentMenu.attr( "role" ) === "menubar" ) {
11451						menuIncrement(
11452							$parentMenu.find( "> li > a" ),
11453							$menuLink,
11454							which === LEFT_KC ? -1 : 1
11455						);
11456					}
11457
11458				// Escape or left arrow: Go up a level if there is a higher-level
11459				// menu or close the current submenu if there isn't
11460				} else if ( which !== RIGHT_KC ) {
11461					$subMenu = $parentMenu.length !== 0 ? $menu : $menuItem;
11462
11463					// There is a higher-level menu
11464					if ( $parentMenu.length !== 0 ) {
11465						event.preventDefault();
11466						$menu.closest( "li" )
11467							.find( menuitemSelector )
11468							.trigger( "click" )
11469							.trigger( "setfocus.wb" );
11470
11471					// No higher-level menu but the current submenu is open
11472					} else if ( $menuItem.parent().children( "ul" ).attr( "aria-hidden" ) === "fal
11472se" ) {
11473						event.preventDefault();
11474						$menuItem
11475							.trigger( "click" )
11476							.trigger( "setfocus.wb" );
11477					}
11478				}
11479
11480			// Select a menu item in the current menu by the first letter
11481			} else if ( which > 64 && which < 91 ) {
11482				event.preventDefault();
11483				$parent = $menuItem.closest( "li" );
11484
11485				// Try to find a match in the next siblings
11486				result = selectByLetter(
11487					which,
11488					$parent.nextAll().find( menuitemSelector ).get()
11489				);
11490
11491				// If couldn't find a match, try the previous siblings
11492				if ( !result ) {
11493					result = selectByLetter(
11494						which,
11495						$parent.prevAll().find( menuitemSelector ).get()
11496					);
11497				}
11498			}
11499		}
11500	}
11501} );
11502
11503// Prevent Firefox from double-triggering menu behaviour
11504$document.on( "keyup", selector + " [role=menuitem]", function( event ) {
11505	event.preventDefault();
11506	return false;
11507} );
11508
11509// Close the mobile panel if switching to medium, large or extra large view
11510$document.on( "mediumview.wb largeview.wb xlargeview.wb", function() {
11511	var mobilePanel = document.getElementById( "mb-pnl" );
11512	if ( mobilePanel && mobilePanel.getAttribute( "aria-hidden" ) === "false" ) {
11513		$( mobilePanel ).trigger( "close.wb-overlay" );
11514	}
11515} );
11516
11517// Add the timer poke to initialize the plugin
11518wb.add( selector );
11519
11520} )( jQuery, window, document, wb );
11521
11522/**
11523 * @title WET-BOEW Multimedia PLayer
11524 * @overview An accessible multimedia player for <audio> and <video> tags
11525 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
11526 * @author WET Community
11527 */
11528/* globals YT */
11529( function( $, DOMPurify, window, wb, undef ) {
11530"use strict";
11531
11532/* Local scoped variables*/
11533var componentName = "wb-mltmd",
11534	selector = "." + componentName,
11535	initEvent = "wb-init" + selector,
11536	ctrls = selector + " .wb-mm-ctrls",
11537	dispCtrls = selector + " .display," + ctrls,
11538	template,
11539	i18n, i18nText,
11540	youtubeReadyEvent = "ready.youtube",
11541	captionsLoadedEvent = "ccloaded" + selector,
11542	captionsLoadFailedEvent = "ccloadfail" + selector,
11543	captionsVisibleChangeEvent = "ccvischange" + selector,
11544	renderUIEvent = "renderui" + selector,
11545	initializedEvent = "inited" + selector,
11546	youtubeEvent = "youtube" + selector,
11547	resizeEvent = "resize" + selector,
11548	templateLoadedEvent = "templateloaded" + selector,
11549	cuepointEvent = "cuepoint" + selector,
11550	captionClass = "cc_on",
11551	multimediaEvents = [
11552		"durationchange",
11553		"playing",
11554		"pause",
11555		"ended",
11556		"volumechange",
11557		"timeupdate",
11558		"waiting",
11559		"canplay",
11560		"seeked",
11561		"progress",
11562		captionsLoadedEvent,
11563		captionsLoadFailedEvent,
11564		captionsVisibleChangeEvent,
11565		cuepointEvent
11566	].join( " " ),
11567	$document = wb.doc,
11568	$window = wb.win,
11569
11570	/**
11571	 * @function init
11572	 * @param {jQuery Event} event Event that triggered the function call
11573	 */
11574	init = function( event ) {
11575
11576		// Start initialization
11577		// returns DOM object = proceed with init
11578		// returns undefined = do not proceed with init (e.g., already initialized)
11579		var eventTarget = wb.init( event, componentName, selector );
11580
11581		if ( eventTarget ) {
11582
11583			// Only initialize the i18nText once
11584			if ( !i18nText ) {
11585
11586				// YT workaround for when played inside a modal dialog, like with lightbox
11587				//
11588				// the issue is when the iFrame is moved, it reloads and then it doesn't emit the right event to
11589				// adjust the WET multimedia controller to represent its current state.
11590				//
11591				// This needs to be executed only once, that is why it is in the i18nText conditional
11592				window.addEventListener( "message", function( e ) {
11593					var data, frames, i, i_len, i_cache;
11594
11595					// Ensure we don't conflict with other postMessage listener
11596					try {
11597						data = JSON.parse( e.data );
11598
11599						// Only for a specific YT message
11600						if ( data.event && data.event === "infoDelivery" && data.info && data.info.playerState ) {
11601
11602							// Find the iFrame and evaluate if it needs to be reposted
11603							frames = document.getElementsByTagName( "iframe" );
11604
11605							i_len = frames.length;
11606							for ( i = 0; i < i_len; i++ ) {
11607								i_cache = frames[ i ];
11608								if ( i_cache.dataset.L2 && i_cache.contentWindow === e.source  ) {
11609
11610									// Prepare the event data and emulate the YT object for our event management need
11611									youTubeEvents.call( i_cache, {
11612										target: i_cache.parentElement.parentElement.object,
11613										data: data.info.playerState
11614									} );
11615									break;
11616								}
11617							}
11618						}
11619					} catch ( err ) {
11620
11621						/* swallow error */
11622					}
11623				} );
11624
11625				//
11626				// END YT workaround
11627				//
11628
11629				i18n = wb.i18n;
11630				i18nText = {
11631					play: i18n( "mmp-play" ),
11632					pause: i18n( "pause" ),
11633					volume: i18n( "volume" ),
11634					cc_on: i18n( "cc", "on" ),
11635					cc_off: i18n( "cc", "off" ),
11636					cc_error: i18n( "cc-err" ),
11637					fs: i18n( "fs" ),
11638					mute_on: i18n( "mute", "on" ),
11639					mute_off: i18n( "mute", "off" ),
11640					duration: i18n( "dur" ),
11641					position: i18n( "pos" )
11642				};
11643			}
11644
11645			if ( template === undef ) {
11646				template = "";
11647				$( eventTarget ).trigger( {
11648					type: "ajax-fetch.wb",
11649					fetch: {
11650						url: wb.getPath( "/assets" ) + "/mediacontrols.html"
11651					}
11652				} );
11653			} else if ( template !== "" ) {
11654				$( eventTarget ).trigger( templateLoadedEvent );
11655			}
11656		}
11657	},
11658
11659	/* helper functions*/
11660
11661	/**
11662	 * @method formatTime
11663	 * @description format a number of seconds to SMTPE Timecode format (HH:MM:SS.FF)
11664	 * @param {Float} time The time to format
11665	 * @returns {String} the formatted time
11666	 */
11667	formatTime = function( time ) {
11668		var index = 2,
11669			timecode = "",
11670			secondsIn, current, pad;
11671
11672		pad = function( number, digits ) {
11673			return new Array( Math.max( digits - String( number ).length + 1, 0 ) ).join( 0 ) + number;
11674		};
11675
11676		time = Math.floor( time );
11677
11678		//Loop to extract hours, minutes and seconds
11679		while ( index >= 0 ) {
11680
11681			//Get the number of seconds for the current iteration (hour, minute or second)
11682			secondsIn = Math.pow( 60, index );
11683			current = Math.floor( time / secondsIn );
11684
11685			if ( timecode !== "" ) {
11686				timecode += ":";
11687			}
11688
11689			timecode += pad( current, 2 );
11690			time -= secondsIn * current;
11691			index -= 1;
11692		}
11693		return timecode;
11694	},
11695
11696	/**
11697	 * @method parseTime
11698	 * @description parse an SMTPE Timecode string (HH:MM:SS.FF) or duration (45s) and returns the number of seconds for the timecode
11699	 * @param {String} time The timecode or duration string to parse
11700	 * @returns {Float} the number of seconds in time
11701	 */
11702	parseTime = function( time ) {
11703		var i, parts, timeStringPortion, partLength, seconds;
11704
11705		if ( time !== undef ) {
11706			if ( time.charAt( time.length - 1 ) === "s" ) {
11707
11708				//Duration parsing
11709				return parseFloat( time.substring( 0, time.length - 1 ) );
11710			} else {
11711
11712				//SMTPE Timecode Parsing
11713				parts = time.split( ":" ).reverse();
11714				seconds = 0;
11715
11716				for ( i = 0, partLength = parts.length; i < partLength; i += 1 ) {
11717					timeStringPortion = i === 0 ?
11718						parseFloat( parts[ i ] ) :
11719						parseInt( parts[ i ], 10 );
11720					seconds += timeStringPortion * Math.pow( 60, i );
11721				}
11722				return seconds;
11723			}
11724		}
11725		return -1;
11726	},
11727
11728	/*
11729	 * Peformant micro templater
11730	 * @credit: https://github.com/premasagar/tim/blob/master/tinytim.js
11731	 * @todo: caching
11732	 */
11733	tmpl = ( function() {
11734		var start = "{{",
11735			end = "}}",
11736
11737			// e.g. config.person.name
11738			path = "[a-z0-9_$][\\.a-z0-9_]*",
11739			pattern = new RegExp( start + "\\s*(" + path + ")\\s*" + end, "gi" );
11740		return function( template, data ) {
11741
11742			// Merge data into the template string
11743			return template.replace( pattern, function( tag, token ) {
11744				var path = token.split( "." ),
11745					len = path.length,
11746					lookup = data,
11747					i = 0;
11748				for ( ; i < len; i += 1 ) {
11749					lookup = lookup[ path[ i ] ];
11750
11751					// Property not found
11752					if ( lookup === undef ) {
11753						throw "tim: '" + path[ i ] + "' not found in " + tag;
11754					}
11755
11756					// Return the required value
11757					if ( i === len - 1 ) {
11758						return lookup;
11759					}
11760				}
11761			} );
11762		};
11763	}() ),
11764
11765	/**
11766	 * @method parseHtml
11767	 * @description parse an HTML fragment and extract embed captions
11768	 * @param {String} content The HTML fragment containing the captions
11769	 * @returns {Array} An array of captions objects (ex: {text: "Caption", begin: 0, end :10})
11770	 */
11771	parseHtml = function( content ) {
11772		var captions = [],
11773			captionSelector = ".wb-tmtxt",
11774			captionElements = content.find( captionSelector ),
11775			len = captionElements.length,
11776			i, captionElement, json, begin, end;
11777
11778		for ( i = 0; i !== len; i += 1 ) {
11779			captionElement = $( captionElements[ i ] );
11780			begin = -1;
11781			end = -1;
11782
11783			if ( captionElement.attr( "data-begin" ) !== undef ) {
11784				begin = parseTime( captionElement.attr( "data-begin" ) );
11785				end = captionElement.attr( "data-end" ) !== undef ?
11786					parseTime( captionElement.attr( "data-end" ) ) :
11787					parseTime( captionElement.attr( "data-dur" ) ) + begin;
11788			} else if ( captionElement.attr( "data" ) !== undef ) {
11789				json = captionElement.attr( "data" )
11790					.replace( /(begin|dur|end)/g, "\"$1\"" )
11791					.replace( /'/g, "\"" );
11792				json = $.parseJSON( json );
11793				begin = parseTime( json.begin );
11794				end = json.end !== undef ?
11795					parseTime( json.end ) :
11796					parseTime( json.dur ) + begin;
11797			}
11798
11799			//Removes nested captions if an
11800			captionElement = captionElement.clone();
11801			captionElement.find( captionSelector ).detach();
11802
11803			captions[ captions.length ] = {
11804				text: captionElement.html(),
11805				begin: begin,
11806				end: end
11807			};
11808		}
11809		return captions;
11810	},
11811
11812	/**
11813	 * @method parseXml
11814	 * @description parse an TTML (Xml) document and extract captions
11815	 * @param {String} content The TTML fragment containing the captions
11816	 * @returns {Array} An array of captions objects (ex: {text: "Caption", begin: 0, end :10})
11817	 */
11818	parseXml = function( content ) {
11819		var captions = [],
11820			captionSelector = "[begin]",
11821			parser = new DOMParser(),
11822			doc = parser.parseFromString( content, "application/xml" ),
11823			captionElements = doc.querySelectorAll( captionSelector ),
11824			len = captionElements.length,
11825			i, captionElement, begin, end;
11826
11827		for ( i = 0; i !== len; i += 1 ) {
11828			captionElement = captionElements[ i ];
11829
11830			begin = parseTime( captionElement.getAttribute( "begin" ) + "" );
11831			end = captionElement.hasAttribute( "end" ) ?
11832				parseTime( captionElement.getAttribute( "end" ) + "" ) :
11833				parseTime( captionElement.getAttribute( "dur" ) + "" ) + begin;
11834
11835			captions[ captions.length ] = {
11836				text: DOMPurify.sanitize( captionElement.textContent ),
11837				begin: begin,
11838				end: end
11839			};
11840		}
11841		return captions;
11842	},
11843
11844	/**
11845	 * @method loadCaptionsExternal
11846	 * @description Loads captions from an external source (HTML embed or TTML)
11847	 * @param {Object} elm The jQuery object for the multimedia player loading the captions
11848	 * @param {String} url The url for the captions resource to load
11849	 * @fires ccloaded.wb-mltmd
11850	 * @fires ccloadfail.wb-mltmd
11851	 */
11852	loadCaptionsExternal = function( elm, url ) {
11853		$.ajax( {
11854			url: url,
11855			dataType: "html",
11856
11857			//Filters out images and objects from the content to avoid loading them
11858			dataFilter: function( data ) {
11859				return data.replace( /<img|object [^>]*>/g, "" );
11860			},
11861			success: function( data ) {
11862
11863				var captionItems;
11864
11865				if ( data.indexOf( "<html" ) !== -1 ) {
11866
11867					// Sanitize the response
11868					captionItems = parseHtml( $( DOMPurify.sanitize( data, { WHOLE_DOCUMENT: true } ) ) );
11869				} else {
11870
11871					// Response is sanitized in the XML parser function
11872					captionItems = parseXml( data );
11873				}
11874
11875				if ( captionItems.length ) {
11876					elm.trigger( {
11877						type: captionsLoadedEvent,
11878						captions: captionItems
11879					} );
11880				} else {
11881					elm.trigger( {
11882						type: captionsLoadFailedEvent
11883					} );
11884				}
11885			},
11886			error: function( response, textStatus, errorThrown ) {
11887				elm.trigger( {
11888					type: captionsLoadFailedEvent,
11889					error: errorThrown
11890				} );
11891			}
11892		} );
11893	},
11894
11895	/**
11896	 * @method loadCaptionsInternal
11897	 * @description Loads same page captions emebed in HTML
11898	 * @param {Object} elm The jQuery object for the multimedia player loading the captions
11899	 * @param {Object}
11899 obj The jQUery object containing the captions
11900	 * @fires ccloaded.wb-mltmd
11901	 */
11902	loadCaptionsInternal = function( elm, obj ) {
11903		var captionItems = parseHtml( obj );
11904
11905		if ( captionItems.length ) {
11906			elm.trigger( {
11907				type: captionsLoadedEvent,
11908				captions: captionItems
11909			} );
11910		} else {
11911			elm.trigger( {
11912				type: captionsLoadFailedEvent
11913			} );
11914		}
11915	},
11916
11917	/**
11918	 * @method updateCaptions
11919	 * @description Update the captions for a multimedia player (called from the timeupdate event of the HTML5 media API)
11920	 * @param {Object} area The jQuery object for the element where captions are displayed
11921	 * @param {Float} seconds The current time of the media (use to sync the captions)
11922	 * @param {Object} captions The JavaScript object containing the captions
11923	 */
11924	updateCaptions = function( area, seconds, captions ) {
11925		var caption, i,
11926			captionsLength = captions.length;
11927
11928		// added &#160; (non-breaking space) to prevent caption space from collapsing
11929		// Used .html() instead of .append for performance purposes
11930		// https://jsperf.com/jquery-append-vs-html-list-performance/2
11931		area.html( "&#160;" );
11932
11933		for ( i = 0; i < captionsLength; i += 1 ) {
11934			caption = captions[ i ];
11935			if ( seconds >= caption.begin && seconds <= caption.end ) {
11936				area.html( $( "<div>" + caption.text + "</div>" ) );
11937			}
11938		}
11939	},
11940
11941	/**
11942	 * @method playerApi
11943	 * @description Normalizes the calls to the HTML5 media API
11944	 * @param {String} fn The function to call
11945	 * @param {object} args The arguments to send to the function call
11946	 */
11947	playerApi = function( fn, args ) {
11948		var $this, method;
11949
11950		switch ( fn ) {
11951		case "play":
11952			try {
11953				this.object.play();
11954			} catch ( ex ) {
11955				this.object.doPlay();
11956			}
11957			break;
11958		case "pause":
11959			try {
11960				this.object.pause();
11961			} catch ( ex ) {
11962				this.object.doPause();
11963			}
11964			break;
11965		case "getCaptionsVisible":
11966			return $( this ).hasClass( captionClass );
11967		case "setCaptionsVisible":
11968			$this = $( this );
11969			if ( args ) {
11970				$this.addClass( captionClass );
11971			} else {
11972				$this.removeClass( captionClass );
11973			}
11974			$this.trigger( captionsVisibleChangeEvent );
11975			break;
11976		case "fullscreen":
11977			if ( this.object.requestFullscreen ) {
11978				this.object.requestFullscreen();
11979			} else if ( this.object.webkitRequestFullscreen ) { /* Safari */
11980				this.object.webkitRequestFullscreen();
11981			} else if ( this.object.msRequestFullscreen ) { /* IE11 */
11982				this.object.msRequestFullscreen();
11983			}
11984			break;
11985		case "getBuffering":
11986			return this.object.buffering || false;
11987		case "setBuffering":
11988			this.object.buffering = args;
11989			break;
11990		case "getPreviousTime":
11991			return this.object.previousTime;
11992		case "setPreviousTime":
11993			this.object.previousTime = args;
11994			break;
11995		default:
11996			method = fn.charAt( 3 ).toLowerCase() + fn.substr( 4 );
11997			switch ( fn.substr( 0, 3 ) ) {
11998			case "get":
11999				return typeof this.object[ method ] !== "function" ?
12000					this.object[ method ] :
12001					this.object[ method ]();
12002			case "set":
12003				typeof this.object[ method ] !== "function" ?
12004					this.object[ method ] = args :
12005					this.object[ fn ]( args );
12006			}
12007		}
12008	},
12009
12010	/**
12011	 * @method youTubeApi
12012	 * @description Normalizes the calls to the YouTube API
12013	 * @param {String} fn The function to call
12014	 * @param {object} args The arguments to send to the function call
12015	 */
12016	youTubeApi = function( fn, args ) {
12017		var $media = $( this.object.getIframe() ),
12018			state;
12019
12020		switch ( fn ) {
12021		case "play":
12022			this.object.wasMutedPlay = this.object.isMuted();
12023			return this.object.playVideo();
12024		case "pause":
12025			return this.object.pauseVideo();
12026		case "getPaused":
12027			state = this.object.getPlayerState();
12028			return state === -1 || state === 0 || state === 2 || state === 5;
12029		case "getPlayed":
12030			return this.object.getPlayerState() > -1;
12031		case "getEnded":
12032			return this.object.getPlayerState() === 0;
12033		case "getDuration":
12034			return this.object.getDuration();
12035		case "getCurrentTime":
12036			return this.object.getCurrentTime();
12037		case "setCurrentTime":
12038			return this.object.seekTo( args, true );
12039		case "fullscreen":
12040			return this.object.getIframe().requestFullscreen();
12041		case "getMuted":
12042			if ( !this.object.playedOnce && this.object.wasMutedPlay ) {
12043				state = this.object.wasMutedPlay;
12044				this.object.playedOnce = true;
12045				return state;
12046			} else {
12047				return this.object.isMuted();
12048			}
12049		case "setMuted":
12050			if ( args ) {
12051				this.object.mute();
12052			} else {
12053				this.object.unMute();
12054			}
12055			setTimeout( function() {
12056				$media.trigger( "volumechange" );
12057			}, ( wb.isReady ? 50 : 500 ) );
12058			break;
12059		case "getVolume":
12060			return this.object.getVolume() / 100;
12061		case "setVolume":
12062			this.object.setVolume( args * 100 );
12063			setTimeout( function() {
12064				$media.trigger( "volumechange" );
12065			}, 50 );
12066			break;
12067		case "getCaptionsVisible":
12068			return $( this ).hasClass( captionClass );
12069		case "setCaptionsVisible":
12070			if ( args ) {
12071				$( this ).addClass( captionClass );
12072				try {
12073					this.object.loadModule( "cc" );
12074					this.object.setOption( "cc", "track", { languageCode: this.object.getOption( "cc", "tracklist" )[ 0 ].languageCode } );
12075				} catch ( e ) {
12076					this.object.loadModule( "captions" );
12077					this.object.setOption( "captions", "track", { languageCode: this.object.getOption( "captions", "tracklist" )[ 0 ].languageCode } );
12078				}
12079			} else {
12080				$( this ).removeClass( captionClass );
12081				this.object.unloadModule( "cc" );
12082				this.object.unloadModule( "captions" );
12083			}
12084			$media.trigger( "ccvischange" );
12085		}
12086	},
12087
12088	/**
12089	 * @method youTubeEvents
12090	 * @description Youtube API event manager
12091	 * @param {object} event The event object fior the triggered event
12092	 */
12093	youTubeEvents = function( event ) {
12094		var media = event.target.getIframe(),
12095			$media = $( media ),
12096			timeline = function() {
12097				$media.trigger( "timeupdate" );
12098			},
12099			$mltmPlayerElm,
12100			mltmPlayerElm,
12101			isMuted;
12102
12103		switch ( event.data ) {
12104		case null: // init
12105			$media
12106				.trigger( "canplay" )
12107				.trigger( "durationchange" );
12108
12109			// Put video on mute if the video is muted on init, run once
12110			$mltmPlayerElm = $media.parentsUntil( selector ).parent();
12111
12112			// Mute the player, GUI
12113			if ( $mltmPlayerElm.data( "putMutedOnInit" ) ) {
12114				youTubeApi.call( $mltmPlayerElm.get( 0 ), "setMuted", true );
12115				$mltmPlayerElm.data( "putMutedOnInit", false );
12116			}
12117			break;
12118		case -1:
12119			event.target.unMute();
12120			$media.trigger( "durationchange" );
12121			break;
12122		case 0:
12123			$media.trigger( "ended" );
12124			media.timeline = clearInterval( media.timeline );
12125			break;
12126		case 1: // play
12127
12128			// Get the media player
12129			$mltmPlayerElm = $media.parentsUntil( selector ).parent();
12130			mltmPlayerElm = $mltmPlayerElm.get( 0 );
12131
12132			// Need to be muted here
12133			isMuted = mltmPlayerElm.player( "getMuted" );
12134
12135			// Reset the close caption state when iframe was reloaded
12136			if ( media.dataset.L2 ) {
12137				youTubeApi.call( mltmPlayerElm, "setCaptionsVisible", $mltmPlayerElm.hasClass( captionClass ) );
12138			}
12139
12140			// Play
12141			$media
12142				.trigger( "canplay" )
12143				.trigger( "play" )
12144				.trigger( "playing" );
12145
12146			// Reset muted as needed because youtube onMute by default when playing
12147			if ( isMuted ) {
12148				youTubeApi.call( mltmPlayerElm, "setMuted", true );
12149			}
12150
12151			media.timeline = setInterval( timeline, 250 );
12152			break;
12153		case 2:
12154			$media.trigger( "pause" );
12155			media.timeline = clearInterval( media.timeline );
12156			break;
12157		case 3:
12158			media.timeline = clearInterval( media.timeline );
12159			break;
12160		}
12161	},
12162
12163	youTubeAPIReady = function() {
12164		var youTube = window.youTube;
12165		youTube.ready = true;
12166		$document.trigger( youtubeReadyEvent );
12167	},
12168
12169	onResize = function() {
12170		$( selector + " object, " + selector + " iframe, " +  selector + " video" ).trigger( resizeEvent );
12171	};
12172
12173$document.on( "timerpoke.wb " + initEvent, selector, init );
12174
12175$window.on( "resize", onResize );
12176
12177$document.on( "ready", onResize );
12178
12179$document.on( "ajax-fetched.wb " + templateLoadedEvent, selector, function( event ) {
12180	var $this = $( this );
12181
12182	if ( event.type === "ajax-fetched" ) {
12183		template = event.fetch.pointer.html();
12184
12185		//Notify all player waiting for the controls to load
12186		$this = $( selector );
12187	}
12188
12189	$this.trigger( {
12190		type: initializedEvent
12191	} );
12192} );
12193
12194$document.on( initializedEvent, selector, function( event ) {
12195	if ( event.namespace === componentName ) {
12196		var $this = $( this ),
12197			$media = $this.children( "audio, video" ).eq( 0 ),
12198			captions = $media.children( "track[kind='captions']" ).attr( "src" ) || undef,
12199			id = $this.attr( "id" ),
12200			mId = $media.attr( "id" ) || id + "-md",
12201			type = $media.is( "audio" ) ? "audio" : "video",
12202			title = $media.attr( "title" ) || "",
12203			width = type === "video" ? $media.attr( "width" ) || $media.width() : 0,
12204			height = type === "video" ? $media.attr( "height" ) || $media.height() : 0,
12205			settings = wb.getData( $this, componentName ),
12206			data = $.extend( {
12207				media: $media,
12208				captions: captions,
12209				id: id,
12210				mId: mId,
12211				type: type,
12212				title: title,
12213				height: height,
12214				width: width
12215			}, i18nText ),
12216			media = $media.get( 0 ),
12217			youTube = window.youTube,
12218			url,
12219			i18n = wb.i18n;
12220
12221		if ( $media.attr( "id" ) === undef ) {
12222			$media.attr( "id", mId );
12223		}
12224
12225		if ( settings !== undef ) {
12226			data.shareUrl = settings.shareUrl;
12227			data.fullscreen = settings.fullscreenBtn || false;
12228		}
12229
12230		$this.addClass( type );
12231
12232		if ( $media.find( "[type='video/youtube']" ).length > 0 ) {
12233
12234			// lets tweak some variables and start the load sequence
12235			url = wb.getUrlParts( $this.find( "[type='video/youtube']" ).attr( "src" ) );
12236
12237			// lets set the flag for the call back
12238			data.youTubeId = url.params.v ? url.params.v : url.pathname.substr( 1 );
12239
12240			// Defaults config set on the video element
12241			data.isInitMuted = $media.get( 0 ).muted;
12242
12243			if ( youTube.ready === false ) {
12244				$document.one( youtubeReadyEvent, function() {
12245					$this.trigger( youtubeEvent, data );
12246				} );
12247			} else {
12248				$this.trigger( youtubeEvent, data );
12249			}
12250
12251			// finally lets load safely
12252			return Modernizr.load( {
12253				load: "https://www.youtube.com/iframe_api",
12254
12255				//possible solution for multimedia and doaction conflict in corporate network
12256				complete: function() {
12257
12258					// Ensure that Youtube API is loading the iframe and if it fails, ensure that it will show a message, like accessing the web via our GC network.
12259					setTimeout( function() {
12260						var resources, arrIframesYt, $notifText;
12261
12262						resources = window.performance.getEntriesByType( "resource" );
12263
12264						/* get all the iframe initiators that have the same YT url id */
12265						arrIframesYt  = resources.filter( function( obj ) {
12266
12267							return obj.initiatorType === "iframe" && obj.name.includes( data.youTubeId );
12268
12269						} );
12270
12271
12272						/* if none found, most probably wb is loaded in restricted network so wb.ready() is triggered for not preventing other wb components to load*/
12273						if ( arrIframesYt.length < 1 ) {
12274							if ( !wb.isReady ) {
12275
12276								// show the video notification error
12277								$notifText = $( "<div aria-live='polite' class='pstn-lft-xs bg-dark text-white'><p class='mrgn-tp-md mrgn-rght-md mrgn-bttm-md mrgn-lft-md'>" + i18n( "msgYoutubeNotLoad" ) + "</p></div>" );
12278								$this.prepend( $notifText );
12279								data.notifyText = $notifText;
12280								wb.ready( $this, componentName );
12281							}
12282						}
12283					}, 1000 );
12284
12285				}
12286			} );
12287
12288		} else if ( media.error === null && media.currentSrc !== "" && media.currentSrc !== undef ) {
12289			$this.trigger( renderUIEvent, [ type, data ] );
12290
12291			// Identify that initialization has completed
12292			wb.ready( $this, componentName );
12293		}
12294	}
12295} );
12296
12297/*
12298 *  Youtube Video mode Event
12299 */
12300$document.on( youtubeEvent, selector, function( event, data ) {
12301	if ( event.namespace === componentName ) {
12302		var mId = data.mId,
12303			$this = $( event.currentTarget ),
12304			$media, ytPlayer;
12305
12306		ytPlayer = new YT.Player( mId, {
12307			videoId: data.youTubeId,
12308			playerVars: {
12309				autoplay: 0,
12310				controls: 0,
12311				origin: wb.pageUrlParts.host,
12312				modestbranding: 1,
12313				rel: 0,
12314				showinfo: 0,
12315				html5: 1,
12316				cc_load_policy: 1
12317			},
12318			events: {
12319				onReady: function( event ) {
12320					if ( data.notifyText ) {
12321						data.notifyText.hide();
12322					}
12323					onResize();
12324					youTubeEvents( event );
12325					if ( !wb.isReady ) {
12326						wb.ready( $this, componentName );
12327					}
12328				},
12329				onStateChange: youTubeEvents,
12330				onApiChange: function() {
12331
12332					//If captions were enabled before the module was ready, re-enable them
12333					var t = $this.get( 0 );
12334					t.player( "setCaptionsVisible", t.player( "getCaptionsVisible" ) );
12335				},
12336				onError: function() {
12337					console.warn( "There is an issue loading the Youtube player" );
12338				}
12339			}
12340		} );
12341
12342		$this.addClass( "youtube" );
12343
12344		$media = $this.find( "#" + mId ).attr( "tabindex", -1 );
12345
12346		data.media = $media;
12347		data.ytPlayer = ytPlayer;
12348
12349		// The fullscreen button is not visible by default because there are no controls when in full screen.
12350		if ( data.fullscreen ) {
12351			$this.attr( "data-fullscreen-btn", true );
12352		}
12353
12354		// Detect if the YT player reloads, like when magnific Popup show the modal, because it moves the iframe
12355		// and then the iframe gets refreshed and reloaded. So the issue is that the iframe stops emitting the event
12356		// needed to adjust the multimedia player controler, like the "onStateChange" event.
12357		$media.on( "load", function( evt ) {
12358
12359			var elm = evt.currentTarget,
12360				ds = elm.dataset;
12361
12362			// Do nothing on the first load and add a flag to indicate it is loaded a second time
12363			if ( ds.L1 ) {
12364				ds.L2 = true;
12365			} else {
12366				ds.L1 = true;
12367			}
12368		} );
12369
12370		$this.trigger( renderUIEvent, [ "youtube", data ] );
12371	}
12372} );
12373
12374$document.on( renderUIEvent, selector, function( event, type, data ) {
12375	if ( event.namespace === componentName ) {
12376		var $this = $( event.currentTarget ),
12377			captionsUrl = wb.getUrlParts( data.captions ),
12378			currentUrl = wb.getUrlParts( window.location.href ),
12379			$media = data.media,
12380			$eventReceiver;
12381
12382		$media
12383			.after( tmpl( template, data ) )
12384			.wrap( "<div class=\"display\"></div>" );
12385
12386		$eventReceiver = $media.is( "object" ) ? $media.children( ":first-child" ) : $media;
12387
12388		// Create an adapter for the event management
12389		$eventReceiver.on( multimediaEvents, function( event ) {
12390			$this.trigger( event );
12391		} );
12392
12393		this.object = data.ytPlayer || $media.get( 0 );
12394		this.player = ( data.ytPlayer ) ? youTubeApi : playerApi;
12395
12396		// Trigger the duration change for cases where the event was called before the event binding
12397		if ( type !== "youtube" && !isNaN( this.player( "getDuration" ) ) ) {
12398			$eventReceiver.trigger( "durationchange" );
12399		}
12400
12401		// Load the progress polyfill if needed
12402		$this.find( "progress" ).trigger( "wb-init.wb-progress" );
12403
12404		// Load the slider polyfill if needed
12405		$this.find( "input[type='range']" ).trigger( "wb-init.wb-slider" );
12406
12407		// Create the share widgets if needed
12408		if ( data.shareUrl !== undef ) {
12409			$( "<div class='wb-share' data-wb-share='{\"type\": \"" +
12410				( type === "audio" ? type : "video" ) + "\", \"title\": \"" +
12411				data.title.replace( /'/g, "&apos;" ) + "\", \"url\": \"" + data.shareUrl +
12412				"\", \"pnlId\": \"" + data.id + "-shr\"}'></div>" )
12413				.insertBefore( $media.parent() )
12414				.trigger( "wb-init.wb-share" );
12415		}
12416
12417		if ( data.isInitMuted ) {
12418			$this.data( "putMutedOnInit", true );
12419		} else if ( !data.ytPlayer && this.object.muted ) {
12420			$media.trigger( "volumechange" );
12421		}
12422
12423		if ( data.captions === undef ) {
12424			return 1;
12425		}
12426
12427		// Load the captions
12428		if ( currentUrl.absolute.replace( currentUrl.hash || "#", "" ) !== captionsUrl.absolute.replace( captionsUrl.hash || "#", "" ) ) {
12429			loadCaptionsExternal( $media, captionsUrl.absolute );
12430		} else {
12431			loadCaptionsInternal( $media, $( "#" + wb.jqEscape( captionsUrl.hash.substring( 1 ) ) ) );
12432		}
12433
12434		// The fullscreen button is not visible by default because there are no controls when in full screen.
12435		if ( data.fullscreen ) {
12436			$this.attr( "data-fullscreen-btn", true );
12437		}
12438	}
12439} );
12440
12441/*
12442 * UI Bindings
12443 */
12444
12445$document.on( "click", selector, function( event ) {
12446	var $target = $( event.target ),
12447		className = $target.attr( "class" ) || "";
12448
12449	// Ignore middle and right mouse buttons
12450	if ( event.which === 2 || event.which === 3 ) {
12451		return true;
12452	}
12453
12454	// Optimized multiple class tests to include child glyphicon because Safari was reporting the click event
12455	// from the child span not the parent button, forcing us to have to check for both elements
12456	// JSPerf for multiple class matching https://jsperf.com/hasclass-vs-is-stackoverflow/7
12457	if (  /playpause|-play|-pause|display/.test( className ) || $target.is( "object" ) || $target.is( "video" ) ) {
12458		this.player( "getPaused" ) || this.player( "getEnded" ) ? this.player( "play" ) : this.player( "pause" );
12459	} else if ( /(^|\s)cc\b|-subtitles/.test( className ) && !$target.attr( "disabled" ) && !$target.parent().attr( "disabled" ) ) {
12460		this.player( "setCaptionsVisible", !this.player( "getCaptionsVisible" ) );
12461	} else if ( /\bmute\b|-volume-(up|off)/.test( className ) ) {
12462		this.player( "setMuted", !this.player( "getMuted" ) );
12463	} else if ( $target.is( "progress" ) || $target.hasClass( "progress" ) || $target.hasClass( "progress-bar" ) ) {
12464		this.player( "setCurrentTime", this.player( "getDuration" ) * ( ( event.pageX - $target.offset().left ) / $target.width() ) );
12465	} else if ( /\brewind\b|-backward/.test( className ) ) {
12466		this.player( "setCurrentTime", this.player( "getCurrentTime" ) - this.player( "getDuration" ) * 0.05 );
12467	} else if ( /\bfastforward\b|-forward/.test( className ) ) {
12468		this.player( "setCurrentTime", this.player( "getCurrentTime" ) + this.player( "getDuration" ) * 0.05 );
12469	} else if ( className.includes( "cuepoint" ) ) {
12470		$( this ).trigger( { type: "cuepoint", cuepoint: $target.data( "cuepoint" ) } );
12471	} else if ( /fullscreen|fs/.test( className ) ) {
12472		this.player( "fullscreen" );
12473	}
12474} );
12475
12476$document.on( "input change", selector, function( event ) {
12477	var target = event.target;
12478
12479	if ( $( target ).hasClass( "volume" ) ) {
12480		event.currentTarget.player( "setMuted", false );
12481		event.currentTarget.player( "setVolume", target.value / 100 );
12482	}
12483} );
12484
12485$document.on( "keydown", dispCtrls, function( event ) {
12486	var playerTarget = event.currentTarget.parentNode,
12487		which = event.which,
12488		volume = 0,
12489		step = 0.05,
12490		$playerTarget = $( playerTarget );
12491
12492	if ( !( event.ctrlKey || event.altKey || event.metaKey ) ) {
12493		switch ( which ) {
12494		case 32:
12495
12496			// Mute/unmute if focused on the mute/unmute button or volume input.
12497			if ( $( event.target ).hasClass( "mute" ) || event.target.nodeName === "INPUT" ) {
12498				$playerTarget.find( ".mute" ).trigger( "click" );
12499			} else if ( $( event.target ).hasClass( "fs" ) ) {
12500
12501				// Enter full screen if focused on the full screen button
12502				$playerTarget.find( ".fs" ).trigger( "click" );
12503			} else if ( $( event.target ).hasClass( "cc" ) ) {
12504
12505				// Show/hide captions if focused on the closed captions button.
12506				$playerTarget.find( ".cc" ).trigger( "click" );
12507			} else {
12508
12509				// Play/pause if focused on anything else (i.e. the video itself, play/pause button or progress bar).
12510				$playerTarget.find( ".playpause" ).trigger( "click" );
12511			}
12512			break;
12513
12514		case 37:
12515			playerTarget.player( "setCurrentTime", this.parentNode.player( "getCurrentTime" ) - this.parentNode.player( "getDuration" ) * 0.05 );
12516			break;
12517
12518		case 39:
12519			playerTarget.player( "setCurrentTime", this.parentNode.player( "getCurrentTime" ) + this.parentNode.player( "getDuration" ) * 0.05 );
12520			break;
12521
12522		case 38:
12523			volume = Math.round( playerTarget.player( "getVolume" ) * 100 ) / 100 + step;
12524			playerTarget.player( "setVolume", volume < 1 ? volume : 1 );
12525			break;
12526
12527		case 40:
12528			volume = Math.round( playerTarget.player( "getVolume" ) * 100 ) / 100 - step;
12529			playerTarget.player( "setVolume", volume > 0 ? volume : 0 );
12530			break;
12531
12532		default:
12533			return true;
12534		}
12535		return false;
12536	}
12537} );
12538
12539$document.on( "keyup", ctrls, function( event ) {
12540	if ( event.which === 32 && !( event.ctrlKey || event.altKey || event.metaKey ) ) {
12541
12542		// Allows the spacebar to be used for play/pause without double triggering
12543		return false;
12544	}
12545} );
12546
12547$document.on( "wb-activate", selector, function() {
12548	this.player( "play" );
12549} );
12550
12551$document.on( "closed.wb-overlay", ".wb-overlay", function( event ) {
12552	var mltmdPlayer = event.currentTarget.querySelector( selector );
12553	if ( mltmdPlayer ) {
12554		mltmdPlayer.player( "pause" );
12555	}
12556} );
12557
12558$document.on( multimediaEvents, selector, function( event, simulated ) {
12559	var eventTarget = event.currentTarget,
12560		eventType = event.type,
12561		eventNamespace = event.namespace,
12562		$this = $( eventTarget ),
12563		invStart = "<span class='wb-inv'>",
12564		invEnd = "</span>",
12565		currentTime, $button, $slider, buttonData, isPlay, isMuted, isCCVisible, skipTo, volume;
12566	switch ( eventType ) {
12567	case "playing":
12568	case "pause":
12569	case "ended":
12570		isPlay = eventType === "playing";
12571		$button = $this.find( ".playpause" );
12572		buttonData = $button.data( "state-" + ( isPlay ? "off" : "on" ) );
12573		if ( isPlay ) {
12574			$this.addClass( "playing" );
12575			$this.find( ".progress" ).addClass( "active" );
12576		} else {
12577			if ( eventType === "ended" ) {
12578				this.loading = clearTimeout( this.loading );
12579			}
12580			$this.removeClass( "playing" );
12581		}
12582		$button
12583			.attr( "title", buttonData )
12584			.children( "span" )
12585			.toggleClass( "glyphicon-play", !isPlay )
12586			.toggleClass( "glyphicon-pause", isPlay )
12587			.html( invStart + buttonData + invEnd );
12588		break;
12589
12590	case "volumechange":
12591		isMuted = eventTarget.player( "getMuted" );
12592		$button = $this.find( ".mute" );
12593		buttonData = $button.data( "state-" + ( isMuted ? "off" : "on" ) );
12594		volume = eventTarget.player( "getVolume" ) * 100;
12595		$button
12596			.attr( {
12597				title: buttonData,
12598				"aria-pressed": isMuted
12599			} )
12600			.children( "span" )
12601			.toggleClass( "glyphicon-volume-up", !isMuted )
12602			.toggleClass( "glyphicon-volume-off", isMuted )
12603			.html( invStart + buttonData + invEnd );
12604		$slider = $this.find( "input[type='range']" );
12605		$slider[ 0 ].value = isMuted ? 0 : volume;
12606		$slider.trigger( "wb-update.wb-slider" );
12607		break;
12608
12609	case "timeupdate":
12610		currentTime = eventTarget.player( "getCurrentTime" );
12611		$this.find( "progress" )
12612			.attr(
12613				"value",
12614				Math.round( currentTime / eventTarget.player( "getDuration" ) * 1000 ) / 10
12615			).trigger( "wb-update.wb-progress" );
12616
12617		$this.find( ".wb-mm-tmln-crrnt span:nth-child(2)" )
12618			.text( formatTime( currentTime ) );
12619
12620		if ( $this.hasClass( captionClass ) && $.data( eventTarget, "captions" ) !== undef ) {
12621			updateCaptions(
12622				$this.find( ".wb-mm-cc" ),
12623				currentTime,
12624				$.data( eventTarget, "captions" )
12625			);
12626		}
12627		break;
12628
12629	case "durationchange":
12630		$this.find( ".wb-mm-tmln-ttl span:nth-child(2)" )
12631			.text( formatTime( eventTarget.player( "getDuration" ) ) );
12632
12633		// Skip to pointer from the querystring
12634		skipTo = wb.pageUrlParts.params[ event.target.id ];
12635		if ( skipTo ) {
12636			skipTo = parseTime( skipTo );
12637			eventTarget.player( "setCurrentTime", skipTo );
12638		}
12639		break;
12640
12641	case "ccloaded":
12642		if ( eventNamespace === componentName ) {
12643			$.data( eventTarget, "captions", event.captions );
12644		}
12645		break;
12646
12647	case "ccloadfail":
12648		if ( eventNamespace === componentName ) {
12649			if ( !$this.hasClass( "errmsg" ) ) {
12650				$this.addClass( "cc_on errmsg" )
12651					.find( ".wb-mm-cc" )
12652					.append( "<div>" + i18nText.cc_error + "</div>" )
12653					.end()
12654					.find( ".cc" )
12655					.attr( "disabled", "" )
12656					.removeAttr( "aria-pressed" );
12657			}
12658		}
12659		break;
12660
12661	case "ccvischange":
12662		if ( eventNamespace === componentName ) {
12663			isCCVisible = eventTarget.player( "getCaptionsVisible" );
12664			$button = $this.find( ".cc" );
12665			buttonData = $button.data( "state-" + ( isCCVisible ? "off" : "on" ) );
12666			$button.attr( {
12667				title: buttonData,
12668				"aria-pressed": isCCVisible
12669			} ).children( "span" ).html( invStart + buttonData + invEnd );
12670		}
12671		break;
12672
12673	case "waiting":
12674		if ( !simulated ) {
12675			$document.off( "progress", selector );
12676		}
12677		this.loading = setTimeout( function() {
12678			$this.addClass( "waiting" );
12679		}, 500 );
12680		break;
12681
12682	case "canplay":
12683	case "seeked":
12684		this.loading = clearTimeout( this.loading );
12685		$this.removeClass( "waiting" );
12686		break;
12687	case "cuepoint":
12688		eventTarget.player( "setCurrentTime", parseTime( event.cuepoint ) );
12689		break;
12690	}
12691} );
12692
12693// Fallback for browsers that don't implement the waiting events
12694$document.on( "progress", selector, function( event ) {
12695	var eventTarget = event.currentTarget,
12696		$this = $( eventTarget );
12697
12698	// Waiting detected
12699	if ( this.player( "getPaused" ) === false && this.player( "getCurrentTime" ) === this.player( "getPreviousTime" ) ) {
12700		if ( eventTarget.player( "getBuffering" ) === false ) {
12701			eventTarget.player( "setBuffering", true );
12702			$this.trigger( "waiting", true );
12703		}
12704
12705	// Waiting has ended
12706	} else if ( eventTarget.player( "getBuffering" ) === true ) {
12707		eventTarget.player( "setBuffering", false );
12708		$this.trigger( "canplay", true );
12709	}
12710	eventTarget.player( "setPreviousTime", eventTarget.player( "getCurrentTime" ) );
12711} );
12712
12713$document.on( resizeEvent, selector, function( event ) {
12714	if ( event.namespace === componentName ) {
12715		var media = event.target,
12716			$media = $( media ),
12717			ratio, newHeight;
12718
12719		if ( $( event.currentTarget ).hasClass( "video" ) ) {
12720			if ( media.videoWidth === 0 || media.videoWidth === undef ) {
12721				ratio = $media.attr( "height" ) / $media.attr( "width" );
12722
12723				// Calculate the new height based on the specified ratio or assume a default 16:9 ratio
12724				newHeight = Math.round( $media.width() * ( !isNaN( ratio ) ? ratio : 0.5625 ) );
12725
12726				$media.css( "height", newHeight + "px" );
12727			} else {
12728				$media.css( "height", "" );
12729			}
12730		}
12731	}
12732} );
12733
12734window.onYouTubeIframeAPIReady = youTubeAPIReady;
12735
12736window.youTube = {
12737	ready: false
12738};
12739
12740wb.add( selector );
12741
12742} )( jQuery, DOMPurify, window, wb );
12743
12744/**
12745 * @title WET-BOEW NavCurrent
12746 * @overview Identify URL in a navigation system that matches current page URL or a URL in the breadcrumb trail. Call by applying .trigger( "navcurr.wb", breadcrumb ) where the breadcrumb parameter is an optional object (DOM or jQuery)
12747 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
12748 * @author @pjackson28
12749 */
12750( function( $, window, wb ) {
12751"use strict";
12752
12753/*
12754 * Variable and function definitions.
12755 * These are global to the plugin - meaning that they will be initialized once per page,
12756 * not once per instance of plugin on the page. So, this is a good place to define
12757 * variables that are common to all instances of the plugin on a page.
12758 */
12759var componentName = "wb-navcurr",
12760	selector = "." + componentName,
12761	$document = wb.doc,
12762	breadcrumbLinksArray, breadcrumbLinksUrlArray,
12763
12764	/**
12765	 * @method init
12766	 * @param {jQuery Event} event Event that triggered the function call
12767	 * @param {jQuery DOM element | DOM element} breadcrumb Optional breadcrumb element
12768	 * @param {string} classNameOverride Optional class name override (default is wb-navcurr)
12769	 */
12770	init = function( event, breadcrumb, classNameOverride ) {
12771		if ( event.namespace === "wb" ) {
12772
12773			// Start initialization
12774			// returns DOM object = proceed with init
12775			// returns undefined = do not proceed with init (e.g., already initialized)
12776			var menu = wb.init( event.target, componentName, selector ),
12777				menuLinks = menu.getElementsByTagName( "a" ),
12778				menuLinksArray = [],
12779				menuLinksUrlArray = [],
12780				windowLocation = window.location,
12781				pageUrl = windowLocation.hostname + windowLocation.pathname.replace( /^([^/])/, "/$1" ),
12782				pageUrlQuery = windowLocation.search,
12783				match = false,
12784				className = classNameOverride ? classNameOverride : componentName,
12785				child, len, i, j, link, linkHref, linkUrl, linkQuery, linkQueryLen,
12786				localBreadcrumbLinks, localBreadcrumbLinksArray, localBreadcrumbLinksUrlArray,
12787				localBreadcrumbQuery, localBreadcrumbLinkUrl;
12788
12789			if ( menu ) {
12790
12791				// Try to find a match with the page Url and cache link + Url for later if no match found
12792				// Perform the check and caching in reverse to go from more specific links to more general links
12793				for ( i = menuLinks.length - 1; i !== -1; i -= 1 ) {
12794					link = menuLinks[ i ];
12795					linkHref = link.getAttribute( "href" );
12796					if ( linkHref !== null ) {
12797						if ( linkHref.length !== 0 && linkHref.charAt( 0 ) !== "#" ) {
12798							linkUrl = link.hostname + link.pathname.replace( /^([^/])/, "/$1" );
12799							linkQuery = link.search;
12800							linkQueryLen = linkQuery.length;
12801							if ( pageUrl.slice( -linkUrl.length ) === linkUrl && ( linkQueryLen === 0 || pageUrlQuery.slice( -linkQueryLen ) === linkQuery ) ) {
12802								match = true;
12803								break;
12804							}
12805							menuLinksArray.push( link );
12806							menuLinksUrlArray.push( linkUrl );
12807						}
12808					}
12809				}
12810
12811				// No page Url match found, try a breadcrumb link match instead
12812				if ( !match && breadcrumb ) {
12813
12814					// Check to see if the data has been cached already
12815					if ( !breadcrumbLinksArray ) {
12816
12817						// Pre-process the breadcrumb links
12818						localBreadcrumbLinksArray = [];
12819						localBreadcrumbLinksUrlArray = [];
12820						localBreadcrumbLinks = ( breadcrumb.jquery ? breadcrumb[ 0 ] : breadcrumb ).getElementsByTagName( "li" );
12821						len = localBreadcrumbLinks.length;
12822						if ( len ) {
12823							link = localBreadcrumbLinks[ len - 1 ];
12824							child = link.firstChild;
12825							linkHref = ( child && child.nodeName === "A" ) ? child.getAttribute( "href" ) : "";
12826							if ( linkHref && linkHref.charAt( 0 ) !== "#" ) {
12827								localBreadcrumbLinksArray.push( child );
12828								localBreadcrumbLinksUrlArray.push( child.hostname + child.pathname.replace( /^([^/])/, "/$1" ) );
12829							}
12830						}
12831
12832						// Cache the data in case of more than one execution (e.g., site menu + secondary navigation)
12833						breadcrumbLinksArray = localBreadcrumbLinksArray;
12834						breadcrumbLinksUrlArray = localBreadcrumbLinksUrlArray;
12835					} else {
12836
12837						// Retrieve the cached data
12838						localBreadcrumbLinksArray = breadcrumbLinksArray;
12839						localBreadcrumbLinksUrlArray = breadcrumbLinksUrlArray;
12840					}
12841
12842					// Try to match each breadcrumb link
12843					len = menuLinksArray.length;
12844					for ( j = localBreadcrumbLinksArray.length - 1; j !== -1; j -= 1 ) {
12845						localBreadcrumbLinkUrl = localBreadcrumbLinksUrlArray[ j ];
12846						localBreadcrumbQuery = localBreadcrumbLinksArray[ j ].search;
12847
12848						for ( i = 0; i !== len; i += 1 ) {
12849							link = menuLinksArray[ i ];
12850							linkUrl = menuLinksUrlArray[ i ];
12851							linkQuery = link.search;
12852							linkQueryLen = linkQuery.length;
12853
12854							if ( localBreadcrumbLinkUrl.slice( -linkUrl.length ) === linkUrl && ( linkQueryLen === 0 || localBreadcrumbQuery.slice( -linkQueryLen ) === linkQuery ) ) {
12855								match = true;
12856								break;
12857							}
12858						}
12859						if ( match ) {
12860							break;
12861						}
12862					}
12863				}
12864
12865				if ( match ) {
12866					link.className += " " + className;
12867					if ( menu.className.indexOf( "wb-menu" ) !== -1 && link.className.indexOf( "item" ) === -1 ) {
12868						$( link ).closest( ".sm" ).parent().children( "a" ).addClass( className );
12869					}
12870				}
12871
12872				// Identify that initialization has completed
12873				wb.ready( $( menu ), componentName );
12874			}
12875		}
12876	};
12877
12878// Bind the navcurrent event of the plugin
12879$document.on( "navcurr.wb", init );
12880
12881} )( jQuery, window, wb );
12882
12883/**
12884 * @title WET-BOEW Overlay
12885 * @overview Provides multiple styles of overlays such as panels and pop-ups
12886 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
12887 * @author @thomasgohard, @pjackson28
12888 */
12889( function( $, window, document, wb ) {
12890"use strict";
12891
12892/*
12893 * Variable and function definitions.
12894 * These are global to the plugin - meaning that they will be initialized once per page,
12895 * not once per instance of plugin on the page. So, this is a good place to define
12896 * variables that are common to all instances of the plugin on a page.
12897 */
12898var componentName = "wb-overlay",
12899	selector = "." + componentName,
12900	initEvent = "wb-init" + selector,
12901	closeClass = "overlay-close",
12902	linkClass = "overlay-lnk",
12903	ignoreOutsideClass = "outside-off",
12904	OverlayOpenFlag = "wb-overlay-dlg",
12905	initialized = false,
12906	sourceLinks = {},
12907	setFocusEvent = "setfocus.wb",
12908	$document = wb.doc,
12909	i18n, i18nText,
12910
12911	/**
12912	 * @method init
12913	 * @param {jQuery Event} event Event that triggered the function call
12914	 */
12915	init = function( event ) {
12916
12917		// Start initialization
12918		// returns DOM object = proceed with init
12919		// returns undefined = do not proceed with init (e.g., already initialized)
12920		var elm = wb.init( event, componentName, selector ),
12921			$elm, footer, $header, closeText, overlayClose;
12922
12923		if ( elm ) {
12924			$elm = $( elm );
12925
12926			// Only initialize the i18nText once
12927			if ( !i18nText ) {
12928				i18n = wb.i18n;
12929				i18nText = {
12930					close: i18n( "close" ),
12931					colon: i18n( "colon" ),
12932					space: i18n( "space" ),
12933					esc: i18n( "esc-key" ),
12934					closeOverlay: i18n( closeClass )
12935				};
12936			}
12937
12938			// One left and right panels add close button
12939			var isPanel = ( $elm.attr( "class" ).indexOf( "wb-panel" ) > -1 ) ? true : false,
12940				isPopup = ( $elm.attr( "class" ).indexOf( "wb-popup" ) > -1 ) ? true : false;
12941			if ( isPanel || isPopup ) {
12942				footer = $elm.find( ".modal-footer" )[ 0 ];
12943
12944				var hasFooter = ( footer && footer.length !== 0 ) ? true : false,
12945					hasButton = hasFooter && $( footer ).find( "." + closeClass ).length !== 0,
12946					closeClassFtr = ( $elm.hasClass( "wb-panel-l" ) ? "pull-right " : "pull-left " )  + closeClass,
12947					closeTextFtr = i18nText.close,
12948					spanTextFtr = i18nText.closeOverlay,
12949					overlayCloseFtr;
12950
12951				if ( !hasButton ) {
12952					if ( !hasFooter ) {
12953						footer = document.createElement( "div" );
12954						footer.setAttribute( "class", "modal-footer" );
12955					}
12956
12957					if ( isPopup ) {
12958						footer.style.border = "0";
12959					}
12960
12961					overlayCloseFtr = "<button type='button' class='btn btn-sm btn-primary " + closeClassFtr +
12962						"' title='" + spanTextFtr + "'>" +
12963						closeTextFtr +
12964						"<span class='wb-inv'>" + spanTextFtr + "</span></button>";
12965
12966					$( footer ).append( overlayCloseFtr );
12967					if ( !hasFooter ) {
12968						$elm.append( footer );
12969					}
12970				}
12971			}
12972
12973			// Add close button
12974			$header = $elm.find( ".modal-title" );
12975			if ( $header.length !== 0 ) {
12976				closeText = i18nText.close + i18nText.colon + i18nText.space +
12977					$header.text() + i18nText.space + i18nText.esc;
12978			} else {
12979				closeText = i18nText.closeOverlay;
12980			}
12981			closeText = closeText.replace( "'", "&#39;" );
12982			overlayClose = "<button type='button' class='mfp-close " + closeClass +
12983				"' title='" + closeText + "'>&#xd7;<span class='wb-inv'> " +
12984				closeText + "</span></button>";
12985
12986			$elm.append( overlayClose );
12987			elm.setAttribute( "aria-hidden", "true" );
12988
12989			// Identify that initialization has completed
12990			initialized = true;
12991			wb.ready( $elm, componentName );
12992		}
12993	},
12994
12995	openOverlay = function( overlayId, noFocus ) {
12996		var $overlay = $( "#" + wb.jqEscape( overlayId ) );
12997
12998		$overlay
12999			.addClass( "open" )
13000			.attr( "role", "dialog" )
13001			.attr( "aria-hidden", "false" );
13002
13003		if ( $overlay.hasClass( "wb-popup-full" ) || $overlay.hasClass( "wb-popup-mid" ) ) {
13004			$overlay.attr( "data-pgtitle", document.getElementsByTagName( "H1" )[ 0 ].textContent );
13005			$document.find( "body" ).addClass( OverlayOpenFlag );
13006		}
13007
13008		if ( !noFocus ) {
13009			$overlay
13010				.scrollTop( 0 )
13011				.trigger( setFocusEvent );
13012		}
13013
13014		// Register the overlay if it wasn't previously registered
13015		// (only required when opening through an event)
13016		if ( !sourceLinks[ overlayId ] ) {
13017			setTimeout( function() {
13018				sourceLinks[ overlayId ] = null;
13019			}, 1 );
13020		}
13021
13022		$overlay.trigger( "opened" + selector );
13023	},
13024
13025	closeOverlay = function( overlayId, noFocus, userClosed ) {
13026		var $overlay = $( "#" + overlayId ),
13027			sourceLink = sourceLinks[ overlayId ];
13028
13029		$overlay
13030			.removeClass( "open" )
13031			.removeAttr( "role" )
13032			.attr( "aria-hidden", "true" );
13033
13034		if ( $overlay.hasClass( "wb-popup-full" ) || $overlay.hasClass( "wb-popup-mid" ) ) {
13035			$document.find( "body" ).removeClass( OverlayOpenFlag );
13036		}
13037
13038		if ( userClosed ) {
13039			$overlay.addClass( "user-closed" );
13040		}
13041
13042		if ( !noFocus && sourceLink ) {
13043
13044			// Returns focus to the source link for the overlay
13045			$( sourceLink ).trigger( setFocusEvent );
13046		}
13047
13048		// Delete the source link reference
13049		delete sourceLinks[ overlayId ];
13050
13051		$overlay.trigger( "closed" + selector );
13052	};
13053
13054$document.on( "timerpoke.wb " + initEvent + " keydown open" + selector +
13055" close" + selector, selector, function( event ) {
13056
13057	var eventType = event.type,
13058		which = event.which,
13059		eventTarget = event.target,
13060		eventTurrentTarget = event.currentTarget,
13061		overlayId = eventTurrentTarget.id,
13062		overlay, $focusable, index, length;
13063
13064	switch ( eventType ) {
13065	case "timerpoke":
13066	case "wb-init":
13067		init( event );
13068		break;
13069
13070	case "open":
13071		if ( eventTurrentTarget === eventTarget ) {
13072			openOverlay( overlayId, event.noFocus );
13073		}
13074		break;
13075
13076	case "close":
13077		if ( eventTurrentTarget === eventTarget ) {
13078			closeOverlay( overlayId, event.noFocus );
13079		}
13080		break;
13081
13082	default:
13083		overlay = document.getElementById( overlayId );
13084
13085		switch ( which ) {
13086
13087		// Tab key
13088		case 9:
13089
13090			// No special tab handling when ignoring outside activity
13091			if ( overlay.className.indexOf( ignoreOutsideClass ) === -1 ) {
13092				$focusable = $( overlay ).find( ":focusable:not([tabindex='-1'])" );
13093				length = $focusable.length;
13094				index = $focusable.index( event.target ) + ( event.shiftKey ? -1 : 1 );
13095
13096				if ( index === -1 || index === length ) {
13097					event.preventDefault();
13098					$focusable.eq( index === -1 ? length - 1 : 0 )
13099						.trigger( setFocusEvent );
13100				}
13101			}
13102			break;
13103
13104		// Escape key
13105		case 27:
13106			if ( !event.isDefaultPrevented() ) {
13107				closeOverlay( overlayId, false, true );
13108			}
13109			break;
13110		}
13111	}
13112} );
13113
13114// Handler for clicking on the close button of the overlay
13115$document.on( "click", "." + closeClass, function( event ) {
13116	var which = event.which;
13117
13118	// Ignore if not initialized and middle/right mouse buttons
13119	if ( initialized && ( !which || which === 1 ) ) {
13120		closeOverlay(
13121			$( event.currentTarget ).closest( selector ).attr( "id" ),
13122			false,
13123			true
13124		);
13125	}
13126} );
13127
13128// Handler for clicking on a source link for the overlay
13129$document.on( "click keydown", "." + linkClass, function( event ) {
13130	var which = event.which,
13131		sourceLink = event.currentTarget,
13132		overlayId = sourceLink.hash.substring( 1 );
13133
13134	// Ignore if not initialized and middle/right mouse buttons
13135	if ( initialized && ( !which || which === 1 || which === 32 ) ) {
13136		event.preventDefault();
13137
13138		// Introduce a delay to prevent outside activity detection
13139		setTimeout( function() {
13140
13141			// Stores the source link for the overlay
13142			sourceLinks[ overlayId ] = sourceLink;
13143
13144			// Opens the overlay
13145			openOverlay( overlayId );
13146		}, 1 );
13147	}
13148} );
13149
13150// Handler for clicking on a same page link within the overlay to outside the overlay
13151$document.on( "click", selector + " a[href^='#']", function( event ) {
13152	var which = event.which,
13153		eventTarget = event.target,
13154		href, overlay, linkTarget;
13155
13156	// Ignore if not initialized and middle/right mouse buttons
13157	if ( initialized && ( !which || which === 1 ) ) {
13158		overlay = $( eventTarget ).closest( selector )[ 0 ];
13159		href = eventTarget.getAttribute( "href" );
13160		linkTarget = document.getElementById( href.substring( 1 ) );
13161
13162		// Ignore same page links to within the overlay
13163		if ( href.length > 1 && !$.contains( overlay, linkTarget ) ) {
13164
13165			// Stop propagation of the click event
13166			if ( event.stopPropagation ) {
13167				event.stopImmediatePropagation();
13168			} else {
13169				event.cancelBubble = true;
13170			}
13171
13172			// Close the overlay and set focus to the same page link
13173			closeOverlay( overlay.id, true );
13174			$( linkTarget ).trigger( setFocusEvent );
13175		}
13176	}
13177} );
13178
13179// Outside activity detection
13180$document.on( "click focusin", "body", function( event ) {
13181	var eventTarget = event.target,
13182		which = event.which,
13183		overlayId, overlay;
13184
13185	// Ignore if not initialized and middle/right mouse buttons
13186	if ( initialized && ( !which || which === 1 ) ) {
13187
13188		// Close any overlays with outside activity
13189		for ( overlayId in sourceLinks ) {
13190			overlay = document.getElementById( overlayId );
13191			if ( overlay && overlay.getAttribute( "aria-hidden" ) === "false" &&
13192				eventTarget.id !== overlayId &&
13193				overlay.className.indexOf( ignoreOutsideClass ) === -1 &&
13194				!$.contains( overlay, eventTarget ) ) {
13195
13196				// Close the overlay
13197				closeOverlay( overlayId );
13198			}
13199		}
13200	}
13201} );
13202
13203// Ensure any element in focus outside an overlay is visible
13204$document.on( "keyup", function( ) {
13205	var elmInFocus, elmInFocusRect, focusAreaBelow, focusAreaAbove,
13206		overlayId, overlay, overlayRect;
13207
13208	// Ignore if not initialized
13209	if ( initialized ) {
13210		elmInFocus = document.activeElement;
13211		elmInFocusRect = elmInFocus.getBoundingClientRect();
13212		focusAreaBelow = 0;
13213		focusAreaAbove = window.innerHeight;
13214
13215		// Ensure that at least one overlay is visible, and that the element in focus is not an overlay,
13216		// a child of an overlay, or the body element
13217		if ( $.isEmptyObject( sourceLinks ) || elmInFocus.className.indexOf( componentName ) !== -1 ||
13218			$( elmInFocus ).parents( selector ).length !== 0 || elmInFocus === document.body ) {
13219			return;
13220		}
13221
13222		// Determine the vertical portion of the viewport that is not obscured by an overlay
13223		for ( overlayId in sourceLinks ) {
13224			overlay = document.getElementById( overlayId );
13225			if ( overlay && overlay.getAttribute( "aria-hidden" ) === "false" ) {
13226				overlayRect = overlay.getBoundingClientRect();
13227				if ( overlay.className.indexOf( "wb-bar-t" ) !== -1 ) {
13228					focusAreaBelow = Math.max( overlayRect.bottom, focusAreaBelow );
13229				} else if ( overlay.className.indexOf( "wb-bar-b" ) !== -1 ) {
13230					focusAreaAbove = Math.min( overlayRect.top, focusAreaAbove );
13231				}
13232			}
13233		}
13234
13235		// Ensure the element in focus is visible
13236		// TODO: Find a solution for when there isn't enough page to scoll up or down
13237		if ( elmInFocusRect.top < focusAreaBelow ) {
13238
13239			// Scroll down till the top of the element is visible
13240			window.scrollBy( 0, focusAreaBelow - elmInFocusRect.top );
13241		} else if ( elmInFocusRect.bottom > focusAreaAbove ) {
13242
13243			// Scroll up till the bottom of the element is visible
13244			window.scrollBy( 0, elmInFocusRect.bottom - focusAreaAbove );
13245		}
13246	}
13247} );
13248
13249// Add the timer poke to initialize the plugin
13250wb.add( selector );
13251
13252} )( jQuery, window, document, wb );
13253
13254/**
13255 * @title WET-BOEW Tag filter
13256 * @overview Filter based content tagging
13257 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13258 * @author @duboisp
13259 */
13260( function( $, window, document, wb ) {
13261"use strict";
13262
13263let i18n, i18nText;
13264
13265const componentName = "wb-paginate",
13266	selector = ".provisional." + componentName,
13267	initEvent = "wb-init" + selector,
13268	$document = wb.doc,
13269	filterClass = "wb-fltr-out",
13270	pgFilterOutClass = "wb-pgfltr-out",
13271	tagFilterClass = "wb-tgfltr-out",
13272	pagerClass = "wb-paginate-pager",
13273	pageData = "data-pagination-idx",
13274	notFilterClassSel = ":not(." + filterClass + "):not(." + tagFilterClass + ")",
13275	defaults = {
13276		lst: {
13277			selector: "li"
13278		},
13279		grp: {
13280			selector: "> *"
13281		},
13282		tbl: {
13283			selector: "tr",
13284			section: ":scope > tbody"
13285		},
13286		itemsPerPage: 10
13287	},
13288
13289	init = function( event ) {
13290		const elm = wb.init( event, componentName, selector );
13291
13292		if ( elm ) {
13293			var $elm = $( elm ),
13294				paginationElm,
13295				elmTagName = elm.nodeName,
13296				setDefault,
13297				uiTargetElm;
13298
13299			if ( !i18nText ) {
13300				i18n = wb.i18n;
13301				i18nText = {
13302					prv: i18n( "prv" ),
13303					nxt: i18n( "nxt" )
13304				};
13305			}
13306
13307			// Setup configurations
13308			switch ( elmTagName ) {
13309			case "UL":
13310				setDefault = defaults.lst;
13311				break;
13312			case "TABLE":
13313				setDefault = defaults.tbl;
13314				break;
13315			default:
13316				setDefault = defaults.grp;
13317				break;
13318			}
13319
13320			elm.id = elm.id || wb.getId();
13321			elm.pgSettings = $.extend( true, {}, setDefault, window[ componentName ], wb.getData( $elm, componentName ) );
13322			elm.pgSettings.currPage = 1;
13323			elm.pgSettings.itemsPerPage = elm.pgSettings.itemsPerPage || defaults.itemsPerPage;
13324			elm.pgSettings.items = elm.querySelectorAll( ( elm.pgSettings.section || ":scope" ) + " " + elm.pgSettings.selector + notFilterClassSel );
13325
13326			// Setup pagination container
13327			paginationElm = document.createElement( "div" );
13328			paginationElm.id = componentName + "-" + elm.id;
13329			paginationElm.classList.add( pagerClass );
13330
13331			// Add pagination container
13332			if ( elm.pgSettings.uiTarget ) {
13333				uiTargetElm = document.querySelector( elm.pgSettings.uiTarget );
13334				uiTargetElm.appendChild( paginationElm );
13335			} else if ( elm.pgSettings.section ) {
13336				if ( elmTagName === "UL" || elmTagName === "TABLE" ) {
13337					elm.after( paginationElm );
13338				} else {
13339					elm.querySelector( elm.pgSettings.section ).after( paginationElm );
13340				}
13341			} else {
13342				elm.after( paginationElm );
13343			}
13344
13345			// Show/hide items and generate pagination
13346			updateItems( elm );
13347			generateUI( elm );
13348
13349			wb.ready( $( elm ), componentName );
13350		}
13351	},
13352
13353	// Set or reset pagination UI
13354	generateUI = function( elm ) {
13355		var paginationUI = "",
13356			currPage = elm.pgSettings.currPage,
13357			pagesCount = elm.pgSettings.pagesCount,
13358			paginationElm = document.querySelector( "#" + componentName + "-" + elm.id ),
13359			i = 1;
13360
13361		// Make sure the defined current page is not bigger than the total pages
13362		if ( currPage > pagesCount ) {
13363			currPage = pagesCount;
13364		}
13365
13366		// Only add pagination if there is more than one page
13367		if ( pagesCount > 1 ) {
13368			paginationUI = "<ol class=\"pagination\">";
13369
13370			// Add Previous page button
13371			paginationUI += "<li" + ( i === currPage ? " class=\"disabled\"" : "" ) + "><a class=\"paginate-prev\" aria-controls=\"" + elm.id + "\" href=\"#" + elm.id + "\">" + i18nText.prv + "</a></li>";
13372
13373			// Add pages buttons
13374			for ( i; i <= pagesCount; i++ ) {
13375				paginationUI += "<li class=\"" + returnItemClass( currPage, pagesCount, i ) + "\"" + ( i === currPage ? " aria-current=\"page\"" : "" ) + "><a href=\"#" + elm.id + "\" " + pageData + "=\"" + i + "\" aria-controls=\"" + elm.id + "\"><span class=\"wb-inv\">Page </span>" + i + "</a></li>";
13376			}
13377
13378			// Add Next page button
13379			paginationUI += "<li" + ( i === currPage ? " class=\"disabled\"" : "" ) + "><a class=\"paginate-next\" aria-controls=\"" + elm.id + "\" href=\"#" + elm.id + "\">" + i18nText.nxt + "</a></li>";
13380
13381			paginationUI += "</ol>";
13382		}
13383
13384		// Insert HTML
13385		paginationElm.innerHTML = paginationUI;
13386	},
13387
13388	// Show/hide items to reflect current page
13389	updateItems = function( elm ) {
13390		let currPage = elm.pgSettings.currPage,
13391			items = elm.pgSettings.items,
13392			itemsPerPage = elm.pgSettings.itemsPerPage;
13393
13394		items.forEach( function( item, index ) {
13395			if ( ( index < ( itemsPerPage * currPage ) ) && ( index >= ( itemsPerPage * currPage ) - itemsPerPage ) ) {
13396				item.classList.remove( pgFilterOutClass );
13397			} else {
13398				item.classList.add( pgFilterOutClass );
13399			}
13400		} );
13401
13402		elm.pgSettings.pagesCount = Math.ceil( items.length / itemsPerPage );
13403	},
13404
13405	// Update pagination to reflect current page
13406	goToPage = function( elm ) {
13407		let paginationElm = document.querySelector( "#" + componentName + "-" + elm.id ),
13408			pageItems = paginationElm.querySelectorAll( "li" ),
13409			itemClass,
13410			pageLink,
13411			currPage = elm.pgSettings.currPage,
13412			pagesCount = elm.pgSettings.pagesCount;
13413
13414		pageItems.forEach( function( pageItem, i ) {
13415			pageLink = pageItem.querySelector( "a" );
13416
13417			if ( pageLink.classList.contains( "paginate-prev" ) ) {
13418				if ( currPage > 1 ) {
13419					pageItem.classList.remove( "disabled" );
13420				} else {
13421					pageItem.classList.add( "disabled" );
13422				}
13423			} else if ( pageLink.classList.contains( "paginate-next" ) ) {
13424				if ( currPage < pagesCount ) {
13425					pageItem.classList.remove( "disabled" );
13426				} else {
13427					pageItem.classList.add( "disabled" );
13428				}
13429			} else {
13430				pageItem.className = "";
13431				pageItem.removeAttribute( "aria-current" );
13432
13433				itemClass = returnItemClass( currPage, pagesCount, i );
13434
13435				if ( i === currPage ) {
13436					pageItem.setAttribute( "aria-current", "page" );
13437				}
13438
13439				pageItem.className = itemClass;
13440			}
13441		} );
13442	},
13443
13444	// Return the list item classname
13445	returnItemClass = function( currPage, pagesCount, i ) {
13446		let itemClass = "";
13447
13448		if ( currPage > 1 && currPage < pagesCount ) {
13449			if ( Math.abs( currPage - i ) > 1 ) {
13450				itemClass += "hidden-xs hidden-sm";
13451
13452				if ( Math.abs( currPage - i ) > 2 ) {
13453					itemClass += " hidden-md";
13454				}
13455			}
13456		} else {
13457			if ( Math.abs( currPage - i ) > 2 ) {
13458				itemClass += "hidden-xs hidden-sm";
13459
13460				if ( Math.abs( currPage - i ) > 4 ) {
13461					itemClass += " hidden-md";
13462				}
13463			}
13464		}
13465
13466		if ( pagesCount > 10 ) {
13467			if ( currPage <= 5 ) {
13468				if ( i > 10 ) {
13469					itemClass += " hidden";
13470				}
13471			} else if ( ( currPage > 5 ) && ( currPage < pagesCount - 5 ) ) {
13472				if ( ( i < currPage - 4 ) || ( i > currPage + 5 ) ) {
13473					itemClass += " hidden";
13474				}
13475			} else {
13476				if ( i <= pagesCount - 10 ) {
13477					itemClass += " hidden";
13478				}
13479			}
13480		}
13481
13482		if ( i === currPage ) {
13483			itemClass += " active";
13484		}
13485
13486		return itemClass;
13487	};
13488
13489// When a filter is updated
13490$document.on( "click", "." + pagerClass + " a", function()  {
13491	let elm = document.querySelector( "#" + this.getAttribute( "aria-controls" ) ),
13492		pageDest = ( ( this.getAttribute( pageData ) ) * 1 ) || elm.pgSettings.currPage;
13493
13494	if ( this.classList.contains( "paginate-next" ) ) {
13495		pageDest++;
13496	} else if ( this.classList.contains( "paginate-prev" ) ) {
13497		pageDest--;
13498	}
13499
13500	if ( pageDest !== elm.pgSettings.currPage ) {
13501		elm.pgSettings.currPage = pageDest;
13502
13503		updateItems( elm );
13504		goToPage( elm );
13505	}
13506} );
13507
13508// Resets items and pagination
13509$document.on( "wb-contentupdated", selector, function() {
13510	this.pgSettings.currPage = 1;
13511	this.pgSettings.items = this.pgSettings.items = this.querySelectorAll( ( this.pgSettings.section || ":scope" ) + " " + this.pgSettings.selector + notFilterClassSel );
13512
13513	updateItems( this );
13514	generateUI( this );
13515} );
13516
13517$document.on( "timerpoke.wb " + initEvent, selector, init );
13518
13519wb.add( selector );
13520
13521} )( jQuery, window, document, wb );
13522
13523/**
13524 * @title WET-BOEW Prettify Plugin
13525 * @overview Wrapper for Google Code Prettify library: https://github.com/google/code-prettify
13526 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13527 * @author @patheard
13528 */
13529/*
13530 * Syntax highlighting of source code snippets in an html page using [google-code-prettify](https://github.com/google/code-prettify).
13531 *
13532 * 1. Apply `class="prettyprint"` to a `pre` or `code` element to apply syntax highlighting. Alternatively use `class="all-pre"` to apply syntax highlighting to all `pre` elements on the page.
13533 * 2. Apply `class="linenums"` to a `pre` or `code` element to add line numbers. Alternatively use `class="all-linenums"` to all applicable `pre` elements. Specify the starting number by adding `linenums:#` before `linenums`.
13534 * 3. Add extra language support by applying `class="lang-*"` to each applicable `pre` or `code` element. Supported language syntax CSS classes are as follows:
13535 *    - lang-apollo
13536 *    - lang-clj
13537 *    - lang-css
13538 *    - lang-dart
13539 *    - lang-go
13540 *    - lang-hs
13541 *    - lang-lisp
13542 *    - lang-lua
13543 *    - lang-ml
13544 *    - lang-n
13545 *    - lang-proto
13546 *    - lang-scala
13547 *    - lang-sql
13548 *    - lang-tex
13549 *    - lang-vb
13550 *    - lang-vhdl
13551 *    - lang-wiki
13552 *    - lang-xq
13553 *    - lang-yaml
13554 */
13555( function( $, window, wb ) {
13556"use strict";
13557
13558/*
13559 * Variable and function definitions.
13560 * These are global to the plugin - meaning that they will be initialized once per page,
13561 * not once per instance of plugin on the page. So, this is a good place to define
13562 * variables that are common to all instances of the plugin on a page.
13563 */
13564var componentName = "wb-prettify",
13565	selector = "." + componentName,
13566	initEvent = "wb-init" + selector,
13567	prettyPrintEvent = "prettyprint" + selector,
13568	$document = wb.doc,
13569
13570	/*
13571	 * Plugin users can override these defaults by setting attributes on the html elements that the
13572	 * selector matches.
13573	 */
13574	defaults = {
13575		linenums: false,
13576		allpre: false
13577	},
13578
13579	/**
13580	 * @method init
13581	 * @param {jQuery Event} event Event that triggered the function call
13582	 */
13583	init = function( event ) {
13584
13585		// Start initialization
13586		// returns DOM object = proceed with init
13587		// returns undefined = do not proceed with init (e.g., already initialized)
13588		var elm = wb.init( event, componentName, selector ),
13589			modeJS = wb.getMode() + ".js",
13590			deps = [ "site!deps/prettify" + modeJS ],
13591			$elm, classes, settings, i, len, $pre;
13592
13593		if ( elm ) {
13594			$elm = $( elm );
13595			classes = elm.className.split( " " );
13596
13597			// Merge default settings with overrides from the selected plugin element. There may be more than one, so don't override defaults globally!
13598			settings = $.extend( {}, defaults, $elm.data() );
13599
13600			// Check the element for `lang-*` syntax CSS classes
13601			for ( i = 0, len = classes.length; i !== len; i += 1 ) {
13602				if ( classes[ i ].indexOf( "lang-" ) === 0 ) {
13603					deps.push( "site!deps/" + classes[ i ] + modeJS );
13604				}
13605			}
13606
13607			// CSS class overides of settings
13608			settings.allpre = settings.allpre || $elm.hasClass( "all-pre" );
13609			settings.linenums = settings.linenums || $elm.hasClass( "linenums" );
13610
13611			// Apply global settings
13612			if ( settings.allpre || settings.linenums ) {
13613				$pre = $document.find( "pre" );
13614				if ( settings.allpre ) {
13615					$pre.addClass( "prettyprint" );
13616				}
13617				if ( settings.linenums ) {
13618					$pre.filter( ".prettyprint" ).addClass( "linenums" );
13619				}
13620			}
13621
13622			// Load the required dependencies and prettify the code once finished
13623			Modernizr.load( {
13624				load: deps,
13625				complete: function() {
13626					$document.trigger( prettyPrintEvent );
13627				}
13628			} );
13629		}
13630	},
13631
13632	prettifyDone = function() {
13633
13634		// Identify that initialization has completed
13635		wb.ready( $document, componentName );
13636	},
13637
13638	/*
13639	 * Invoke the Google pretty print library if it has been initialized
13640	 * @method prettyprint
13641	 */
13642	prettyprint = function( event ) {
13643		if ( event.namespace === componentName &&
13644			typeof window.prettyPrint === "function" ) {
13645
13646			window.prettyPrint( prettifyDone );
13647		}
13648	};
13649
13650// Bind the plugin events
13651$document
13652	.on( "timerpoke.wb " + initEvent, selector, init )
13653	.on( prettyPrintEvent, prettyprint );
13654
13655// Add the timer poke to initialize the plugin
13656wb.add( selector );
13657
13658} )( jQuery, window, wb );
13659
13660/**
13661 * @title WET-BOEW Resize
13662 * @overview Text and window resizing event handler
13663 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13664 * @author @pjackson28
13665 */
13666( function( $, window, document, wb ) {
13667"use strict";
13668
13669/*
13670 * Variable and function definitions.
13671 * These are global to the plugin - meaning that they will be initialized once per page,
13672 * not once per instance of plugin on the page. So, this is a good place to define
13673 * variables that are common to all instances of the plugin on a page.
13674 */
13675var componentName = "wb-rsz",
13676	selector = "#" + componentName,
13677	initEvent = "wb-init" + selector,
13678	$document = wb.doc,
13679	sizes = [],
13680	events = [
13681		"txt-rsz.wb",
13682		"win-rsz-width.wb",
13683		"win-rsz-height.wb"
13684	],
13685
13686	// Breakpoint names and lower pixel limits
13687	breakpoints = {
13688		xxsmallview: 0,
13689		xsmallview: 480,
13690		smallview: 768,
13691		mediumview: 992,
13692		largeview: 1200,
13693		xlargeview: 1600
13694	},
13695	resizeTest, currentView,
13696
13697	/**
13698	 * @method init
13699	 * @param {jQuery Event} event Event that triggered the function call
13700	 */
13701	init = function( event ) {
13702
13703		// Start initialization
13704		// returns DOM object = proceed with init
13705		// returns undefined = do not proceed with init (e.g., already initialized)
13706		var elm = wb.init( event, componentName, selector ),
13707			localResizeTest;
13708
13709		if ( elm ) {
13710
13711			// Set up the DOM element used for resize testing
13712			localResizeTest = document.createElement( "span" );
13713			localResizeTest.innerHTML = "&#160;";
13714			localResizeTest.setAttribute( "id", componentName );
13715			document.body.appendChild( localResizeTest );
13716			resizeTest = localResizeTest;
13717
13718			// Get a snapshot of the current sizes
13719			sizes = [
13720				localResizeTest.offsetHeight,
13721				window.innerWidth || $document.width(),
13722				window.innerHeight || $document.height()
13723			];
13724
13725			// Determine the current view
13726			viewChange( sizes[ 1 ] );
13727
13728			// Identify that initialization has completed
13729			wb.ready( $document, componentName );
13730		}
13731	},
13732
13733	viewChange = function( viewportWidth ) {
13734		var breakpoint, viewName;
13735
13736		// Check for a change between views
13737		for ( breakpoint in breakpoints ) {
13738
13739			// Determine the current view
13740			if ( viewportWidth < breakpoints[ breakpoint ] ) {
13741				break;
13742			} else {
13743				viewName = breakpoint;
13744			}
13745		}
13746
13747		// Determine if the current view is different than the previous view
13748		if ( viewName !== currentView ) {
13749
13750			// Change the breakpoint class on the html element
13751			wb.html
13752				.removeClass( currentView || "" )
13753				.addClass( viewName );
13754
13755			// Update the current view
13756			currentView = viewName;
13757
13758			// Trigger the view event
13759			$document.trigger( viewName + ".wb" );
13760		}
13761	},
13762
13763	/**
13764	 * Tests for text size, window width and window height changes and triggers an event when a change is found
13765	 * @method test
13766	 */
13767	test = function() {
13768		var currentSizes = [
13769				resizeTest.offsetHeight,
13770				window.innerWidth || $document.width(),
13771				window.innerHeight || $document.height()
13772			],
13773			len = currentSizes.length,
13774			i;
13775
13776		// Check for a viewport or text size change
13777		for ( i = 0; i !== len; i += 1 ) {
13778			if ( currentSizes[ i ] !== sizes[ i ] ) {
13779
13780				// Change detected so trigger related event
13781				$document.trigger( events[ i ], currentSizes );
13782
13783				// Check for a view change
13784				viewChange( currentSizes[ 1 ] );
13785			}
13786		}
13787		sizes = currentSizes;
13788
13789		return;
13790	};
13791
13792// Bind the init event to the plugin
13793$document.on( initEvent, init );
13794
13795// Re-test on each timerpoke
13796$document.on( "timerpoke.wb", selector, test );
13797
13798// Initialize the resources
13799$document.trigger( initEvent );
13800
13801// Add the timer poke to initialize the plugin
13802wb.add( selector );
13803
13804} )( jQuery, window, document, wb );
13805
13806/**
13807 * @title WET-BOEW Session Timeout
13808 * @overview Helps Web asset owners to provide session timeout and inactivity timeout functionality.
13809 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
13810 * @author @patheard
13811 */
13812( function( $, DOMPurify, window, document, wb ) {
13813"use strict";
13814
13815/*
13816 * Variable and function definitions.
13817 * These are global to the plugin - meaning that they will be initialized once per page,
13818 * not once per instance of plugin on the page. So, this is a good place to define
13819 * variables that are common to all instances of the plugin on a page.
13820 */
13821var $modal, $modalLink, countdownInterval, i18n, i18nText,
13822	$document = wb.doc,
13823	componentName = "wb-sessto",
13824	selector = "." + componentName,
13825	confirmClass = componentName + "-confirm",
13826	initEvent = "wb-init" + selector,
13827	resetEvent = "reset" + selector,
13828	keepaliveEvent = "keepalive" + selector,
13829	inactivityEvent = "inactivity" + selector,
13830	dataAttr = componentName,
13831
13832	/*
13833	 * Plugin users can override these defaults by setting attributes on the html elements that the
13834	 * selector matches.
13835	 * For example, adding the attribute data-option1="false", will override option1 for that plugin instance.
13836	 */
13837	defaults = {
13838		inactivity: 1200000,		/* default inactivity period 20 minutes */
13839		reactionTime: 180000,		/* default confirmation period of 3 minutes */
13840		sessionalive: 1200000,		/* default keepalive period of 20 minutes */
13841		refreshCallbackUrl: null,	/* refresh callback if using AJAX keepalive (no default) */
13842		logouturl: "./",			/* logout URL once the session has expired */
13843		refreshOnClick: true,		/* refresh session if user clicks on the page */
13844		refreshLimit: 120000,		/* default period of 2 minutes (ajax calls happen only once during this period) */
13845		method: "POST",				/* the request method to use */
13846		additionalData: null,		/* additional data to send with the request */
13847		refreshCallback: function( response ) {	/* callback function used to check the server response */
13848			return response.replace( /\s/g, "" ) === "true";
13849		}
13850	},
13851
13852	/**
13853	 * @function init
13854	 * @param {jQuery Event} event Event that triggered the function call
13855	 */
13856	init = function( event ) {
13857
13858		// Start initialization
13859		// returns DOM object = proceed with init
13860		// returns undefined = do not proceed with init (e.g., already initialized)
13861		var elm = wb.init( event, componentName, selector ),
13862			$elm, settings, onReady;
13863
13864		if ( elm ) {
13865			$elm = $( elm );
13866
13867			// For backwards compatibility where data-wet-boew was used instead of data-wb-sessto
13868			if ( !$elm.attr( "data-" + componentName ) ) {
13869				dataAttr = "wet-boew";
13870			}
13871
13872			// Merge default settings with overrides from the plugin element
13873			// and save back to the element for future reference
13874			settings = $.extend( {}, defaults, window[ componentName ], $elm.data( dataAttr ) );
13875			$elm.data( dataAttr, settings );
13876
13877			// Only initialize the i18nText once
13878			if ( !i18nText ) {
13879				i18n = wb.i18n;
13880				i18nText = {
13881					buttonContinue: i18n( "st-btn-cont" ),
13882					buttonEnd: i18n( "st-btn-end" ),
13883					buttonSignin: i18n( "tmpl-signin" ),
13884					timeoutBegin: i18n( "st-to-msg-bgn" ),
13885					timeoutEnd: i18n( "st-to-msg-end" ),
13886					timeoutTitle: i18n( "st-msgbx-ttl" ),
13887					timeoutAlready: i18n( "st-alrdy-to-msg" )
13888				};
13889			}
13890
13891			onReady = function() {
13892
13893				// Setup the refresh on click behaviour
13894				initRefreshOnClick( $elm, settings );
13895
13896				// Initialize the keepalive and inactive timeouts of the plugin
13897				$elm.trigger( resetEvent, settings );
13898
13899				// Identify that initialization has completed
13900				wb.ready( $elm, componentName );
13901			};
13902
13903			// Create the modal dialog
13904			initModalDialog( onReady );
13905		}
13906	},
13907
13908	/**
13909	 * Initializes a timeout that triggers an event
13910	 * @function initEventTimeout
13911	 * @param {jQuery DOM Element} $elm Element to trigger the event on
13912	 * @param {string} eventName Name of the event to trigger on setTimeout
13913	 * @param {mixed} time Time to wait before triggering the event
13914	 * @param {Object} settings Key-value object
13915	 */
13916	initEventTimeout = function( $elm, eventName, time, settings ) {
13917		var duration = parseTime( time );
13918
13919		// Clear any existing timeout for the event
13920		clearTimeout( $elm.data( eventName ) );
13921
13922		// Create the new timeout that will trigger the event
13923		$elm.data( eventName, setTimeout( function() {
13924			$elm.trigger( eventName, settings );
13925		}, duration ) );
13926	},
13927
13928	/**
13929	 * Creates the modal dialog element, appends to the <body> and initializes the lightbox plugin
13930	 * that is used to create the dialog behaviour.
13931	 * @function initModalDialog
13932	 */
13933	initModalDialog = function( callback ) {
13934		var modalID = "#" + componentName + "-modal",
13935			child, modal, temp;
13936
13937		if ( $document.find( modalID ).length === 0 ) {
13938			modal = document.createDocumentFragment();
13939			temp = document.createElement( "div" );
13940
13941			// Create the modal dialog.  A temp <div> element is used so that its innerHTML can be set as a string.
13942			temp.innerHTML = "<a class='wb-lbx lbx-modal mfp-hide' href='#" + componentName + "-modal'>" + i18nText.timeoutTitle + "</a>" +
13943				"<section id='" + componentName + "-modal' class='mfp-hide modal-dialog modal-content overlay-def'>" +
13944				"<header class='modal-header'><h2 class='modal-title'>" + i18nText.timeoutTitle + "</h2></header>" +
13945				"<div class='modal-body'></div>" +
13946				"<div class='modal-footer'></div>" +
13947				"</section>";
13948
13949			// Get the temporary <div>'s top level children and append to the fragment
13950			while ( ( child = temp.firstChild ) !== null ) {
13951				modal.appendChild( child );
13952			}
13953			document.body.appendChild( modal );
13954
13955			$modal = $document.find( modalID );
13956
13957			// Get object references to the modal and its triggering link
13958			$modalLink = $modal.prev()
13959				.one( "wb-ready.wb-lbx", callback )
13960				.trigger( "wb-init.wb-lbx" );
13961		} else {
13962			callback();
13963		}
13964	},
13965
13966	/**
13967	 * Initialize the refresh on click keepalive behaviour. This will cause a `keepalive.wb-sessto`
13968	 * event to be triggered when the document is clicked, limited by the settings.refreshLimit value.
13969	 * @function initRefreshOnClick
13970	 * @param {jQuery DOM Element} $elm DOM element to trigger the event on
13971	 * @param {Object} settings Key-value object that will be passed when event is triggered.
13972	 */
13973	initRefreshOnClick = function( $elm, settings ) {
13974		if ( settings.refreshOnClick ) {
13975			$document.on( "click", function( event ) {
13976				var className = event.target.className,
13977					lastActivity, currentTime;
13978
13979				// Ignore clicks when the modal dialog is open
13980				if ( ( !className || className.indexOf( confirmClass ) === -1 ) &&
13981					$( ".mfp-ready ." + confirmClass ).length === 0 ) {
13982
13983					lastActivity = $elm.data( "lastActivity" );
13984					currentTime = getCurrentTime();
13985					if ( !lastActivity || ( currentTime - lastActivity ) > settings.refreshLimit ) {
13986						$elm
13987							.trigger( resetEvent, settings )
13988							.trigger( keepaliveEvent, settings );
13989						$elm.data( "lastActivity", currentTime );
13990					}
13991				}
13992			} );
13993		}
13994	},
13995
13996	/**
13997	 * Keepalive session event handler. Sends the POST request to determine if the session is still alive.
13998	 * @function keepalive
13999	 * @param {jQuery Event} event `keepalive.wb-sessto` event that triggered the function call
14000	 * @param {Object} settings Key-value object
14001	 */
14002	keepalive = function( event, settings ) {
14003		var $elm = $( event.target );
14004		if ( settings.refreshCallbackUrl !== null ) {
14005			$.ajax( {
14006				url: settings.refreshCallbackUrl,
14007				data: settings.additionalData,
14008				dataType: "text",
14009				method: settings.method,
14010				success: function( response ) {
14011
14012					// Sanitize the response
14013					response = DOMPurify.sanitize( response );
14014
14015					// Session is valid
14016					if ( response && settings.refreshCallback( response ) ) {
14017						$elm.trigger( resetEvent, settings );
14018
14019					// Session has timed out - let the user know they need to sign in again
14020					} else {
14021
14022						// End the inactivity timeouts since the session is already kaput
14023						clearTimeout( $elm.data( inactivityEvent ) );
14024						clearTimeout( $elm.data( keepaliveEvent ) );
14025
14026						openModal( {
14027							body: "<p>" + i18nText.timeoutAlready + "</p>",
14028							buttons: $( "<button type='button' class='" + confirmClass +
14029								" btn btn-primary popup-modal-dismiss'>" + i18nText.buttonSignin + "</button>" )
14030								.data( "logouturl", settings.logouturl )
14031						} );
14032					}
14033				}
14034			} );
14035		}
14036	},
14037
14038	/**
14039	 * Inactivity check event handler. Displays the modal dialog to allow the user to confirm their activity.
14040	 * @function inactivity
14041	 * @param {jQuery Event} event `inactivity.wb-sessto` event that triggered the function call
14042	 * @param {Object} settings Key-value object
14043	 */
14044	inactivity = function( event, settings ) {
14045		var $buttonContinue, $buttonEnd,
14046			time = getTime( settings.reactionTime ),
14047			startTime = getCurrentTime(),
14048			timeoutBegin = i18nText.timeoutBegin
14049				.replace( "#min#", "<span class='min'>" + time.minutes + "</span>" )
14050				.replace( "#sec#", "<span class='sec'>" + time.seconds + "</span>" ),
14051			buttonStart = "<button type='button' class='",
14052			buttonEnd = "</button>";
14053
14054		// Clear the keepalive timeout to avoid double firing of requests
14055		clearInterval( $( event.target ).data( keepaliveEvent ) );
14056
14057		$buttonContinue = $( buttonStart + confirmClass +
14058			" btn btn-primary popup-modal-dismiss'>" + i18nText.buttonContinue + buttonEnd )
14059			.data( settings )
14060			.data( "start", startTime );
14061		$buttonEnd = $( buttonStart + confirmClass + " btn btn-default'>" +
14062			i18nText.buttonEnd + buttonEnd )
14063			.data( "logouturl", settings.logouturl );
14064
14065		openModal( {
14066			body: "<p>" + timeoutBegin + "<br />" + i18nText.timeoutEnd + "</p>",
14067			buttons: [ $buttonContinue, $buttonEnd ],
14068			open: function() {
14069				var $minutes = $modal.find( ".min" ),
14070					$seconds = $modal.find( ".sec" ),
14071					endDuration = settings.reactionTime;
14072
14073				countdownInterval = setInterval( function() {
14074					if ( countdown( $minutes, $seconds, startTime, endDuration ) ) {
14075						clearInterval( countdownInterval );
14076
14077						// Let the user know their session has timed out
14078						$modal.find( "p" ).text( i18nText.timeoutAlready );
14079						$buttonContinue.text( i18nText.buttonSignin );
14080						$buttonEnd.hide();
14081					}
14082				}, 500 );
14083			}
14084		} );
14085	},
14086
14087	/**
14088	 * Initialize the inactivity and keepalive timeouts of the plugin
14089	 * @function reset
14090	 * @param {jQuery Event} event `reset.wb-sessto` event that triggered the function call
14091	 * @param {Object} settings Key-value object
14092	 */
14093	reset = function( event, settings ) {
14094		var $elm = $( event.target );
14095
14096		initEventTimeout( $elm, inactivityEvent, settings.inactivity, settings );
14097		if ( settings.refreshCallbackUrl !== null ) {
14098			initEventTimeout( $elm, keepaliveEvent, settings.sessionalive, settings );
14099		}
14100	},
14101
14102	/**
14103	 * Checks if the user wants to keep their session alive.
14104	 * @function inactivity
14105	 * @param {jQuery Event} event `confirm.wb-sessto` event that triggered the function call
14106	 */
14107	confirm = function( event ) {
14108		var elm = event.target,
14109			$elm = $( elm ),
14110			settings = $elm.data();
14111
14112		event.preventDefault();
14113		$.magnificPopup.close();
14114		clearInterval( countdownInterval );
14115
14116		// User wants their session maintained
14117		if ( settings.start !== undefined && ( getCurrentTime() - settings.start ) <= settings.reactionTime ) {
14118			$( selector )
14119				.trigger( resetEvent, settings )
14120				.trigger( keepaliveEvent, settings );
14121
14122		// Negative confirmation or the user took too long; logout
14123		} else {
14124			window.location.href = settings.logouturl;
14125		}
14126	},
14127
14128	/**
14129	 * Add the modal dialog's content and display it to the user
14130	 * @function openModal
14131	 * @param {Object} data Key-value object
14132	 */
14133	openModal = function( data ) {
14134
14135		// Detach the modal to prevent reflows while updating the element
14136		$modal = $modal.detach();
14137		$modal.find( ".modal-body" ).html( data.body );
14138		$modal.find( ".modal-footer" ).empty().append( data.buttons );
14139
14140		// Re-attach the modal and open the dialog
14141		$modal = $modal.insertAfter( $modalLink );
14142		$modalLink.magnificPopup( "open" );
14143
14144		// Execute the open callback if it exists
14145		if ( data.open ) {
14146			data.open();
14147		}
14148	},
14149
14150	/**
14151	 * Returns the current time in milliseconds
14152	 * @function getCurrentTime
14153	 * @returns {integer} Current time in milliseconds
14154	 */
14155	getCurrentTime = function() {
14156		return ( new Date() ).getTime();
14157	},
14158
14159	/**
14160	 * Parses a time value into a milliseconds integer value.
14161	 * @function parseTime
14162	 * @param {Mixed} value The time value to parse (integer or string)
14163	 * @returns {integer} Millisecond integer value parsed from the time value
14164	 */
14165	parseTime = function( value ) {
14166		var result, num, mult,
14167			powers = {
14168				ms: 1,
14169				cs: 10,
14170				ds: 100,
14171				s: 1000,
14172				das: 10000,
14173				hs: 100000,
14174				ks: 1000000
14175			};
14176
14177		if ( value == null ) { //eslint-disable-line no-eq-null
14178			return null;
14179		}
14180
14181		result = /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/.exec( $.trim( value.toString() ) );
14182		if ( result[ 2 ] ) {
14183			num = parseFloat( result[ 1 ] );
14184			mult = powers[ result[ 2 ] ] || 1;
14185			return num * mult;
14186		}
14187		return value;
14188	},
14189
14190	/**
14191	 * Converts a millisecond value into minutes and seconds
14192	 * @function getTime
14193	 * @param {integer} milliseconds The time value in milliseconds
14194	 * @returns {Object} An object with a seconds and minutes property
14195	 */
14196	getTime = function( milliseconds ) {
14197		var time = { minutes: "", seconds: "" };
14198
14199		if ( milliseconds != null ) { //eslint-disable-line no-eq-null
14200			time.minutes = parseInt( ( milliseconds / ( 1000 * 60 ) ) % 60, 10 );
14201			time.seconds = parseInt( ( milliseconds / 1000 ) % 60, 10 );
14202		}
14203		return time;
14204	},
14205
14206	/**
14207	 * Given 2 elements representing minutes and seconds, decrement their time value by 1 second
14208	 * @function countdown
14209	 * @param {jQuery DOM Element} $minutes Element that contains the minute value
14210	 * @param {jQuery DOM Element} $seconds Element that contains the second value
14211	 * @param { integer } startTime The time value of when the countdown started in milliseconds
14212	 * @param { integer } endDuration The time value of the duration of the countdown in milliseconds
14213	 * @returns {boolean} Is the countdown finished?
14214	 */
14215	countdown = function( $minutes, $seconds, startTime, endDuration ) {
14216		var newTime = getTime( endDuration - ( getCurrentTime() - startTime ) );
14217
14218		// Update the DOM elements
14219		$minutes.text( newTime.minutes );
14220		$seconds.text( newTime.seconds );
14221
14222		return newTime.minutes <= 0 && newTime.seconds <= 0;
14223	};
14224
14225// Bind the plugin events
14226$document.on( "timerpoke.wb " + initEvent + " " + keepaliveEvent + " " +
14227inactivityEvent + " " + resetEvent, selector, function( event, settings ) {
14228
14229	var eventType = event.type;
14230
14231	switch ( eventType ) {
14232	case "timerpoke":
14233	case "wb-init":
14234		init( event );
14235		break;
14236
14237	case "keepalive":
14238		keepalive( event, settings );
14239		break;
14240
14241	case "inactivity":
14242		inactivity( event, settings );
14243		break;
14244
14245	case "reset":
14246		reset( event, settings );
14247		break;
14248	}
14249} );
14250
14251$document.on( "click", "." + confirmClass, confirm );
14252
14253// Add the timer poke to initialize the plugin
14254wb.add( selector );
14255
14256} )( jQuery, DOMPurify, window, document, wb );
14257
14258/**
14259 * @title WET-BOEW Share widget
14260 * @overview Facilitates sharing Web content on social media platforms.
14261 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
14262 * @author @pjackson28
14263 */
14264( function( $, window, document, wb ) {
14265"use strict";
14266
14267/*
14268 * Variable and function definitions.
14269 * These are global to the plugin - meaning that they will be initialized once per page,
14270 * not once per instance of plugin on the page. So, this is a good place to define
14271 * variables that are common to all instances of the plugin on a page.
14272 */
14273var componentName = "wb-share",
14274	selector = "." + componentName,
14275	initEvent = "wb-init" + selector,
14276	shareLink = "shr-lnk",
14277	panelCount = 0,
14278	$document = wb.doc,
14279	i18n, i18nText,
14280
14281	/*
14282	 * Plugin users can override these defaults by setting attributes on the html elements that the
14283	 * selector matches.
14284	 */
14285	defaults = {
14286		hdLvl: "h2",
14287
14288		// Supported types are: "page", "video" and "audio"
14289		type: "page",
14290
14291		// For custom types
14292		// custType = " this comment" results in "Share this comment"
14293		custType: "",
14294
14295		url: wb.pageUrlParts.href,
14296		title: document.title || $document.find( "h1:first" ).text(),
14297
14298		pnlId: "",
14299		lnkClass: "",
14300		img: "",
14301		desc: "",
14302
14303		// For filtering the sites that area displayed and controlling the order
14304		// they are displayed. Empty array displays all sites in the default order.
14305		// Otherwise, it displays the sites in the order in the array using the
14306		// keys used by the sites object.
14307		filter: [],
14308
14309		sites: {
14310
14311			// The definitions of the available bookmarking sites, in URL use
14312			// '{u}' for the page URL, '{t}' for the page title, {i} for the image, and '{d}' for the description
14313			blogger: {
14314				name: "Blogger",
14315				url: "https://www.blogger.com/blog_this.pyra?t=&amp;u={u}&amp;n={t}"
14316			},
14317			diigo: {
14318				name: "Diigo",
14319				url: "https://www.diigo.com/post?url={u}&amp;title={t}"
14320			},
14321			facebook: {
14322				name: "Facebook",
14323				url: "https://www.facebook.com/sharer.php?u={u}&amp;t={t}"
14324			},
14325			gmail: {
14326				name: "Gmail",
14327				url: "https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=&su={t}&body={u}%0A{d}"
14328			},
14329			linkedin: {
14330				name: "LinkedIn®",
14331				url: "https://www.linkedin.com/shareArticle?mini=true&amp;url={u}&amp;title={t}&amp;ro=false&amp;summary={d}&amp;source="
14332			},
14333			myspace: {
14334				name: "MySpace",
14335				url: "https://www.myspace.com/Modules/PostTo/Pages/?u={u}&amp;t={t}"
14336			},
14337			pinterest: {
14338				name: "Pinterest",
14339				url: "https://www.pinterest.com/pin/create/button/?url={u}&amp;media={i}&amp;description={t}"
14340			},
14341			reddit: {
14342				name: "reddit",
14343				url: "https://reddit.com/submit?url={u}&amp;title={t}"
14344			},
14345			tinyurl: {
14346				name: "TinyURL",
14347				url: "https://tinyurl.com/create.php?url={u}"
14348			},
14349			tumblr: {
14350				name: "tumblr",
14351				url: "https://www.tumblr.com/share/link?url={u}&amp;name={t}&amp;description={d}"
14352			},
14353			twitter: {
14354				name: "Twitter",
14355				url: "https://twitter.com/intent/tweet?text={t}&url={u}"
14356			},
14357			yahoomail: {
14358				name: "Yahoo! Mail",
14359				url: "https://compose.mail.yahoo.com/?to=&subject={t}&body={u}%0A{d}"
14360			},
14361			whatsapp: {
14362				name: "Whatsapp",
14363				url: "https://api.whatsapp.com/send?text={t}%0A{d}%0A{u}"
14364			}
14365		}
14366	},
14367
14368	/**
14369	 * @method init
14370	 * @param {jQuery Event} event Event that triggered the function call
14371	 */
14372	init = function( event ) {
14373
14374		// Start initialization
14375		// returns DOM object = proceed with init
14376		// returns undefined = do not proceed with init (e.g., already initialized)
14377		var elm = wb.init( event, componentName, selector ),
14378			sites, heading, settings, panel, link, $share, $elm,
14379			pageHref, pageTitle, pageImage, pageDescription,
14380			siteProperties, url, shareText, id, pnlId, regex,
14381			filter, i, len, keys, key;
14382
14383		if ( elm ) {
14384
14385			// Only initialize the i18nText once
14386			if ( !i18nText ) {
14387				i18n = wb.i18n;
14388				i18nText = {
14389					shareText: i18n( "shr-txt" ),
14390					page: i18n( "shr-pg" ),
14391					video: i18n( "shr-vid" ),
14392					audio: i18n( "shr-aud" ),
14393					disclaimer: i18n( "shr-disc" ),
14394					email: i18n( "email" )
14395				};
14396
14397				// Add an email mailto option
14398				defaults.sites.email = {
14399					name: i18nText.email,
14400					url: "mailto:?subject={t}&body={u}%0A{d}",
14401					isMailto: true
14402				};
14403			}
14404
14405			$elm = $( elm );
14406			settings = $.extend(
14407				true,
14408				{},
14409				defaults,
14410				window[ componentName ],
14411				wb.getData( $elm, componentName )
14412			);
14413			sites = settings.sites;
14414			filter = settings.filter;
14415			heading = settings.hdLvl;
14416
14417			shareText = i18nText.shareText + ( settings.custType.length !== 0 ? settings.custType : i18nText[ settings.type ] );
14418			pnlId = settings.pnlId;
14419			id = "shr-pg" + ( pnlId.length !== 0 ? "-" + pnlId : panelCount );
14420			pageHref = encodeURIComponent( settings.url );
14421
14422			regex = /'|&#39;|&apos;/g;
14423			pageTitle = encodeURIComponent( settings.title )
14424				.replace( regex, "%27" );
14425			pageImage = encodeURIComponent( settings.img );
14426			pageDescription = encodeURIComponent( settings.desc )
14427				.replace( regex, "%27" );
14428
14429			// Don't create the panel for the second link (class="link-only")
14430			if ( elm.className.indexOf( "link-only" ) === -1 ) {
14431				panel = "<section id='" + id  + "' class='shr-pg mfp-hide modal-dialog modal-content overlay-def" +
14432					"'><header class='modal-header'><" + heading + " class='modal-title'>" +
14433					shareText + "</" + heading + "></header><div class='modal-body'>" +
14434					"<ul class='list-unstyled colcount-xs-2'>";
14435
14436				// If there is no filter array of site keys, then generate an array of site keys
14437				if ( !filter || filter.length === 0 ) {
14438					keys = [];
14439					for ( key in sites ) {
14440						if ( Object.prototype.hasOwnProperty.call( sites, key ) ) {
14441							keys.push( key );
14442						}
14443					}
14444				} else {
14445					keys = filter;
14446				}
14447
14448				// i18n-friendly sort of the site keys
14449				keys.sort( function( x, y ) {
14450					return wb.normalizeDiacritics( x ).localeCompare( wb.normalizeDiacritics( y ) );
14451				} );
14452				len = keys.length;
14453
14454				// Generate the panel
14455				for ( i = 0; i !== len; i += 1 ) {
14456					key = keys[ i ];
14457					siteProperties = sites[ key ];
14458					url = siteProperties.url
14459						.replace( /\{u\}/, pageHref )
14460						.replace( /\{t\}/, pageTitle )
14461						.replace( /\{i\}/, pageImage )
14462						.replace( /\{d\}/, pageDescription );
14463					panel += "<li><a href='" + url + "' class='" + shareLink +
14464						" " + ( siteProperties.isMailto ? "email" : key ) +
14465						" btn btn-default' target='_blank' rel='noreferrer noopener'>" +
14466						siteProperties.name + "</a></li>";
14467				}
14468
14469				panel += "</ul><p class='col-sm-12 shr-dscl'>" + i18nText.disclaimer +
14470					"</p><div class='clearfix'></div></div></section>";
14471				panelCount += 1;
14472			}
14473			link = "<a href='#" + id + "' aria-controls='" + id +
14474				"' class='shr-opn wb-lbx " + settings.lnkClass +
14475				"'><span class='glyphicon glyphicon-share'></span>" +
14476				shareText + "</a>";
14477
14478			$share = $( ( panel ? panel : "" ) + link );
14479
14480			$elm.append( $share );
14481
14482			$share
14483				.trigger( "wb-init.wb-lbx" );
14484
14485			// Identify that initialization has completed
14486			wb.ready( $elm, componentName );
14487		}
14488	};
14489
14490// Bind the init event of the plugin
14491$document.on( "timerpoke.wb " + initEvent, selector, init );
14492
14493// Add the timer poke to initialize the plugin
14494wb.add( selector );
14495
14496} )( jQuery, window, document, wb );
14497
14498/**
14499 * @title WET-BOEW step form
14500 * @overview Provide ability for a form to be broken into steps.
14501 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
14502 * @author @kodecount
14503 */
14504( function( $, window, document, wb ) {
14505"use strict";
14506
14507/*
14508 * Variable and function definitions.
14509 * These are global to the plugin - meaning that they will be initialized once per page,
14510 * not once per instance of plugin on the page. So, this is a good place to define
14511 * variables that are common to all instances of the plugin on a page.
14512 */
14513var componentName = "wb-steps",
14514	selector = ".provisional." + componentName,
14515	initEvent = "wb-init" + selector,
14516	$document = wb.doc,
14517	i18n, i18nText,
14518	btnPrevious, btnNext, btnSubmit,
14519
14520	/**
14521	 * @method init
14522	 * @param {jQuery Event} evt Event that triggered the function call
14523	 */
14524	init = function( evt ) {
14525
14526		// Start initialization
14527		// returns DOM object = proceed with init
14528		// returns undefined = do not proceed with init (e.g., already initialized)
14529		var elm = wb.init( evt, componentName, selector );
14530
14531		if ( elm ) {
14532
14533			// Ensure there is a unique id on the element
14534			if ( !elm.id ) {
14535				elm.id = wb.getId();
14536			}
14537
14538			// Only initialize the i18nText once
14539			if ( !i18nText ) {
14540				i18n = wb.i18n;
14541				i18nText = {
14542					prv: i18n( "prv" ),
14543					nxt: i18n( "nxt" )
14544				};
14545			}
14546
14547			/*
14548			 * Variable and function definitions
14549			 * These will be initialized once per instance of plugin.
14550			 */
14551			var form = elm.getElementsByTagName( "FORM" )[ 0 ],
14552				fieldsets = ( form ) ? $( form ).children( "fieldset" ) : 0,
14553				hasStepsInitialized;
14554
14555			// Initialize navigation buttons
14556			btnPrevious = createStepsButton( "prev", "mrgn-rght-sm mrgn-bttm-md", i18nText.prv );
14557			btnNext = createStepsButton( "next", "mrgn-bttm-md", i18nText.nxt );
14558			btnSubmit = form.querySelector( "input[type=submit], button[type=submit]" );
14559			btnSubmit.classList.add( "mrgn-bttm-md" );
14560
14561			/*
14562			 * Determines if html is correctly formatted and initialize all fieldsets/legend combinations into steps.
14563			 */
14564			for ( var i = 0, len = fieldsets.length; i < len; i++ ) {
14565
14566				/*
14567				 * Variable and function definitions
14568				 * These well be initialized once per instance of each fieldset.
14569				 * Determines the following business rules:
14570				 *  -Only allow steps if elements are in proper order fieldset -> legend -> div
14571				 *  -Only allow NEXT button on first step
14572				 *  -Only allow final SUBMIT button on last step
14573				 */
14574				var fieldset = fieldsets[ i ],
14575					isFirstFieldset = ( i === 0 ) ? true : false,
14576					isLastFieldset = ( i === ( len - 1 ) ) ? true : false,
14577					legend = fieldset.firstElementChild,
14578					div = ( legend && legend.tagName === "LEGEND" ) ? legend.nextElementSibling : false,
14579					buttonGroup = document.createElement( "div" ),
14580					wrapper = document.createElement( "div" ),
14581					buttonGroupClassList = buttonGroup.classList,
14582					divClassList = div.classList;
14583
14584				buttonGroupClassList.add( "buttons" );
14585				fieldset.parentNode.insertBefore( wrapper, fieldset );
14586				wrapper.appendChild( fieldset );
14587				wrapper.classList.add( "steps-wrapper" );
14588
14589				if ( div && div.tagName === "DIV" ) {
14590					var btnClone;
14591					hasStepsInitialized = true;
14592
14593					if ( !isFirstFieldset ) {
14594						btnClone = btnPrevious.cloneNode( true );
14595						setStepsBtnEvent( btnClone );
14596						buttonGroup.appendChild( btnClone );
14597						wrapper.appendChild( buttonGroup );
14598					}
14599
14600					if ( !isLastFieldset ) {
14601						btnClone = btnNext.cloneNode( true );
14602						setStepsBtnEvent( btnClone );
14603						buttonGroup.appendChild( btnClone );
14604					} else {
14605						buttonGroup.appendChild( btnSubmit );
14606					}
14607
14608					wrapper.appendChild( buttonGroup );
14609
14610					fieldset.classList.add( "wb-tggle-fildst" );
14611					divClassList.add( "hidden" );
14612					buttonGroupClassList.add( "hidden" );
14613
14614					if ( isFirstFieldset ) {
14615						legend.classList.add( "wb-steps-active" );
14616						btnClone.classList.remove( "hidden" );
14617						divClassList.remove( "hidden" );
14618						buttonGroupClassList.remove( "hidden" );
14619					}
14620				}
14621			}
14622
14623			/*
14624			 * if steps has initialized hide any precreated submit or reset buttons
14625			 */
14626			if ( form && hasStepsInitialized ) {
14627				$( form ).children( "input" ).hide();
14628				wb.ready( $( elm ), componentName );
14629			}
14630		}
14631	},
14632
14633	/**
14634	 * @method createStepsButton
14635	 * @param {string var} tagName, {string var} type, {boolean var} isPrimary, {string var} style, {string var} text
14636	 * @returns {Object} A ready-to-use button element
14637	 */
14638	createStepsButton = function( type, style, text ) {
14639		var control = document.createElement( "BUTTON" );
14640
14641		// set default attributes
14642		control.className = ( type === "prev" ? "btn btn-md btn-default" : "btn btn-md btn-primary" ) + " " + style;
14643		control.setAttribute( "type", "button" );
14644		control.innerHTML = text;
14645
14646		return control;
14647	},
14648
14649	/**
14650	 * @method setStepsBtnEvent
14651	 * @param {JavaScript element} elm
14652	 */
14653	setStepsBtnEvent = function( elm ) {
14654		elm.addEventListener( "click", function( evt ) {
14655			evt.preventDefault();
14656			var classes = ( this.className ) ? this.className : false,
14657				isNext = ( classes && classes.indexOf( "btn-primary" ) > -1 ),
14658				isFormValid = true,
14659				parentElement = this.parentElement,
14660				parentParentElement = parentElement.parentElement,
14661				parentPreviousClassList = parentElement.previousElementSibling.classList;
14662
14663			// confirm if form is valid
14664			if ( isNext && jQuery.validator && jQuery.validator !== "undefined" ) {
14665				isFormValid =  $( "#" + parentParentElement.parentElement.id ).valid();
14666			}
14667
14668			// continue if valid
14669			if ( isFormValid ) {
14670				showSteps( parentParentElement, isNext );
14671				if ( isNext ) {
14672					parentPreviousClassList.remove( "wb-steps-error" );
14673				}
14674			} else if ( isNext && !isFormValid ) {
14675				parentPreviousClassList.add( "wb-steps-error" );
14676			}
14677		} );
14678	},
14679
14680	/**
14681	 * @method showSteps
14682	 * @param {JavaScript element} elm and {boolean var} isNext
14683	 */
14684	showSteps = function( elm, isNext ) {
14685		var fieldsetElement = elm.getElementsByTagName( "FIELDSET" )[ 0 ],
14686			fields = fieldsetElement.getElementsByTagName( "div" )[ 0 ],
14687			legend = fieldsetElement.getElementsByTagName( "legend" )[ 0 ],
14688			buttonGroup = elm.querySelector( "div.buttons" ),
14689			fieldset;
14690
14691		if ( elm ) {
14692			fields.classList.add( "hidden" );
14693			buttonGroup.classList.add( "hidden" );
14694
14695			if ( legend ) {
14696				legend.classList.remove( "wb-steps-active" );
14697			}
14698
14699			fieldset = ( !isNext ) ? elm.previousElementSibling : elm.nextElementSibling;
14700			if ( fieldset ) {
14701				legend = fieldset.getElementsByTagName( "LEGEND" )[ 0 ];
14702				elm = fieldset.getElementsByTagName( "DIV" )[ 0 ];
14703				buttonGroup = fieldset.querySelector( "div.buttons" );
14704				if ( legend ) {
14705					legend.classList.add( "wb-steps-active" );
14706					legend.tabIndex = 0;
14707					legend.focus();
14708					legend.tabIndex = -1;
14709				}
14710				if ( elm ) {
14711					elm.classList.remove( "hidden" );
14712				}
14713				if ( buttonGroup ) {
14714					buttonGroup.classList.remove( "hidden" );
14715				}
14716			}
14717		}
14718	};
14719
14720// Bind the init event of the plugin
14721$document.on( "timerpoke.wb " + initEvent, selector, init );
14722
14723// Add the timer poke to initialize the plugin
14724wb.add( selector );
14725
14726} )( jQuery, window, document, wb );
14727
14728/**
14729 * @title WET-BOEW Tables
14730 * @overview Integrates the DataTables plugin into WET providing searching, sorting, filtering, pagination and other advanced features for tables.
14731 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
14732 * @author @jeresiv
14733 */
14734/*jshint scripturl:true*/
14735( function( $, window, wb ) {
14736"use strict";
14737
14738/*
14739 * Variable and function definitions.
14740 * These are global to the plugin - meaning that they will be initialized once per page,
14741 * not once per instance of plugin on the page. So, this is a good place to define
14742 * variables that are common to all instances of the plugin on a page.
14743 */
14744var componentName = "wb-tables",
14745	selector = "." + componentName,
14746	initEvent = "wb-init" + selector,
14747	$document = wb.doc,
14748	idCount = 0,
14749	i18n, i18nText, defaults,
14750
14751	/**
14752	 * @method init
14753	 * @param {jQuery Event} event Event that triggered the function call
14754	 */
14755	init = function( event ) {
14756
14757		// Start initialization
14758		// returns DOM object = proceed with init
14759		// returns undefined = do not proceed with init (e.g., already initialized)
14760		var elm = wb.init( event, componentName, selector ),
14761			elmId;
14762
14763		if ( elm ) {
14764			elmId = elm.id;
14765
14766			// Ensure there is a unique id on the element
14767			if ( !elmId ) {
14768				elmId = componentName + "-id-" + idCount;
14769				idCount += 1;
14770				elm.id = elmId;
14771			}
14772
14773			// Only initialize the i18nText once
14774			if ( !i18nText ) {
14775				i18n = wb.i18n;
14776				i18nText = {
14777					aria: {
14778						sortAscending: i18n( "sortAsc" ),
14779						sortDescending: i18n( "sortDesc" )
14780					},
14781					emptyTable: i18n( "emptyTbl" ),
14782					info: i18n( "infoEntr" ),
14783					infoEmpty: i18n( "infoEmpty" ),
14784					infoFiltered: i18n( "infoFilt" ),
14785					lengthMenu: i18n( "lenMenu" ),
14786					loadingRecords: i18n( "load" ),
14787					paginate: {
14788						first: i18n( "first" ),
14789						last: i18n( "last" ),
14790						next: i18n( "nxt" ),
14791						previous: i18n( "prv" ),
14792						page: i18n( "page" )
14793					},
14794					processing: i18n( "process" ),
14795					search: i18n( "filter" ),
14796					thousands: i18n( "info1000" ),
14797					zeroRecords: i18n( "infoEmpty" ),
14798					tblFilterInstruction: i18n( "tbFilterInst" )
14799				};
14800			}
14801
14802			defaults = {
14803				asStripeClasses: [],
14804				language: i18nText,
14805				dom: "<'top'fil>rt<'bottom'p><'clear'>"
14806			};
14807
14808			Modernizr.load( {
14809				load: [ "site!deps/jquery.dataTables" + wb.getMode() + ".js" ],
14810				testReady: function() {
14811					return ( $.fn.dataTable && $.fn.dataTable.version );
14812				},
14813				complete: function() {
14814					var $elm = $( "#" + elmId ),
14815						dataTableExt = $.fn.dataTableExt,
14816						settings = wb.getData( $elm, componentName ) || {};
14817
14818					// Explicitly deactivate the paging for the filterEmphasis provisional feature/styling when not configured
14819					if ( $elm.hasClass( "provisional" ) && $elm.hasClass( "filterEmphasis" ) ) {
14820						settings.paging = settings.paging ? settings.paging : false;
14821					}
14822
14823					/*
14824					 * Extend sorting support
14825					 */
14826					$.extend( dataTableExt.type.order, {
14827
14828						// Enable internationalization support in the sorting
14829						"html-pre": function( a ) {
14830							return wb.normalizeDiacritics(
14831								!a ? "" : a.replace ?
14832									a.replace( /<.*?>/g, "" ).toLowerCase() : a + ""
14833							);
14834						},
14835						"string-case-pre": function( a ) {
14836							return wb.normalizeDiacritics( a );
14837						},
14838						"string-pre": function( a ) {
14839							return wb.normalizeDiacritics( a );
14840						},
14841
14842						// Formatted number sorting
14843						"formatted-num-asc": function( a, b ) {
14844							return wb.formattedNumCompare( a, b );
14845						},
14846						"formatted-num-desc": function( a, b ) {
14847							return wb.formattedNumCompare( b, a );
14848						}
14849					} );
14850
14851					// Create the DataTable object
14852					$elm.dataTable( $.extend( true, {}, defaults, window[ componentName ], settings ) );
14853				}
14854			} );
14855		}
14856	},
14857	updatePaginationMarkup = function( $pagination, setFocusOnId ) {
14858
14859		var ol = document.createElement( "OL" ),
14860			li = document.createElement( "LI" ),
14861			paginate_buttons = $pagination.find( ".paginate_button" ),
14862			navFocusOnId;
14863
14864		if ( $pagination.length === 0 ) {
14865			return;
14866		}
14867
14868		// Set the element to get the focus upon navigation
14869		navFocusOnId = setFocusOnId || $pagination.get( 0 ).id;
14870
14871		// Update Pagination List
14872		for ( var i = 0; i < paginate_buttons.length; i++ ) {
14873			var item = li.cloneNode( true );
14874			item.appendChild( paginate_buttons[ i ] );
14875			ol.appendChild( item );
14876		}
14877
14878		ol.className = "pagination mrgn-tp-0 mrgn-bttm-0";
14879		$pagination.empty();
14880		$pagination.append( ol );
14881
14882		// Update the aria-pressed properties on the pagination buttons
14883		// Should be pushed upstream to DataTables
14884		$pagination.find( ".paginate_button" )
14885			.attr( {
14886				"href": "#" + navFocusOnId
14887			} )
14888
14889			// This is required to override the datatable.js (v1.10.13) behavior to cancel the event propagation on anchor element.
14890			.on( "keypress", function( evn ) {
14891				if ( evn.keyCode === 13 ) {
14892					window.location = evn.target.href;
14893				}
14894			} )
14895
14896			.not( ".previous, .next" )
14897			.attr( "aria-pressed", "false" )
14898			.html( function( index, oldHtml ) {
14899				return "<span class='wb-inv'>" + i18nText.paginate.page + " </span>" + oldHtml;
14900			} )
14901			.filter( ".current" )
14902			.attr( "aria-pressed", "true" );
14903	};
14904
14905// Bind the init event of the plugin
14906$document.on( "timerpoke.wb " + initEvent, selector, init );
14907
14908// Handle the draw.dt event
14909$document.on( "draw.dt", selector, function( event, settings ) {
14910	var $elm = $( event.target ),
14911		pagination = $elm.next( ".bottom" ).find( "div:first-child" ),
14912		pagination_top = $elm.prevAll( ".top" ).find( "div.dataTables_paginate" ),
14913		paginate_buttons = $elm.next( ".bottom" ).find( ".paginate_button" ),
14914		pbLength = paginate_buttons.length,
14915		pHasLF = pagination.find( ".last, .first" ).length === 2,
14916		pHasPN = pagination.find( ".previous, .next" ).length === 2;
14917
14918	// Handle sorting/ordering
14919	var order = $elm.dataTable( { "retrieve": true } ).api().order();
14920	$elm.find( "th" ).each( function( index ) {
14921		var $th = $( this ),
14922			$btn = $th.find( "button" );
14923		if ( order && order.length && order[ 0 ][ 0 ] === index ) {
14924			var label = ( order[ 0 ][ 1 ] === "desc" ) ? i18nText.aria.sortAscending : i18nText.aria.sortDescending;
14925			label = $btn.text() + label;
14926			$btn.attr( "title", label );
14927		}
14928		$th.removeAttr( "aria-label" );
14929	} );
14930
14931	// Determine if Pagination required
14932	if (
14933		pbLength === 1 ||
14934		(
14935			pbLength === 3 &&
14936			(
14937				pHasLF ||
14938				pHasPN
14939			)
14940		) ||
14941		(
14942			pbLength === 5 &&
14943			pHasLF &&
14944			pHasPN
14945		)
14946	) {
14947		pagination.addClass( "hidden" );
14948		pagination_top.addClass( "hidden" );
14949	} else {
14950
14951		// Make sure Pagination is visible
14952		pagination.removeClass( "hidden" );
14953		pagination_top.removeClass( "hidden" );
14954
14955		updatePaginationMarkup( pagination, $elm.get( 0 ).id );
14956		updatePaginationMarkup( pagination_top );
14957	}
14958
14959	// Identify that the table has been updated
14960	$elm.trigger( "wb-updated" + selector, [ settings ] );
14961} );
14962
14963// Identify that initialization has completed
14964$document.on( "init.dt", selector, function( event ) {
14965	var $elm = $( event.target ),
14966		settings = $.extend( true, {}, defaults, window[ componentName ], wb.getData( $elm, componentName ) );
14967
14968	// Handle sorting/ordering
14969	var ordering = ( settings && settings.ordering === false ) ? false : true;
14970	if ( ordering ) {
14971		$elm.find( "thead th" ).each( function() {
14972			var $th = $( this ),
14973				label = ( $th.attr( "aria-sort" ) === "ascending" ) ? i18nText.aria.sortDescending : i18nText.aria.sortAscending;
14974			if ( ( $th.attr( "data-orderable" ) !== "false" ) && !( $th.hasClass( "sorting_disabled" ) ) ) {
14975				$th.html( "<button type='button' aria-controls='" + $th.attr( "aria-controls" ) +  "' title='" + $th.text().replace( /'/g, "&#39;" ) + label + "'>" + $th.html() + "<span class='sorting-cnt'><span class='sorting-icons' aria-hidden='true'></span></span></button>" );
14976				$th.removeAttr( "aria-label tabindex aria-controls" );
14977			}
14978		} );
14979		$elm.attr( "aria-label", i18nText.tblFilterInstruction );
14980	}
14981
14982	// Apply the filter emphasis style
14983	if ( $elm.hasClass( "provisional" ) && $elm.hasClass( "filterEmphasis" ) ) {
14984		$elm.parent().addClass( "provisional filterEmphasis" );
14985	}
14986
14987	wb.ready( $( event.target ), componentName );
14988} );
14989
14990// Handle the draw.dt event
14991$document.on( "submit", ".wb-tables-filter", function( event ) {
14992
14993	event.preventDefault();
14994
14995	var $form = $( this ),
14996		$datatable = $( "#" + $form.data( "bind-to" ) ).dataTable( { "retrieve": true } ).api(),
14997		$toNumber = function stringToNumber( number ) {
14998			number = number.replace( /[^0-9\-,.]+/g, "" );
14999			if ( /[,]\d{1,2}$/.test( number ) ) {
15000				number = number.replace( /(\d{2})$/, ".$1" );
15001			}
15002			number = number.replace( /,/g, "" );
15003			return parseFloat( number );
15004		},
15005		$isDate = function isDate( date ) {
15006			return date instanceof Date && !isNaN( date );
15007		};
15008
15009	// Lets reset the search
15010	$datatable.search( "" ).columns().search( "" );
15011
15012	// Lets loop throug all options
15013	var $prevCol = -1, $cachedVal = "";
15014	$form.find( "[name]" ).each( function() {
15015		var $elm = $( this ),
15016			$val = $elm.val(),
15017			$value = "",
15018			$regex = "",
15019			$column = parseInt( $elm.attr( "data-column" ), 10 ),
15020			$isAopts = $elm.data( "aopts" ),
15021			$aoptsSelector = "[data-aopts*='\"column\": \"" + $column + "\"']:checked",
15022			$aopts = $( $aoptsSelector ),
15023			$aoType = ( $aopts && $aopts.data( "aopts" ) ) ? $aopts.data( "aopts" ).type.toLowerCase() : "",
15024			$fData;
15025
15026		// Ignore the advanced options fields
15027		if ( $isAopts ) {
15028			return;
15029		}
15030
15031		// Verifies if filtering the same column
15032		if ( $column !== $prevCol || $prevCol === -1 ) {
15033			$cachedVal = "";
15034		}
15035		$prevCol = $column;
15036
15037		// Filters based on input type
15038		if ( $elm.is( "select" ) ) {
15039			$value = $elm.find( "option:selected" ).val();
15040		} else if ( $elm.is( "input[type='number']" ) ) {
15041			var $minNum, $maxNum = null;
15042
15043			// Retain minimum number (always the first number input)
15044			if ( $cachedVal === "" ) {
15045				$cachedVal = parseFloat( $val );
15046				$cachedVal = ( $cachedVal ) ? $cachedVal : "-0";
15047			} else {
15048				$maxNum = parseFloat( $val );
15049				$maxNum = ( $maxNum ) ? $maxNum : "-0";
15050			}
15051			$minNum = $cachedVal;
15052
15053			//Number filtering logic needs to be reviewed in order to remove the "-0" value (issue #9235)
15054			// Generates a list of numbers (within the min and max number)
15055			if ( !isNaN( $minNum ) && !isNaN( $maxNum ) ) {
15056				$fData = $datatable.column( $column ).data().filter( function( obj ) {
15057					var $num = $toNumber( obj.toString() );
15058
15059					if ( !isNaN( $num ) ) {
15060						if ( $aoType === "and" ) {
15061							if ( $cachedVal !== $maxNum && $cachedVal !== "-0" && $maxNum !== "0" && $num >= $minNum && $num <= $maxNum ) {
15062								return true;
15063							}
15064						} else {
15065							if ( $maxNum === null ) { // only one input number
15066								return $minNum === "-0" || $minNum === $num;
15067							} else if ( $maxNum === $cachedVal && $cachedVal === "-0" ) { // both are empty
15068								return true;
15069							} else if ( $maxNum !== "-0" && $minNum === $maxNum && $num === $maxNum ) { // min and max are the same
15070								return true;
15071							} else if ( $maxNum === "-0" && $num >= $minNum ) { // max number is missing
15072								return true;
15073							} else if ( $cachedVal === "-0" && $num <= $maxNum ) { // min number is missing
15074								return true;
15075							} else if ( $cachedVal !== "-0" && $num >= $minNum && $num <= $maxNum ) { // min and max are present
15076								return true;
15077							}
15078						}
15079					}
15080					return false;
15081				} );
15082				$fData = $fData.join( "|" );
15083
15084				// If no numbers match set as -0, so no results return
15085				$value = ( $fData ) ? $fData : "-0";
15086				$regex = "(" + $value.replace( /&nbsp;|\s/g, "\\s" ).replace( /\$/g, "\\$" ) + ")";
15087			}
15088		} else if ( $elm.is( "input[type='date']" ) && $val ) {
15089			var $minDate, $maxDate;
15090
15091			// Retain minimum date (always the first date input)
15092			if ( $cachedVal === "" ) {
15093				$cachedVal = new Date( $val );
15094				$cachedVal.setDate( $cachedVal.getDate() + 1 );
15095				$cachedVal.setHours( 0, 0, 0, 0 );
15096			}
15097			$minDate = $cachedVal;
15098
15099			// Maximum date is always the current selected date
15100			$maxDate = new Date( $val );
15101			$maxDate.setDate( $maxDate.getDate() + 1 );
15102			$maxDate.setHours( 23, 59, 59, 999 );
15103
15104			// Generates a list of date strings (within the min and max date)
15105			$fData = $datatable.column( $column ).data().filter( function( obj ) {
15106				var $date = obj.replace( /[0-9]{2}\s[0-9]{2}:/g, function( e ) {
15107					return e.replace( /\s/g, "T" );
15108				} );
15109
15110				if ( !$date.includes( "T" ) ) {
15111					$date = $date + "T00:00:00";
15112				}
15113				$date = new Date( $date );
15114				$date.setHours( 0, 0, 0, 0 );
15115
15116				if ( !$isDate( $minDate ) || !$isDate( $maxDate ) || !$isDate( $date ) ) {
15117					return;
15118				}
15119				return ( $date >= $minDate && $date <= $maxDate );
15120			} );
15121			$fData = $fData.join( "|" );
15122
15123			// If no dates match set as element value, so no results return
15124			$value = ( $fData ? $fData : $val );
15125		} else if ( $elm.is( ":checkbox" ) ) {
15126
15127			// Verifies if checkbox is checked before setting value
15128			if ( $elm.is( ":checked" ) ) {
15129				if ( $aoType === "both" ) {
15130					$cachedVal += "(?=.*\\b" + $val + "\\b)";
15131				} else {
15132					$cachedVal += ( $cachedVal.length > 0 ) ? "|" : "";
15133					$cachedVal += $val;
15134				}
15135
15136				$value = $cachedVal;
15137				$value = $value.replace( /\s/g, "\\s*" );
15138
15139				// Adjust regex based on advanced options
15140				switch ( $aoType ) {
15141				case "both":
15142					$regex = "(" + $value + ").*";
15143					break;
15144				case "either":
15145					$regex = "^(" + $value + ")$";
15146					break;
15147				case "and":
15148					$regex = ( $value.indexOf( "|" ) > -1 ) ? "^(" + $value + "|[,\\s])(" + $value + "|[,\\s])+$" : "(" + $value + ")";
15149					break;
15150				case "any":
15151				default:
15152					$regex = "(" + $value + ")";
15153					break;
15154				}
15155			}
15156		} else {
15157			$value = $val;
15158		}
15159
15160		if ( $value ) {
15161
15162			// Verifies if regex was preset, if not preset use 'contains value' as default
15163			if ( !$regex ) {
15164				if ( $elm[ 0 ].getAttribute( "data-exact" ) ) {
15165					$regex = "^" + $value + "$";
15166				} else {
15167					$value = $value.replace( /\s/g, "\\s*" );
15168					$regex = "(" + $value + ")";
15169				}
15170			}
15171
15172			$datatable.column( $column ).search( $regex, true );
15173		}
15174	} );
15175	$datatable.draw();
15176	return false;
15177} );
15178
15179$document.on( "click", ".wb-tables-filter [type='reset']", function( event ) {
15180	event.preventDefault();
15181
15182	var $form = $( this ).closest( ".wb-tables-filter" ),
15183		$datatable = $( "#" + $form.data( "bind-to" ) ).dataTable( { "retrieve": true } ).api();
15184
15185	$datatable.search( "" ).columns().search( "" ).draw();
15186
15187	$form.find( "select" ).prop( "selectedIndex", 0 );
15188	$form.find( "input:checkbox, input:radio" ).prop( "checked", false );
15189	$form.find( ":input" ).not( ":button, :submit, :reset, :hidden, :checkbox, :radio" ).val( "" );
15190	return false;
15191} );
15192
15193// Add the timer poke to initialize the plugin
15194wb.add( selector );
15195
15196} )( jQuery, window, wb );
15197
15198/**
15199 * @title WET-BOEW Tabbed interface
15200 * @overview Dynamically stacks multiple sections of content, transforming them into a tabbed interface.
15201 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
15202 * @author WET Community
15203 */
15204( function( $, window, wb ) {
15205"use strict";
15206
15207/*
15208 * Variable and function definitions.
15209 * These are global to the plugin - meaning that they will be initialized once per page,
15210 * not once per instance of plugin on the page. So, this is a good place to define
15211 * variables that are common to all instances of the plugin on a page.
15212 */
15213var componentName = "wb-tabs",
15214	selector = "." + componentName,
15215	initEvent = "wb-init" + selector,
15216	shiftEvent = "wb-shift" + selector,
15217	selectEvent = "wb-select" + selector,
15218	updatedEvent = "wb-updated" + selector,
15219	setFocusEvent = "setfocus.wb",
15220	controls = selector + " ul[role=tablist] a, " + selector + " ul[role=tablist] .tab-count",
15221	initialized = false,
15222	tabsAccordionClass = "tabs-acc",
15223	nestedTglPanelSelector = "> .tabpanels > details > .tgl-panel",
15224	activePanel = "-activePanel",
15225	activateEvent = "click keydown",
15226	pagePath = wb.pageUrlParts.pathname + "#",
15227	$document = wb.doc,
15228	i18n, i18nText,
15229
15230	// Includes "smallview", "xsmallview" and "xxsmallview"
15231	smallViewPattern = "smallview",
15232	isSmallView, oldIsSmallView,
15233
15234	defaults = {
15235		excludePlay: false,
15236		interval: 6,
15237		updateHash: false,
15238		ignoreSession: false
15239	},
15240
15241	/**
15242	 * @method init
15243	 * @param {jQuery Event} event Event that triggered the function call
15244	 */
15245	init = function( event ) {
15246
15247		// Start initialization
15248		// returns DOM object = proceed with init
15249		// returns undefined = do not proceed with init (e.g., already initialized)
15250		var elm = wb.init( event, componentName, selector ),
15251			isCarousel = true,
15252			open = "open",
15253			hashTargetLen = 0,
15254			$panels, $tablist, activeId, $openPanel, $elm, elmId, $hashTarget,
15255			settings, $panel, i, len, tablist, isOpen, hashFocus,
15256			newId, positionY, groupClass, $tabPanels, openByHash;
15257
15258		if ( elm ) {
15259			$elm = $( elm );
15260
15261			// For backwards compatibility. Should be removed in WET v4.1
15262			if ( $elm.children( ".tabpanels" ).length === 0 ) {
15263				$elm.children( "[role=tabpanel], details" ).wrapAll( "<div class='tabpanels'></div>" );
15264			}
15265
15266			$panels = $elm.find( "> .tabpanels > [role=tabpanel], > .tabpanels > details" );
15267			$tablist = $elm.children( "[role=tablist]" );
15268			isCarousel = $tablist.length !== 0;
15269
15270			// If a carousel contains only 1 panel, remove its controls, visually-hide its thumbnails and prevent it from attempting to play
15271			if ( isCarousel && $panels.length === 1 ) {
15272
15273				$elm.removeClass( "show-thumbs playing" );
15274				$elm.addClass( "exclude-controls" );
15275			}
15276
15277			activeId = wb.jqEscape( wb.pageUrlParts.hash.substring( 1 ) );
15278			hashFocus = activeId.length !== 0;
15279			$openPanel = hashFocus ? $panels.filter( "#" + activeId ) : undefined;
15280			openByHash = $openPanel && $openPanel.length !== 0;
15281			elmId = elm.id;
15282
15283			settings = $.extend(
15284				true,
15285				{},
15286				defaults,
15287				{
15288					interval: $elm.hasClass( "slow" ) ?
15289						9 : $elm.hasClass( "fast" ) ?
15290							3 : defaults.interval,
15291					excludeControls: $elm.hasClass( "exclude-controls" ),
15292					excludePlay: $elm.hasClass( "exclude-play" ),
15293					updateHash: $elm.hasClass( "update-hash" ),
15294					playing: $elm.hasClass( "playing" ),
15295					ignoreSession: $elm.hasClass( "ignore-session" )
15296				},
15297				window[ componentName ],
15298				wb.getData( $elm, componentName )
15299			);
15300
15301			try {
15302
15303				// If the panel was not set by URL hash
15304				if ( !openByHash ) {
15305					if ( hashFocus ) {
15306						$hashTarget = $panels.find( "#" + activeId );
15307						hashTargetLen = $hashTarget.length;
15308					}
15309
15310					// If the anchor target is within a panel, then open that panel
15311					if ( hashTargetLen !== 0 ) {
15312						activeId = $hashTarget.closest( "[role=tabpanel]" ).attr( "id" );
15313
15314					// Attempt to retrieve active panel from sessionStorage
15315					} else {
15316						if ( !settings.ignoreSession ) {
15317							activeId = sessionStorage.getItem( pagePath + elmId + activePanel );
15318						}
15319					}
15320
15321					if ( activeId ) {
15322						$openPanel = $panels.filter( "#" + activeId );
15323					}
15324
15325				// If the panel was set by URL hash, then store in sessionStorage
15326				} else {
15327					if ( !settings.ignoreSession ) {
15328						try {
15329							sessionStorage.setItem( pagePath + elmId + activePanel, activeId );
15330						} catch ( error ) {
15331
15332							/* swallow error */
15333						}
15334					}
15335				}
15336			} catch ( error ) {
15337
15338				/* swallow error */
15339			}
15340
15341			// Determine the current view
15342			isSmallView = document.documentElement.className.indexOf( smallViewPattern ) !== -1;
15343
15344			// Only initialize the i18nText once
15345			if ( !i18nText ) {
15346				i18n = wb.i18n;
15347				i18nText = {
15348					prev: i18n( "prv" ),
15349					next: i18n( "nxt" ),
15350					play: i18n( "tab-play" ),
15351					rotStart: i18n( "tab-rot" ).on,
15352					rotStop: i18n( "tab-rot" ).off,
15353					space: i18n( "space" ),
15354					hyphen: i18n( "hyphen" ),
15355					pause: i18n( "pause" ),
15356					tabCount: i18n( "lb-curr" )
15357				};
15358			}
15359
15360			// Build the tablist and enhance the panels as needed for details/summary
15361			if ( !isCarousel ) {
15362				$elm.addClass( tabsAccordionClass );
15363				groupClass = elmId + "-grp";
15364				$tabPanels = $elm.children( ".tabpanels" );
15365				$panels = $tabPanels.children( "details" );
15366				len = $panels.length;
15367
15368				$tabPanels.detach();
15369
15370				// Ensure there is only one panel open
15371				// Order of priority is hash, open property, first details
15372				if ( !$openPanel || $openPanel.length === 0 ) {
15373					$openPanel = $panels.filter( "[open]" ).first();
15374					if ( $openPanel.length === 0 ) {
15375						$openPanel = $panels.eq( 0 );
15376					}
15377				}
15378				$panels.removeAttr( open );
15379				$openPanel.attr( open, open );
15380
15381				// Hide the tablist in small view and the summary elements in large view
15382				tablist = "<ul role='tablist' aria-live='off' class='generated'>";
15383
15384				for ( i = 0; i !== len; i += 1 ) {
15385					$panel = $panels.eq( i );
15386					$panel
15387						.addClass( groupClass )
15388						.html(
15389							$panel.html()
15390								.replace( /(<\/summary>)/i, "$1<div class='tgl-panel'>" ) +
15391							"</div>"
15392						);
15393
15394					newId = $panel.attr( "id" );
15395					if ( !newId ) {
15396						newId = wb.getId();
15397						$panel.attr( "id", newId );
15398					}
15399					isOpen = !!$panel.attr( open );
15400
15401					if ( isSmallView ) {
15402						if ( !Modernizr.details ) {
15403							$panel.toggleClass( "open", isOpen );
15404						}
15405					} else {
15406						$panel.attr( {
15407							role: "tabpanel",
15408							open: open
15409						} );
15410						$panel.addClass( ( Modernizr.details ? "" :  open + " " ) +
15411							"fade " + ( isOpen ? "in" : "noheight out wb-inv" ) );
15412					}
15413
15414					tablist += "<li" + ( isOpen ? " class='active'" : "" ) +
15415						"><a id='" + newId + "-lnk' href='#" + newId + "'>" +
15416						$panel.children( "summary" ).html() + "</a></li>";
15417				}
15418
15419				$tablist = $( tablist + "</ul>" );
15420				$tabPanels.find( "> details > summary" )
15421					.addClass( "wb-toggle tgl-tab" )
15422					.attr( "data-toggle", "{\"parent\": \"#" + elmId +
15423						"\", \"group\": \"." + groupClass + "\"}" );
15424
15425				$elm
15426					.prepend( $tablist )
15427					.append( $tabPanels )
15428					.trigger( "wb-init.wb-toggle" );
15429			} else if ( $openPanel && $openPanel.length !== 0 ) {
15430				$panels.filter( ".in" )
15431					.addClass( "out noheight" )
15432					.removeClass( "in" );
15433				$openPanel
15434					.addClass( "in" )
15435					.removeClass( "out noheight" );
15436				$tablist.find( ".active" )
15437					.removeClass( "active" );
15438				$tablist.find( "a" )
15439					.filter( "[href$='" + activeId + "']" )
15440					.parent()
15441					.addClass( "active" );
15442			}
15443
15444			drizzleAria( $panels, $tablist );
15445
15446			if ( isCarousel ) {
15447
15448				// Returns true if the tabs should be rotating automatically
15449				if ( createControls( $tablist, settings ) ) {
15450
15451					// Register this specific tabs instance for timerpoke.wb events
15452					wb.add( "#" + elmId + selector );
15453				}
15454			}
15455
15456			// If focus is being set by the URL hash, then ensure the tabs are
15457			// not above the top of the viewport (if the panel was the target),
15458			// or the anchor is not above the top of viewport (if the anchor was
15459			// the target)
15460			if ( hashFocus ) {
15461
15462				// Need a slight delay to allow for the reflow
15463				setTimeout( function() {
15464					if ( openByHash ) {
15465						positionY = $tablist.offset().top;
15466					} else if ( hashTargetLen !== 0 ) {
15467						positionY = $hashTarget.offset().top;
15468					} else {
15469						positionY = -1;
15470					}
15471
15472					if ( positionY !== -1 && positionY < document.body.scrollTop ) {
15473						document.body.scrollTop = positionY;
15474					}
15475				}, 1 );
15476			}
15477
15478			$elm.data( {
15479				"wb-tabs": {
15480					panels: $panels,
15481					tablist: $tablist,
15482					settings: settings,
15483					ctime: 0
15484				}
15485			} );
15486
15487			initialized = true;
15488			onResize( $elm );
15489
15490			// Update the URL hash if needed
15491			if ( settings.updateHash ) {
15492				updateHash( $openPanel[ 0 ] );
15493			}
15494
15495			// Identify that initialization has completed
15496			wb.ready( $elm, componentName );
15497		}
15498	},
15499
15500	/**
15501	 * @method onTimerPoke
15502	 * @param {jQuery DOM element} $elm The plugin element
15503	 */
15504	onTimerPoke = function( $elm ) {
15505		var data = $elm.data( componentName ),
15506			delayCurrent = parseFloat( data.ctime ) + 0.5;
15507
15508		// Check if we need to rotate panels
15509		if ( parseFloat( data.settings.interval ) <= delayCurrent ) {
15510			$elm.trigger( shiftEvent );
15511			delayCurrent = 0;
15512		}
15513
15514		data.ctime = delayCurrent;
15515		$elm.data( componentName, data );
15516	},
15517
15518	/**
15519	 * @method createControls
15520	 * @param {jQuery DOM element} $tablist The plugin element
15521	 * @param {object} settings Settings for the tabs instance
15522	 * @returns {boolean} Whether or not the tabs should be rotating initially
15523	 */
15524	createControls = function( $tablist, settings ) {
15525		var prevText = i18nText.prev,
15526			nextText = i18nText.next,
15527			spaceText = i18nText.space,
15528			excludeControls = settings.excludeControls,
15529			excludePlay = settings.excludePlay,
15530			isPlaying = !excludeControls && !excludePlay && settings.playing,
15531			state = isPlaying ? i18nText.pause : i18nText.play,
15532			hidden = isPlaying ? i18nText.rotStop : i18nText.rotStart,
15533			glyphiconStart = "<span class='glyphicon glyphicon-",
15534			wbInvStart = "<span class='wb-inv'>",
15535			tabsToggleStart = "<li class='control ",
15536			btnMiddle = "' href='javascript:;' role='button' title='",
15537			btnEnd = "</span></a></li> ",
15538			iconState = glyphiconStart + ( isPlaying ? "pause" : "play" ) + "'></span>",
15539			$tabs = $tablist.find( "[role=tab]" ),
15540			currentIndex = $tabs.index( $tabs.filter( "[aria-selected=true]" ) ) + 1,
15541			i18nTabCount = i18nText.tabCount,
15542			firstReplaceIndex = i18nTabCount.indexOf( "%" ),
15543			lastReplaceIndex = i18nTabCount.lastIndexOf( "%" ) + 1,
15544			prevControl = tabsToggleStart + "prv'><a class='prv" + btnMiddle +
15545				prevText + "'>" + glyphiconStart + "chevron-left'></span>" +
15546				wbInvStart + prevText + btnEnd,
15547			tabCount = tabsToggleStart + " tab-count' tabindex='0'><div>" +
15548				i18nTabCount.substring( 0, firstReplaceIndex ) +
15549				"<div class='curr-count'>" +
15550				i18nTabCount.substring( firstReplaceIndex, lastReplaceIndex )
15551					.replace( "%curr%", "<span class='curr-index'>" + currentIndex + "</span>" )
15552					.replace( "%total%", $tabs.length ) +
15553				"</div>" + i18nTabCount.substring( lastReplaceIndex ) +
15554				"</div></li>",
15555			nextControl = tabsToggleStart + "nxt'><a class='nxt" + btnMiddle +
15556				nextText + "'>" + glyphiconStart + "chevron-right'></span>" +
15557				wbInvStart + nextText + btnEnd,
15558			playControl =  tabsToggleStart + "plypause'><a class='plypause" +
15559				btnMiddle + state + "'>" + iconState + " <span>" + state +
15560				"</span>" + wbInvStart + spaceText + i18nText.hyphen + spaceText +
15561				hidden + btnEnd;
15562
15563		if ( !excludeControls ) {
15564			$tablist.prepend( prevControl + tabCount + nextControl );
15565		}
15566
15567		if ( !excludeControls && !excludePlay ) {
15568			$tablist.append( playControl );
15569		}
15570		$tablist.find( "a[role=button]" ).attr( "href", "javascript:;" );
15571
15572		return isPlaying;
15573	},
15574
15575	/**
15576	 * @method drizzleAria
15577	 * @param {jQuery DOM element} $panels Tabpanel groupings
15578	 * @param {jQuery DOM element} $tablist Pointers to the groupings
15579	 */
15580	drizzleAria = function( $panels, $tabList ) {
15581
15582		// Let's process the elements for aria
15583		var panels = $panels.get(),
15584			tabCounter = panels.length - 1,
15585			listItems = $tabList.children().get(),
15586			listCounter = listItems.length - 1,
15587			isDetails = $panels[ 0 ].nodeName.toLowerCase() === "details",
15588			isActive, item, link, panelId, activeFound;
15589
15590		for ( ; tabCounter !== -1; tabCounter -= 1 ) {
15591			item = panels[ tabCounter ];
15592			isActive = item.className.indexOf( "out" ) === -1;
15593
15594			if ( !isDetails || !isSmallView ) {
15595				item.setAttribute( "aria-hidden", isActive ? "false" : "true" );
15596				item.setAttribute( "aria-expanded", isActive ? "true" : "false" );
15597			}
15598			item.setAttribute( "aria-labelledby", item.id + "-lnk" );
15599		}
15600
15601		activeFound = false;
15602		for ( ; listCounter !== -1; listCounter -= 1 ) {
15603			item = listItems[ listCounter ];
15604			item.setAttribute( "role", "presentation" );
15605
15606			isActive = item.className.indexOf( "active" ) !== -1;
15607			if ( isActive ) {
15608				activeFound = true;
15609			} else if ( listCounter === 0 && !activeFound ) {
15610				isActive = true;
15611				item.className += " active";
15612			}
15613
15614			link = item.getElementsByTagName( "a" )[ 0 ];
15615			panelId = link.getAttribute( "href" ).substring( 1 );
15616
15617			link.tabIndex = isActive ? "0" : "-1";
15618			link.setAttribute( "role", "tab" );
15619			link.setAttribute( "aria-selected", isActive ? "true" : "false" );
15620			link.setAttribute( "aria-controls", panelId );
15621			link.id = panelId + "-lnk";
15622		}
15623		$tabList.attr( "aria-live", "off" );
15624	},
15625
15626	/**
15627	 * @method updateHash
15628	 * @param {DOM element} elm Tabpanel to be referenced in the URL hash
15629	 */
15630	updateHash = function( elm ) {
15631		var elmId = elm.id;
15632
15633		wb.ignoreHashChange = true;
15634		elm.id += "-off";
15635		window.location.hash = elmId;
15636		elm.id = elmId;
15637		wb.ignoreHashChange = false;
15638	},
15639
15640	updateNodes = function( $panels, $controls, $next, $control ) {
15641		var $tabs = $controls.find( "[role=tab]" ),
15642			newIndex = $tabs.index( $control ) + 1,
15643			$currPanel = $panels.filter( ".in" ),
15644			$container = $next.closest( selector ),
15645			tabSettings = $container.data( componentName ).settings,
15646			mPlayers = $currPanel.find( ".wb-mltmd-inited" ).get(),
15647			mPlayersLen = mPlayers.length,
15648			mPlayer, i, j, last;
15649
15650		// Handle the direction of the slide transitions
15651		if ( $currPanel[ 0 ].className.indexOf( "slide" ) !== -1 ) {
15652			i = $panels.index( $currPanel );
15653			j = $panels.index( $next );
15654			last = $panels.length - 1;
15655
15656			$panels.toggleClass(
15657				"reverse",
15658				( i > j && ( i !== last || j !== 0 ) ) || ( i === 0 && j === last )
15659			);
15660		}
15661
15662		$currPanel
15663			.removeClass( "in" )
15664			.addClass( "out noheight" )
15665			.attr( {
15666				"aria-hidden": "true",
15667				"aria-expanded": "false"
15668			} );
15669
15670		// Pause all multimedia players in the current panel
15671		for ( i = 0; i !== mPlayersLen; i += 1 ) {
15672			mPlayer = mPlayers[ i ];
15673			if ( mPlayer.player ) {
15674				mPlayer.player( "pause" );
15675			}
15676		}
15677
15678		$next
15679			.removeClass( "out noheight" )
15680			.addClass( "in" )
15681			.attr( {
15682				"aria-hidden": "false",
15683				"aria-expanded": "true"
15684			} );
15685
15686		$controls
15687			.find( ".active" )
15688			.removeClass( "active" )
15689			.children( "a" )
15690			.attr( {
15691				"aria-selected": "false",
15692				tabindex: "-1"
15693			} );
15694
15695		// Update the Item x of n
15696		$controls
15697			.find( ".curr-index" )
15698			.html( newIndex );
15699
15700		$control
15701			.attr( {
15702				"aria-selected": "true",
15703				tabindex: "0"
15704			} )
15705			.parent()
15706			.addClass( "active" );
15707
15708		// Update sessionStorage with the current active panel
15709		if ( !tabSettings.ignoreSession ) {
15710			try {
15711				sessionStorage.setItem(
15712					pagePath + $container.attr( "id" ) + activePanel,
15713					$next.attr( "id" )
15714				);
15715			} catch ( error ) {
15716
15717				/* swallow error */
15718			}
15719		}
15720
15721		// Update the URL hash if needed
15722		if ( tabSettings.updateHash ) {
15723			updateHash( $next[ 0 ] );
15724		}
15725
15726		// Identify that the tabbed interface/carousel was updated
15727		$container.trigger( updatedEvent, [ $next ] );
15728	},
15729
15730	/**
15731	 * @method onPick
15732	 * @param {jQuery DOM element} $sldr The plugin element
15733	 * @param {jQuery DOM element} $elm The selected link from the tablist
15734	 */
15735	onPick = function( $sldr, $elm ) {
15736		var data = $sldr.data( componentName ),
15737			$panels = data.panels,
15738			$controls = data.tablist,
15739			$next = $panels.filter( "#" + $elm.attr( "aria-controls" ) );
15740
15741		updateNodes( $panels, $controls, $next, $elm );
15742	},
15743
15744	/**
15745	 * @method onShift
15746	 * @param (jQuery event} event Current event
15747	 * @param {jQuery DOM element} $elm The plugin element
15748	 */
15749	onShift = function( event, $elm ) {
15750		var data = $elm.data( componentName ),
15751			$panels = data.panels,
15752			len = $panels.length,
15753			current = $elm.find( "> .tabpanels > .in" ).prevAll( "[role=tabpanel]" ).length,
15754			autoCycle = !event.shiftto,
15755			next = current > len ? 0 : current + ( autoCycle ? 1 : event.shiftto );
15756
15757		onSelect( $panels[ ( next > len - 1 ) ? 0 : ( next < 0 ) ? len - 1 : next ].id, autoCycle );
15758	},
15759
15760	/**
15761	 * @method onSelect
15762	 * @param (string) id Id attribute of the panel
15763	 * @param (boolean) autoCycle Whether change is caused by an auto cycle
15764	 */
15765	onSelect = function( id, autoCycle ) {
15766		var panelSelector = "#" + id,
15767			$panel = $( panelSelector ),
15768			$panelSelectorLink;
15769
15770		if ( isSmallView && $panel[ 0 ].nodeName.toLowerCase() === "details" ) {
15771			$panel.children( "summary" ).trigger( $panel.attr( "open" ) ? setFocusEvent : "click" );
15772		} else {
15773			$panelSelectorLink = $( panelSelector + "-lnk" );
15774			$panelSelectorLink.trigger( {
15775				type: "click",
15776				which: autoCycle ? undefined : 1
15777			} );
15778
15779			// Don't change the focus if change is cause by an auto cycle
15780			if ( !autoCycle ) {
15781				$panelSelectorLink.trigger( setFocusEvent );
15782			}
15783		}
15784	},
15785
15786	/**
15787	 * @method onCycle
15788	 * @param {jQuery DOM element} $elm The plugin element
15789	 * @param {integer} shifto The item to shift to
15790	 */
15791	onCycle = function( $elm, shifto ) {
15792		$elm.trigger( {
15793			type: shiftEvent,
15794			shiftto: shifto
15795		} );
15796	},
15797
15798	/**
15799	 * @method onResize
15800	 * @param {jQuery Object} $currentElm Element being initialized (only during initialization process).
15801	 */
15802	onResize = function( $currentElm ) {
15803		var $elms, $elm, $tabPanels, $details, $detailsElm, $tablist,
15804			$openDetails, openDetailsId, activeId, $summary, $panelElm,
15805			i, len, j, len2, viewChange, isInit, isActive;
15806
15807		if ( initialized ) {
15808			isSmallView = document.documentElement.className.indexOf( smallViewPattern ) !== -1;
15809			viewChange = isSmallView !== oldIsSmallView;
15810			isInit = $currentElm.length ? true : false;
15811
15812			if ( viewChange || isInit ) {
15813				$elms = isInit ? $currentElm : $( selector );
15814				len = $elms.length;
15815
15816				for ( i = 0; i !== len; i += 1 ) {
15817					$elm = $elms.eq( i );
15818					$tabPanels = $elm.children( ".tabpanels" );
15819					$details = $tabPanels.children( "details" );
15820					len2 = $details.length;
15821
15822					if ( $details.length !== 0 ) {
15823						$tabPanels.detach();
15824						$summary = $details.children( "summary" );
15825						$tablist = $elm.children( "ul" );
15826
15827						if ( isSmallView ) {
15828
15829							// Switch to small view
15830							activeId = $tablist.find( ".active a" ).attr( "href" ).substring( 1 );
15831							for ( j = 0; j !== len2; j += 1 ) {
15832								$detailsElm = $details.eq( j );
15833								$panelElm = $detailsElm.children( ".tgl-panel" );
15834								isActive = $detailsElm.attr( "id" ) === activeId;
15835
15836								$detailsElm
15837									.removeAttr( "role aria-expanded aria-hidden" )
15838									.removeClass( "fade out noheight in" )
15839									.toggleClass( "open", isActive );
15840
15841								$panelElm
15842									.attr( "role", "tabpanel" )
15843									.removeAttr( "aria-expanded" )
15844									.removeAttr( "aria-hidden" );
15845
15846								if ( isActive ) {
15847									$detailsElm.attr( "open", "open" );
15848								} else {
15849									$detailsElm.removeAttr( "open" );
15850								}
15851
15852								if ( !isInit ) {
15853									$detailsElm
15854										.children( "summary" )
15855										.attr( {
15856											"aria-expanded": isActive,
15857											"aria-selected": isActive
15858										} );
15859								}
15860							}
15861						} else if ( oldIsSmallView ) {
15862
15863							// Switch to large view
15864							$openDetails = $details.filter( "[open]" );
15865							openDetailsId = $openDetails.attr( "id" );
15866
15867							$openDetails = ( $openDetails.length === 0 ? $details : $openDetails ).eq( 0 );
15868
15869							$details
15870								.attr( {
15871									role: "tabpanel",
15872									open: "open"
15873								} )
15874								.not( $openDetails )
15875								.addClass( "fade out noheight wb-inv" )
15876								.attr( {
15877									"aria-hidden": "true",
15878									"aria-expanded": "false"
15879								} );
15880
15881							$details.children( ".tgl-panel" ).removeAttr( "role" );
15882
15883							$openDetails
15884								.addClass( "fade in" )
15885								.attr( {
15886									"aria-hidden": "false",
15887									"aria-expanded": "true"
15888								} );
15889						}
15890
15891						$summary.attr( "aria-hidden", !isSmallView );
15892						$tablist.attr( "aria-hidden", isSmallView );
15893
15894						$elm.append( $tabPanels );
15895
15896						// Update the tablist role
15897						if ( isSmallView && !isInit ) {
15898							$elm.attr( "role", "tablist" );
15899						} else if ( oldIsSmallView ) {
15900							$elm
15901								.removeAttr( "role" )
15902								.find( nestedTglPanelSelector ).removeAttr( "role" );
15903
15904							$elm.find( "> ul [href$='" + openDetailsId + "']" ).trigger( "click" );
15905						}
15906					}
15907				}
15908
15909				// Need timeout to account for Toggle changes
15910				if ( isInit && !isSmallView && $elms.hasClass( tabsAccordionClass ) ) {
15911					setTimeout( function() {
15912						$elms
15913							.removeAttr( "role" )
15914							.find( nestedTglPanelSelector ).removeAttr( "role" );
15915					}, 1 );
15916				}
15917			}
15918
15919			oldIsSmallView = isSmallView;
15920		}
15921
15922		if ( viewChange || isInit ) {
15923
15924			// Remove wb-inv from regular tabs that were used to prevent FOUC (after 300ms delay)
15925			setTimeout( function() {
15926				$( selector + " .tabpanels > details.wb-inv" ).removeClass( "wb-inv" );
15927			}, 300 );
15928		}
15929	};
15930
15931// Bind the init event of the plugin
15932$document.on( "timerpoke.wb " + initEvent + " " + shiftEvent + " " + selectEvent, selector, function( event ) {
15933	var eventTarget = event.target,
15934		eventCurrentTarget = event.currentTarget,
15935		$elm;
15936
15937	// Filter out any events triggered by descendants
15938	if ( eventCurrentTarget === eventTarget ) {
15939		switch ( event.type ) {
15940		case "timerpoke":
15941			$elm = $( eventTarget );
15942			if ( !$elm.hasClass( componentName + "-inited" ) ) {
15943				init( event );
15944			} else if ( $elm.hasClass( "playing" ) ) {
15945				onTimerPoke( $elm );
15946			}
15947			break;
15948
15949		/*
15950		 * Init
15951		 */
15952		case "wb-init":
15953			init( event );
15954			break;
15955
15956		/*
15957		 * Change tab panels by a delta
15958		 */
15959		case "wb-shift":
15960			onShift( event, $( eventTarget ) );
15961			break;
15962
15963		/*
15964		 * Select a specific tab panel
15965		 */
15966		case "wb-select":
15967			onSelect( event.id );
15968			break;
15969		}
15970	}
15971
15972	/*
15973	 * Since we are working with events we want to ensure that we are being passive about our control,
15974	 * so returning true allows for events to always continue
15975	 */
15976	return true;
15977} );
15978
15979/*
15980 * Tabs, next, previous and play/pause
15981 */
15982$document.on( activateEvent, controls, function( event ) {
15983	var which = event.which,
15984		elm = event.currentTarget,
15985		className = elm.className,
15986		spaceText = i18nText.space,
15987		$elm, $sldr, sldrId, plypause, buttonText, data, isPlaying, isPlayPause;
15988
15989	// No control, alt or meta keys and only left mouse button, enter key,
15990	// space bar, escape key and arrow keys
15991	if ( !( event.ctrlKey || event.altKey || event.metaKey ) &&
15992			( !which || which === 1 || which === 13 || which === 27 ||
15993			which === 32 || ( which > 36 && which < 41 ) ) ) {
15994
15995		// Stop propagation of the activate event
15996		event.preventDefault();
15997		if ( event.stopPropagation ) {
15998			event.stopImmediatePropagation();
15999		} else {
16000			event.cancelBubble = true;
16001		}
16002
16003		$elm = $( elm );
16004		$sldr = $elm.closest( selector );
16005		sldrId = $sldr[ 0 ].id;
16006		isPlaying = $sldr.hasClass( "playing" );
16007		isPlayPause = className.indexOf( "plypause" ) !== -1;
16008
16009		// Reset ctime to 0
16010		data = $sldr.data( componentName );
16011		data.ctime = 0;
16012		$sldr.data( componentName, data );
16013
16014		// Stop the slider from playing unless it is already stopped
16015		// and the play button is activated
16016		if ( ( isPlaying && which ) || ( isPlayPause && !( which > 36 && which < 41 ) ) ) {
16017			if ( isPlaying ) {
16018				wb.remove( "#" + sldrId + selector );
16019			} else {
16020				wb.add( "#" + sldrId + selector );
16021			}
16022
16023			$sldr.toggleClass( "playing" );
16024			isPlaying = !isPlaying;
16025			buttonText = isPlaying ? i18nText.pause : i18nText.play;
16026
16027			plypause = $sldr.find( "a.plypause" )[ 0 ];
16028			plypause.setAttribute( "title", buttonText );
16029			plypause.innerHTML = "<span class='glyphicon glyphicon-" +
16030				( isPlaying ? "pause" : "play" ) + "'></span> " +
16031				"<span>" + buttonText + "</span><span class='wb-inv'>" +
16032				spaceText + i18nText.hyphen + spaceText +
16033				( isPlaying ? i18nText.rotStop : i18nText.rotStart ) + "</span>";
16034		}
16035
16036		// Arrow keys
16037		if ( which > 36 ) {
16038			onCycle( $sldr, which < 39 ? -1 : 1 );
16039			$sldr.find( "> [role=tablist] .active a" ).trigger( setFocusEvent );
16040
16041		// Not the escape key
16042		} else if ( which !== 27 ) {
16043
16044			// If the target is a tab
16045			if ( elm.getAttribute( "role" ) === "tab" ) {
16046
16047				// Only change the tabpanel if the tab is not currently selected
16048				if ( elm.getAttribute( "aria-selected" ) !== "true" ) {
16049					onPick( $sldr, $elm );
16050				}
16051
16052				// Put focus on the tab panel if the enter key or space bar are used
16053				if ( which === 13 || which === 32 ) {
16054					$sldr.find( elm.getAttribute( "href" ) )
16055						.trigger( setFocusEvent );
16056				}
16057
16058			// If the target is next, previous or tab count
16059			} else if ( !isPlayPause ) {
16060				onCycle( $sldr, className.indexOf( "prv" ) !== -1 ? -1 : 1 );
16061			}
16062		}
16063	}
16064
16065	/*
16066	 * Since we are working with events we want to ensure that we are being passive about our control,
16067	 * so returning true allows for events to always continue
16068	 */
16069	return true;
16070} );
16071
16072$document.on( activateEvent, selector + " [role=tabpanel]", function( event ) {
16073	var currentTarget = event.currentTarget,
16074		which = event.which,
16075		$container;
16076
16077	// Stop propagation of the click/keydown event
16078	if ( event.stopPropagation ) {
16079		event.stopImmediatePropagation();
16080	} else {
16081		event.cancelBubble = true;
16082	}
16083
16084	// Ctrl + Up arrow
16085	if ( event.ctrlKey && event.which === 38 ) {
16086
16087		// Move focus to the tab or summary element
16088		if ( isSmallView ) {
16089			$( currentTarget ).prev().trigger( setFocusEvent );
16090		} else {
16091			$( currentTarget )
16092				.closest( selector )
16093				.find( "[href$='#" + currentTarget.id + "']" )
16094				.trigger( setFocusEvent );
16095		}
16096
16097	// Left mouse button click or escape key
16098	} else if ( !which || which === 1 || which === 27 ) {
16099		$container = $( event.currentTarget ).closest( selector );
16100
16101		// Stop the carousel
16102		if ( $container.hasClass( "playing" ) ) {
16103			$container.find( ".plypause" ).trigger( "click" );
16104		}
16105	}
16106} );
16107
16108// Handling for links to tabs from within a panel
16109$document.on( "click", selector + " [role=tabpanel] a", function( event ) {
16110	var currentTarget = event.currentTarget,
16111		href = currentTarget.getAttribute( "href" ),
16112		which = event.which,
16113		$tabpanels, $panel, $summary;
16114
16115	// Ignore middle and right mouse buttons
16116	if ( ( !which || which === 1 ) && href.charAt( 0 ) === "#" ) {
16117		$tabpanels = $( currentTarget ).closest( ".tabpanels" );
16118		$panel = $tabpanels.children( "#" + wb.jqEscape( href.substring( 1 ) ) );
16119		if ( $panel.length !== 0 ) {
16120			event.preventDefault();
16121			$summary = $panel.children( "summary" );
16122			if ( $summary.length !== 0 && $summary.attr( "aria-hidden" ) !== "true" ) {
16123				$summary.trigger( "click" );
16124			} else {
16125				$tabpanels.parent().find( href + "-lnk" ).trigger( "click" );
16126			}
16127		}
16128	}
16129} );
16130
16131// These events only fire at the document level
16132$document.on( wb.resizeEvents, onResize );
16133
16134$document.on( activateEvent, selector + " > .tabpanels > details > summary", function( event ) {
16135	var which = event.which,
16136		details = event.currentTarget.parentNode,
16137		$details, $container, tabSettings;
16138
16139	if ( !( event.ctrlKey || event.altKey || event.metaKey ) &&
16140		( !which || which === 1 || which === 13 || which === 32 ) ) {
16141
16142		$container = $( details.parentNode.parentNode );
16143		$details = $( details );
16144		tabSettings = $container.data( componentName ).settings;
16145
16146		// Update sessionStorage with the current active panel
16147		if ( !tabSettings.ignoreSession ) {
16148			try {
16149				sessionStorage.setItem(
16150					pagePath + $container.attr( "id" ) + activePanel,
16151					details.id
16152				);
16153			} catch ( error ) {
16154
16155				/* swallow error */
16156			}
16157		}
16158
16159		// Update the URL hash if needed
16160		if ( tabSettings.updateHash ) {
16161			updateHash( details );
16162		}
16163
16164		// Identify that the tabbed interface accordion was updated
16165		// if the panel was not already open
16166		if ( !$details.attr( "open" ) ) {
16167			$container.trigger( updatedEvent, [ $details ] );
16168		}
16169	}
16170} );
16171
16172// Change the panel based upon an external link click
16173$document.on( "click", ".wb-tabs-ext", function( event ) {
16174	var which = event.which;
16175
16176	// Ignore middle and right mouse buttons
16177	if ( !which || which === 1 ) {
16178		event.preventDefault();
16179		onSelect( event.currentTarget.getAttribute( "href" ).substring( 1 ) );
16180	}
16181} );
16182
16183// Add the timer poke to initialize the plugin
16184wb.add( selector );
16185
16186} )( jQuery, window, wb );
16187
16188/**
16189 * @title WET-BOEW Tag filter
16190 * @overview Filter based content tagging
16191 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
16192 * @author @duboisp
16193 */
16194( function( $, window, document, wb ) {
16195"use strict";
16196
16197let wait;
16198
16199const componentName = "wb-tagfilter",
16200	selector = ".provisional." + componentName,
16201	selectorCtrl = "." + componentName + "-ctrl",
16202	initEvent = "wb-init" + selector,
16203	$document = wb.doc,
16204	filterOutClass = "wb-fltr-out",
16205	tgFilterOutClass = "wb-tgfltr-out",
16206	itemsWrapperClass = "wb-tagfilter-items",
16207	noResultWrapperClass = "wb-tagfilter-noresult",
16208
16209	init = function( event ) {
16210		const elm = wb.init( event, componentName, selector );
16211
16212		if ( elm ) {
16213			const filterControls = elm.querySelectorAll( selectorCtrl ),
16214				taggedItems = elm.querySelectorAll( "[data-wb-tags]" ),
16215				taggedItemsWrapper = elm.querySelector( "." + itemsWrapperClass ),
16216				noResultWrapper = elm.querySelector( "." + noResultWrapperClass );
16217
16218			elm.items = [];
16219			elm.filters = {};
16220			elm.activeFilters = [];
16221
16222			if ( taggedItemsWrapper ) {
16223				taggedItemsWrapper.id = taggedItemsWrapper.id || wb.getId(); // Ensure the element has an ID
16224				taggedItemsWrapper.setAttribute( "aria-live", "polite" );
16225			}
16226
16227			// Handle filters
16228			if ( filterControls.length ) {
16229				elm.filters = buildFiltersObj( filterControls );
16230
16231				filterControls.forEach( function( item ) {
16232					item.setAttribute( "aria-controls", taggedItemsWrapper.id );
16233				} );
16234			}
16235
16236			// Handle tagged items
16237			if ( taggedItems.length ) {
16238				elm.items = buildTaggedItemsArr( taggedItems );
16239			}
16240
16241			// Add accessibility to no result element
16242			if ( noResultWrapper ) {
16243				noResultWrapper.setAttribute( "role", "status" );
16244			}
16245
16246			// Update list of visible items (in case of predefined filters)
16247			update( elm );
16248
16249			wb.ready( $( elm ), componentName );
16250		}
16251	},
16252
16253	// Add every tagged item to an array of objects with their DOM ID, list of associated tags, and default isMatched attribute
16254	buildTaggedItemsArr = function( taggedItems ) {
16255		let taggedItemsArr = [];
16256
16257		taggedItems.forEach( function( taggedItem ) {
16258			let tagsList = taggedItem.dataset.wbTags.split( " " );
16259
16260			if ( !taggedItem.id ) {
16261				taggedItem.setAttribute( "id", wb.getId() );
16262			}
16263
16264			taggedItemsArr.push( {
16265				id: taggedItem.id,
16266				tags: tagsList,
16267				isMatched: true,
16268				itemText: taggedItem.innerText.toLowerCase()
16269			} );
16270		} );
16271
16272		return taggedItemsArr;
16273	},
16274
16275	// Build list of available filters using all filters grouped by filter name
16276	buildFiltersObj = function( filterControls ) {
16277		let filtersObj = {};
16278
16279		filterControls.forEach( function( control ) {
16280			if ( !control.name ) {
16281				console.error( componentName + ": Filter controls require an attribute 'name'." );
16282			}
16283
16284			switch ( control.type ) {
16285			case "checkbox":
16286			case "radio":
16287				if ( !( control.name in filtersObj ) ) {
16288					filtersObj[ control.name ] = [ ];
16289				}
16290
16291				filtersObj[ control.name ].push( {
16292					isChecked: control.checked,
16293					type: control.type,
16294					value: control.value
16295				} );
16296
16297				break;
16298			case "select-one":
16299				filtersObj[ control.name ] = [ {
16300					type: control.type,
16301					value: control.value
16302				} ];
16303				break;
16304			}
16305		} );
16306
16307		return filtersObj;
16308	},
16309
16310	// Update array of active filters according to UI selected controls
16311	refineFilters = function( instance ) {
16312		instance.activeFilters = [ ]; // Clear active filters
16313
16314		for ( let filterGroupName in instance.filters ) {
16315			let filterGroup = instance.filters[ filterGroupName ],
16316				filterGroupChkCnt = filterGroup.filter( function( o ) {
16317					return o.isChecked === true;
16318				} ).length,
16319				filterGroupActiveFilters = [ ];
16320
16321			switch ( filterGroup[ 0 ].type ) {
16322			case "checkbox":
16323				if ( filterGroupChkCnt > 0 ) {
16324					filterGroup.forEach( function( filterItem ) {
16325						if ( filterItem.isChecked ) {
16326							filterGroupActiveFilters.push( filterItem.value );
16327						}
16328					} );
16329				}
16330				break;
16331
16332			case "radio":
16333				if ( filterGroupChkCnt > 0 ) {
16334					for ( let filterItem of filterGroup ) {
16335						if ( filterItem.isChecked === true ) {
16336							if ( filterItem.value !== "" ) {
16337								filterGroupActiveFilters.push( filterItem.value );
16338							}
16339							break;
16340						}
16341					}
16342				} else {
16343					console.warn( componentName + ": Radio button groups must have a default selected value. If you want to display all items, add an option called \"All\" with an empty value." );
16344				}
16345				break;
16346
16347			case "select-one":
16348				if ( filterGroup[ 0 ].value !== "" ) {
16349					filterGroupActiveFilters.push( filterGroup[ 0 ].value );
16350				}
16351				break;
16352			}
16353
16354			instance.activeFilters.push( filterGroupActiveFilters );
16355		}
16356	},
16357
16358	// Match tagged items to active filters and only return items that have an active filter in every filter group
16359	matchItemsToFilters = function( instance ) {
16360		let filtersGroups = instance.activeFilters.length;
16361
16362		instance.items.forEach( function( item ) {
16363			let matchCount = 0;
16364
16365			instance.activeFilters.forEach( function( filterGroup ) {
16366				if ( filterGroup.length === 0 ) {
16367					matchCount++;
16368				} else {
16369					let itemIncludesFilter = filterGroup.filter( function( f ) {
16370						return item.tags.includes( f );
16371					} ).length;
16372
16373					if ( itemIncludesFilter ) {
16374						matchCount++;
16375					}
16376				}
16377			} );
16378
16379			matchCount === filtersGroups ? item.isMatched = true : item.isMatched = false;
16380		} );
16381	},
16382
16383	// Update list of visible items according to their "isMatched" property
16384	updateDOMItems = function( instance ) {
16385		const updatedItemsList = instance.items.forEach( function( item ) {
16386			let domItem = instance.querySelector( "#" + item.id ),
16387				matched = item.isMatched;
16388
16389			if ( matched ) {
16390				if ( domItem.classList.contains( tgFilterOutClass ) ) {
16391					domItem.classList.remove( tgFilterOutClass );
16392				}
16393			} else {
16394				if ( !domItem.classList.contains( tgFilterOutClass ) ) {
16395					domItem.classList.add( tgFilterOutClass );
16396				}
16397			}
16398		} );
16399
16400		return updatedItemsList;
16401	},
16402
16403	// Utility method to update stored active filters, update stored items and update visibility of tagged items
16404	update = function( instance ) {
16405		refineFilters( instance );
16406		matchItemsToFilters( instance );
16407		updateDOMItems( instance );
16408
16409		$( instance ).trigger( "wb-contentupdated", [ { source: componentName } ] );
16410	};
16411
16412// When a filter is updated
16413$document.on( "change", selectorCtrl, function( event )  {
16414	let control = event.currentTarget,
16415		filterType = control.type,
16416		filterName = control.name,
16417		filterValue = control.value,
16418		elm = control.closest( selector ),
16419		filterGroup = elm.filters[ filterName ];
16420
16421	switch ( filterType ) {
16422	case "checkbox":
16423
16424		// Update virtual filter to the new state
16425		filterGroup.find( function( filter ) {
16426			return filter.value === filterValue;
16427		} ).isChecked = !!control.checked;
16428		break;
16429
16430	case "radio":
16431
16432		// Set all virtual radio items to unchecked
16433		filterGroup.forEach( function( filterItem ) {
16434			filterItem.isChecked = false;
16435		} );
16436
16437		// Set selected radio button's associated virtual filter to checked
16438		filterGroup.find( function( filter ) {
16439			return filter.value === filterValue;
16440		} ).isChecked = true;
16441		break;
16442
16443	case "select-one":
16444
16445		// Update virtual filter to the new value
16446		filterGroup[ 0 ].value = filterValue;
16447		break;
16448	}
16449
16450	// Update list of visible items
16451	update( elm );
16452} );
16453
16454$document.on( "wb-contentupdated", selector, function( event, data )  {
16455	let that = this,
16456		supportsHas = window.getComputedStyle( document.documentElement ).getPropertyValue( "--supports-has" ); // Get "--supports-has" CSS property
16457
16458	// Reinitialize tagfilter if content on the page has been updated by another plugin
16459	if ( data.source !== componentName ) {
16460		if ( wait ) {
16461			clearTimeout( wait );
16462		}
16463
16464		wait = setTimeout( function() {
16465			that.classList.remove( "wb-init", componentName + "-inited" );
16466			$( that ).trigger( "wb-init." + componentName );
16467		}, 100 );
16468	}
16469
16470	// Show no result message if on Firefox -- Remove once Firefox supports ":has()"
16471	if ( supportsHas === "false" ) {
16472		let noResultItem = this.querySelector( "." + noResultWrapperClass );
16473
16474		if ( noResultItem && this.items.length > 0 ) {
16475			let visibleItems = this.querySelectorAll( "." + itemsWrapperClass + " " + "[data-wb-tags]:not(." + tgFilterOutClass + ", ." + filterOutClass + ")" );
16476
16477			if ( visibleItems.length < 1 ) {
16478				noResultItem.style.display = "block";
16479			} else {
16480				noResultItem.style.display = "none";
16481			}
16482		}
16483	}
16484} );
16485
16486$document.on( "timerpoke.wb " + initEvent, selector, init );
16487
16488wb.add( selector );
16489
16490} )( jQuery, window, document, wb );
16491
16492/**
16493 * @title WET-BOEW Text highlighting
16494 * @overview Automatically highlights certain words on a Web page. The highlighted words can be selected via the query string.
16495 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
16496 * @author @pjackson28
16497 */
16498( function( $, window, document, wb ) {
16499"use strict";
16500
16501/*
16502 * Variable and function definitions.
16503 * These are global to the plugin - meaning that they will be initialized once per page,
16504 * not once per instance of plugin on the page. So, this is a good place to define
16505 * variables that are common to all instances of the plugin on a page.
16506 */
16507var componentName = "wb-txthl",
16508	selector = "." + componentName,
16509	initEvent = "wb-init" + selector,
16510	$document = wb.doc,
16511
16512	/**
16513	 * @method init
16514	 * @param {jQuery Event} event Event that triggered the function call
16515	 */
16516	init = function( event ) {
16517
16518		// Start initialization
16519		// returns DOM object = proceed with init
16520		// returns undefined = do not proceed with init (e.g., already initialized)
16521		var elm = wb.init( event, componentName, selector ),
16522			params = wb.pageUrlParts.params,
16523			searchCriteria, newText;
16524
16525		if ( elm ) {
16526			if ( event.txthl ) {
16527				searchCriteria = $.isArray( event.txthl ) ? event.txthl.join( "|" ) : event.txthl;
16528			} else if ( params && params.txthl ) {
16529				searchCriteria = decodeURIComponent(
16530					wb.pageUrlParts.params.txthl
16531						.replace( /^\s+|\s+$|\|+|"|\(|\)/g, "" ).replace( /\++/g, "|" )
16532				);
16533			}
16534
16535			if ( searchCriteria ) {
16536
16537				// Strip html
16538				var map = { "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#039;" };
16539				searchCriteria = searchCriteria.replace( /[&<>"']/g, function( m ) {
16540					return map[ m ];
16541				} );
16542
16543				// Make sure that we're not checking for text within a tag; only the text outside of tags.
16544				searchCriteria = "(?=([^>]*<))([\\s'])?(" + searchCriteria + ")(?!>)";
16545
16546				newText = elm.innerHTML.replace( new RegExp( searchCriteria, "gi" ), function( match, group1, group2, group3 ) {
16547					return ( !group2 ? "" : group2 ) + "<mark>" + group3 + "</mark>";
16548				} );
16549				elm.innerHTML = newText;
16550			}
16551
16552			// Identify that initialization has completed
16553			wb.ready( $( elm ), componentName );
16554		}
16555	};
16556
16557// Bind the init event of the plugin
16558$document.on( "timerpoke.wb " + initEvent, selector, init );
16559
16560// Add the timer poke to initialize the plugin
16561wb.add( selector );
16562
16563} )( jQuery, window, document, wb );
16564
16565/**
16566 * @title WET-BOEW Toggle
16567 * @overview Plugin that allows a link to toggle elements between on and off states.
16568 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
16569 * @author @patheard
16570 */
16571( function( $, window, wb ) {
16572"use strict";
16573
16574/*
16575 * Variable and function definitions.
16576 * These are global to the plugin - meaning that they will be initialized once per page,
16577 * not once per instance of plugin on the page. So, this is a good place to define
16578 * variables that are common to all instances of the plugin on a page.
16579 */
16580var componentName = "wb-toggle",
16581	selector = "." + componentName,
16582	selectorPanel = ".tgl-panel",
16583	selectorTab = ".tgl-tab",
16584	initEvent = "wb-init" + selector,
16585	toggleEvent = "toggle" + selector,
16586	toggledEvent = "toggled" + selector,
16587	setFocusEvent = "setfocus.wb",
16588	states = {},
16589	$document = wb.doc,
16590	$window = wb.win,
16591
16592	defaults = {
16593		stateOn: "on",
16594		stateOff: "off"
16595	},
16596
16597	/**
16598	 * @method init
16599	 * @param {jQuery Event} event Event that triggered the function call
16600	 */
16601	init = function( event ) {
16602
16603		// Start initialization
16604		// returns DOM object = proceed with init
16605		// returns undefined = do not proceed with init (e.g., already initialized)
16606		var link = wb.init( event, componentName, selector, true ),
16607			$link, data, persistState;
16608
16609		if ( link ) {
16610
16611			// Merge the elements settings with the defaults
16612			$link = $( link );
16613			data = $.extend( {}, defaults, $link.data( "toggle" ) );
16614			$link.data( "toggle", data );
16615
16616			// Add aria attributes of the toggle element
16617			initAria( link, data );
16618
16619			// Persist toggle state across page loads
16620			if ( data.persist ) {
16621				persistState = initPersist( $link, data );
16622			}
16623
16624			// Toggle behaviour when the page is printed
16625			if ( data.print ) {
16626				initPrint( $link, data );
16627			}
16628
16629			// Set the initial state if the state has been specified and
16630			// the persistent state has not been set
16631			if ( !persistState && data.state ) {
16632				setState( $link, data, data.state );
16633			}
16634
16635			// Identify that initialization has completed
16636			wb.ready( $link, componentName );
16637		}
16638	},
16639
16640	/**
16641	 * Initialize the aria attributes for a given toggle element
16642	 * @param {DOM element} link The toggle element to initialize
16643	 * @param {Object} data Simple key/value data object passed when the event was triggered
16644	 */
16645	initAria = function( link, data ) {
16646		var i, len, elm, elms, parent, tabs, tab, panel, isOpen, wrapper,
16647			ariaControls = "",
16648			hasOpen = false;
16649
16650		// Group toggle elements with a parent are assumed to be a tablist
16651		if ( data.group != null && data.parent != null ) { //eslint-disable-line no-eq-null
16652			parent = document.querySelector( data.parent );
16653
16654			// Check that the tablist widget hasn't already been initialized
16655			if ( parent.getAttribute( "role" ) !== "tablist" ) {
16656
16657				// Only apply the tablist role if the parent is not the tabbed interface container
16658				// or the page is currently in "smallview", "xsmallview" or "xxsmallview"
16659				if ( parent.className.indexOf( "wb-tabs" ) === -1 ||
16660					document.documentElement.className.indexOf( "smallview" ) !== -1 ) {
16661					parent.setAttribute( "role", "tablist" );
16662				}
16663
16664				elms = parent.querySelectorAll( data.group );
16665				tabs = parent.querySelectorAll( data.group + " " + selectorTab );
16666
16667				// Set the tab and panel aria attributes
16668				for ( i = 0, len = elms.length; i !== len; i += 1 ) {
16669					elm = elms[ i ];
16670					tab = tabs[ i ];
16671					panel = elm.querySelector( selectorPanel );
16672
16673					// Check if the element is toggled on based on the
16674					// open attribute or "on" CSS class
16675					isOpen = elm.nodeName.toLowerCase() === "details" ?
16676						!!elm.hasAttribute( "open" ) :
16677						( " " + tab.className + " " ).indexOf( " " + data.stateOn + " " );
16678					if ( isOpen ) {
16679						hasOpen = true;
16680					}
16681
16682					if ( !tab.getAttribute( "id" ) ) {
16683						tab.setAttribute( "id", wb.getId() );
16684					}
16685
16686					//Details and summary don't support aria roles and some aria attribute that is why they are wrapped in a div
16687					if ( elm.nodeName.toLowerCase() === "details" && elm.parentNode.className.toLowerCase().indexOf( "accordion" ) > -1 ) {
16688						wrapper = document.createElement( "div" );
16689						wrapper.classList.add( "tgl-tab" );
16690						wrapper.setAttribute( "role", "tab" );
16691						wrapper.setAttribute( "aria-selected", isOpen );
16692						wrapper.setAttribute( "tabindex", isOpen ? "0" : "-1" );
16693						wrapper.setAttribute( "aria-posinset", i + 1 );
16694						wrapper.setAttribute( "aria-setsize", len );
16695						parent.replaceChild( wrapper, elm );
16696						wrapper.appendChild( elm );
16697					} else {
16698						tab.setAttribute( "role", "tab" );
16699						tab.setAttribute( "aria-selected", isOpen );
16700						tab.setAttribute( "tabindex", isOpen ? "0" : "-1" );
16701						tab.setAttribute( "aria-posinset", i + 1 );
16702						tab.setAttribute( "aria-setsize", len );
16703					}
16704					panel.setAttribute( "role", "tabpanel" );
16705					panel.setAttribute( "aria-labelledby", tab.getAttribute( "id" ) );
16706					panel.setAttribute( "aria-expanded", isOpen );
16707					panel.setAttribute( "aria-hidden", !isOpen );
16708				}
16709
16710				// No open panels so put the first summary in the tab order
16711				if ( !hasOpen ) {
16712					tabs[ 0 ].setAttribute( "tabindex", "0" );
16713				}
16714			}
16715
16716		// Set the elements this link controls
16717		} else {
16718			elms = getElements( link, data );
16719			for ( i = 0, len = elms.length; i !== len; i += 1 ) {
16720				elm = elms[ i ];
16721				if ( !elm.id ) {
16722					elm.id = wb.getId();
16723				}
16724				ariaControls += elm.id + " ";
16725			}
16726			link.setAttribute( "aria-controls", ariaControls.slice( 0, -1 ) );
16727		}
16728	},
16729
16730	/**
16731	 * Initializes persistent behaviour of the toggle element
16732	 * @param {jQuery Object} $link The toggle element to initialize
16733	 * @param {Object} data Simple key/value data object passed when the event was triggered
16734	 * @returns {string} Persistent state
16735	 */
16736	initPersist = function( $link, data ) {
16737		var state,
16738			link = $link[ 0 ];
16739
16740		// Store the persistence type and key for later use
16741		data.persist = data.persist === "session" ? sessionStorage : localStorage;
16742		data.persistKey = componentName + ( data.group ? data.group : "" ) + link.id;
16743
16744		// If there's a saved toggle state, trigger the change to that state
16745		state = data.persist.getItem( data.persistKey );
16746		if ( state ) {
16747			$link.trigger( toggleEvent, $.extend( {}, data, { type: state } ) );
16748		}
16749
16750		return state;
16751	},
16752
16753	/**
16754	 * Initialize open on print behaviour of the toggle element
16755	 * @param {jQuery Object} $link The toggle element to initialize
16756	 * @param {Object} data Simple key/value data object passed when the event was triggered
16757	 */
16758	initPrint = function( $link, data ) {
16759		var mediaQuery,
16760			printEvent = "beforeprint";
16761
16762		$window.on( printEvent, function() {
16763			$link.trigger( toggleEvent, $.extend( {}, data, { type: data.print } ) );
16764		} );
16765
16766		// Fallback for browsers that don't support print events
16767		if ( window.matchMedia ) {
16768			mediaQuery = window.matchMedia( "print" );
16769			if ( mediaQuery.addListener ) {
16770				mediaQuery.addListener( function( query ) {
16771					if ( query.matches ) {
16772						$window.trigger( printEvent );
16773					}
16774				} );
16775			}
16776		}
16777	},
16778
16779	/**
16780	 * Click handler for the toggle links
16781	 * @param {jQuery Event} event The event that triggered this invocation
16782	 */
16783	click = function( event ) {
16784		var $link = $( event.currentTarget );
16785
16786		$link.trigger( toggleEvent, $link.data( "toggle" ) );
16787		event.preventDefault();
16788
16789		// Assign focus to eventTarget
16790		$link.trigger( setFocusEvent );
16791	},
16792
16793	/**
16794	 * Toggles the elements a link controls between the on and off states.
16795	 * @param {jQuery Event} event The event that triggered this invocation
16796	 * @param {Object} data Simple key/value data object passed when the event was triggered
16797	 */
16798	toggle = function( event, data ) {
16799		if ( event.namespace === componentName ) {
16800			var dataGroup, key, $elmsGroup,
16801				isGroup = !!data.group,
16802				isPersist = !!data.persist,
16803				isTablist = isGroup && !!data.parent,
16804				link = event.currentTarget,
16805				$link = $( link ),
16806				stateFrom = getState( $link, data ),
16807				isToggleOn = stateFrom === data.stateOff,
16808				stateTo = isToggleOn ? data.stateOn : data.stateOff,
16809				$elms = isTablist ?	$link.parent( data.group ) : getElements( link, data );
16810
16811			// Group toggle behaviour: only one element in the group open at a time.
16812			if ( isGroup ) {
16813
16814				// Get the grouped elements using data.group as the CSS selector
16815				dataGroup = $.extend( {}, data, { selector: data.group } );
16816				$elmsGroup = getElements( link, dataGroup );
16817
16818				// Set the toggle state to "off".  For tab lists, this is stored on the tab element
16819				setState( isTablist ? $( data.parent ).find( selectorTab ) : $elmsGroup,
16820					dataGroup, data.stateOff );
16821
16822				// Toggle all grouped elements to "off"
16823				$elmsGroup.wb( "toggle", data.stateOff, data.stateOn );
16824				$elmsGroup.trigger( toggledEvent, {
16825					isOn: false,
16826					isTablist: isTablist,
16827					elms: $elmsGroup
16828				} );
16829
16830				// Remove all grouped persistence keys
16831				if ( isPersist ) {
16832					for ( key in data.persist ) {
16833						if ( key.indexOf( componentName + data.group ) === 0 ) {
16834							data.persist.removeItem( key );
16835						}
16836					}
16837				}
16838			}
16839
16840			// Set the toggle state. For tab lists, this is set on the tab element
16841			setState( isTablist ? $link : $elms, data, stateTo );
16842
16843			// Toggle all elements to the requested state
16844			$elms.wb( "toggle", stateTo, stateFrom );
16845			$elms.trigger( toggledEvent, {
16846				isOn: isToggleOn,
16847				isTablist: isTablist,
16848				elms: $elms
16849			} );
16850
16851			// Ensure that last focused element in the accordion remains keyboard focusable
16852			// whether it is collapsed or not
16853			if ( isGroup ) {
16854				$elms.find( "summary" ).attr( { "tabindex": "0" } );
16855			}
16856
16857			// Store the toggle link's current state if persistence is turned on.
16858			// Try/catch is required to address exceptions thrown when using BB10 or
16859			// private browsing in iOS.
16860			if ( isPersist ) {
16861				try {
16862					data.persist.setItem( data.persistKey, stateTo );
16863				} catch ( error ) {
16864
16865					/* swallow error */
16866				}
16867			}
16868		}
16869	},
16870
16871	/**
16872	 * Sets the required property and attribute for toggling open/closed a details element
16873	 * @param {jQuery Event} event The event that triggered this invocation
16874	 * @param {Object} data Simple key/value data object passed when the event was triggered
16875	 */
16876	toggleDetails = function( event, data ) {
16877		if ( event.namespace === componentName && event.target === event.currentTarget ) {
16878			var top,
16879				isOn = data.isOn,
16880				$elms = data.elms,
16881				$this = $( this ),
16882				$detail = $this.is( "summary" ) ? $this.parent() : $this;
16883
16884			// Stop propagation of the toggleDetails event
16885			if ( event.stopPropagation ) {
16886				event.stopImmediatePropagation();
16887			} else {
16888				event.cancelBubble = true;
16889			}
16890
16891			$detail.prop( "open", isOn );
16892
16893			if ( data.isTablist ) {
16894
16895				// Set the required aria attributes
16896				$elms.find( selectorTab ).parents( selectorTab ).attr( {
16897					"aria-selected": isOn,
16898					tabindex: isOn ? "0" : "-1"
16899				} );
16900				$elms.find( selectorPanel ).attr( {
16901					"aria-hidden": !isOn,
16902					"aria-expanded": isOn
16903				} );
16904
16905				// Check that the top of the open element is in view.
16906				if ( isOn && $elms.length === 1 ) {
16907					top = $elms.offset().top;
16908					if ( top < $window.scrollTop() ) {
16909						$window.scrollTop( top );
16910					}
16911				}
16912			}
16913		}
16914	},
16915
16916	/**
16917	 * Returns the elements a given toggle element controls.
16918	 * @param {DOM element} link Toggle element that was clicked
16919	 * @param {Object} data Simple key/value data object passed when the event was triggered
16920	 * @returns {jQuery Object} DOM elements the toggle link controls
16921	 */
16922	getElements = function( link, data ) {
16923		var selector = data.selector || link,
16924			parent = data.parent || null;
16925
16926		return parent !== null ? $( parent ).find( selector ) : $( selector );
16927	},
16928
16929	/**
16930	 * Gets the current toggle state of elements controlled by the given link.
16931	 * @param {jQuery Object} $link Toggle link that was clicked
16932	 * @param {Object} data Simple key/value data object passed when the event was triggered
16933	 */
16934	getState = function( $link, data ) {
16935		var parent = data.parent,
16936			selector = data.selector,
16937			type = data.type;
16938
16939		// Get opposite state of the type. Toggle reverses this
16940		// to the requested state.
16941		if ( type ) {
16942			return type === "on" ? data.stateOff : data.stateOn;
16943
16944		// <details> elements use the open attribute to determine state
16945		} else if ( $link[ 0 ].nodeName.toLowerCase() === "summary" ) {
16946			return $link.parent().attr( "open" ) ? data.stateOn : data.stateOff;
16947
16948		// When no selector, use the data attribute of the link
16949		} else if ( !selector ) {
16950			return $link.data( componentName + "-state" ) || data.stateOff;
16951
16952		// When toggling multiple <details> elements, state is "off" if any are collapsed
16953		} else if ( selector === "details" && !type ) {
16954			var anyCollapsed = false;
16955			getElements( $link, data ).each( function() {
16956				if ( !$( this ).attr( "open" ) ) {
16957					anyCollapsed = true;
16958				}
16959			} );
16960			return anyCollapsed ? data.stateOff : data.stateOn;
16961
16962		// Get the current on/off state of the elements specified by the selector and parent
16963		} else if ( Object.prototype.hasOwnProperty.call( states, selector ) ) {
16964			return Object.prototype.hasOwnProperty.call( states[ selector ], parent ) ?
16965				states[ selector ][ parent ] :
16966				states[ selector ].all;
16967		}
16968		return data.stateOff;
16969	},
16970
16971	/*
16972	 * Sets the current toggle state of elements controlled by the given link.
16973	 * @param {DOM element} link Toggle link that was clicked
16974	 * @param {Object} data Simple key/value data object passed when the event was triggered
16975	 * @param {String} state The current state of the elements: "on" or "off"
16976	 */
16977	setState = function( $elms, data, state ) {
16978		var prop,
16979			parent = data.parent,
16980			selector = data.selector,
16981			elmsState = states[ selector ];
16982
16983		if ( selector ) {
16984
16985			// Check the selector object has been created
16986			if ( !elmsState ) {
16987				elmsState = { all: data.stateOff };
16988				states[ selector ] = elmsState;
16989			}
16990
16991			// If there's a parent, set its state
16992			if ( parent ) {
16993				elmsState[ parent ] = state;
16994
16995			// No parent means set all states for the given selector. This is
16996			// because toggle links that apply to the entire DOM also affect
16997			// links that are restricted by parent.
16998			} else {
16999				for ( prop in elmsState ) {
17000					if ( Object.prototype.hasOwnProperty.call( elmsState, prop ) ) {
17001						elmsState[ prop ] = state;
17002					}
17003				}
17004			}
17005		}
17006
17007		// Store the state on the elements as well. This allows a link to toggle itself.
17008		$elms.data( componentName + "-state", state );
17009	};
17010
17011// Bind the plugin's events
17012$document.on( "timerpoke.wb " + initEvent + " " + toggleEvent +
17013" click", selector, function( event, data ) {
17014
17015	var eventType = event.type;
17016
17017	switch ( eventType ) {
17018	case "click":
17019		click( event );
17020		break;
17021
17022	case "toggle":
17023		toggle( event, data );
17024		break;
17025
17026	case "timerpoke":
17027	case "wb-init":
17028		init( event );
17029		break;
17030	}
17031} );
17032
17033$document.on( toggledEvent, "summary, details", toggleDetails );
17034
17035// Keyboard handling for the accordion
17036$document.on( "keydown", selectorTab, function( event ) {
17037	var which = event.which,
17038		data, $elm, $parent, $group, $newPanel, index;
17039
17040	if ( !event.ctrlKey && which > 34 && which < 41 ) {
17041		event.preventDefault();
17042		$elm = $( event.currentTarget );
17043		data = $elm.data( "toggle" );
17044		$parent = $document.find( data.parent );
17045		$group = $parent.find( data.group );
17046		index = $group.index( $elm.parent() );
17047
17048		switch ( which ) {
17049
17050		// End
17051		case 35:
17052			$newPanel = $group.last();
17053			break;
17054
17055		// Home
17056		case 36:
17057			$newPanel = $group.first();
17058			break;
17059
17060		// Left / up arrow
17061		case 37:
17062		case 38:
17063			if ( index === 0 ) {
17064				$newPanel = $group.last();
17065			} else {
17066				$newPanel = $group.eq( index - 1 );
17067			}
17068			break;
17069
17070		// Right / down arrow
17071		case 39:
17072		case 40:
17073			if ( index === $group.length - 1 ) {
17074				$newPanel = $group.first();
17075			} else {
17076				$newPanel = $group.eq( index + 1 );
17077			}
17078			break;
17079		}
17080
17081		$newPanel
17082			.children( "summary" )
17083			.trigger( setFocusEvent );
17084	}
17085} );
17086
17087$document.on( "keydown", selectorPanel, function( event ) {
17088
17089	// Ctrl + Up arrow
17090	if ( event.ctrlKey && event.which === 38 ) {
17091
17092		// Move focus to the summary element
17093		$( event.currentTarget )
17094			.prev()
17095			.trigger( setFocusEvent );
17096	}
17097} );
17098
17099// Add the timer poke to initialize the plugin
17100wb.add( selector );
17101
17102} )( jQuery, window, wb );
17103
17104/**
17105 * @title WET-BOEW Twitter embedded timeline
17106 * @overview Helps with implementing Twitter embedded timelines.
17107 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
17108 * @author @pjackson28
17109 */
17110( function( $, window, wb ) {
17111"use strict";
17112
17113/*
17114 * Variable and function definitions.
17115 * These are global to the plugin - meaning that they will be initialized once per page,
17116 * not once per instance of plugin on the page. So, this is a good place to define
17117 * variables that are common to all instances of the plugin on a page.
17118 */
17119var componentName = "wb-twitter",
17120	selector = "." + componentName,
17121	initEvent = "wb-init" + selector,
17122	$document = wb.doc,
17123
17124	/**
17125	 * @method init
17126	 * @param {jQuery Event} event Event that triggered the function call
17127	 */
17128	init = function( event ) {
17129
17130		// Start initialization
17131		// returns DOM object = proceed with init
17132		// returns undefined = do not proceed with init (e.g., already initialized)
17133		var eventTarget = wb.init( event, componentName, selector ),
17134			protocol = wb.pageUrlParts.protocol;
17135
17136		if ( eventTarget ) {
17137			const twitterLink = eventTarget.firstElementChild;
17138
17139			// Ignore IE11
17140			// Note: Twitter's widget no longer supports it...
17141			if ( wb.ie11 ) {
17142				wb.ready( $( eventTarget ), componentName );
17143				return;
17144			}
17145
17146			// If the plugin container's first child element is a Twitter link...
17147			if ( twitterLink && twitterLink.matches( "a.twitter-timeline" ) ) {
17148				const loadingDiv = document.createElement( "div" );
17149				let observer;
17150
17151				// Add a loading icon below the link
17152				loadingDiv.className = "twitter-timeline-loading";
17153				twitterLink.after( loadingDiv );
17154
17155				// Remove the loading icon after the timeline widget appears
17156				// Note: Twitter's widget script removes "a.twitter-timeline" upon filling-in the timeline's content... at which point the loading icon is no longer useful
17157				observer = new MutationObserver( function( mutations ) {
17158
17159					// Check for DOM mutations
17160					mutations.forEach( function( mutation ) {
17161
17162						// Deal only with removed HTML nodes
17163						mutation.removedNodes.forEach( function( removedNode ) {
17164
17165							// If the removed node was a Twitter link, remove its adjacent loading icon and stop observing
17166							if ( removedNode === twitterLink && mutation.nextSibling === loadingDiv ) {
17167								loadingDiv.remove();
17168								observer.disconnect();
17169							}
17170						} );
17171					} );
17172				} );
17173
17174				// Observe changes to the plugin container's direct child elements
17175				observer.observe( eventTarget, {
17176					childList: true
17177				} );
17178			}
17179
17180			Modernizr.load( {
17181				load: ( protocol.indexOf( "http" ) === -1 ? "http:" : protocol ) + "//platform.twitter.com/widgets.js",
17182				complete: function() {
17183
17184					// Identify that initialization has completed
17185					wb.ready( $( eventTarget ), componentName );
17186				}
17187			} );
17188		}
17189	};
17190
17191$document.on( "timerpoke.wb " + initEvent, selector, init );
17192
17193// Add the timer poke to initialize the plugin
17194wb.add( selector );
17195
17196} )( jQuery, window, wb );
17197
17198/**
17199 * @title WET-BOEW Data Json [data-json-after], [data-json-append],
17200 * [data-json-before], [data-json-prepend], [data-json-replace], [data-json-replacewith] and [data-wb-json]
17201 * @overview Insert content extracted from JSON file.
17202 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
17203 * @author @duboisp
17204 */
17205/*global jsonpointer */
17206( function( $, window, wb ) {
17207"use strict";
17208
17209/*
17210 * Variable and function definitions.
17211 * These are global to the plugin - meaning that they will be initialized once per page,
17212 * not once per instance of plugin on the page. So, this is a good place to define
17213 * variables that are common to all instances of the plugin on a page.
17214 */
17215var componentName = "wb-data-json",
17216	shortName = "wb-json",
17217	selectors = [
17218		"[data-json-after]",
17219		"[data-json-append]",
17220		"[data-json-before]",
17221		"[data-json-prepend]",
17222		"[data-json-replace]",
17223		"[data-json-replacewith]",
17224		"[data-" + shortName + "]"
17225	],
17226	allowJsonTypes = [ "after", "append", "before", "prepend", "val" ],
17227	allowAttrNames = /(href|src|data-*|aria-*|role|pattern|min|max|step|low|high|lang|hreflang|action)/,
17228	allowPropNames = /(checked|selected|disabled|required|readonly|multiple|hidden)/,
17229	selectorsLength = selectors.length,
17230	selector = selectors.join( "," ),
17231	initEvent = "wb-init." + componentName,
17232	updateEvent = "wb-update." + componentName,
17233	contentUpdatedEvent = "wb-contentupdated",
17234	dataQueue = componentName + "-queue",
17235	$document = wb.doc,
17236	isExtensionRegistered,
17237	s,
17238
17239	/**
17240	 * @method init
17241	 * @param {jQuery Event} event Event that triggered this handler
17242	 * @param {string} ajaxType The type of JSON operation, either after, append, before or replace
17243	 */
17244	init = function( event ) {
17245
17246		// Start initialization
17247		// returns DOM object = proceed with init
17248		// returns undefined = do not proceed with init (e.g., already initialized)
17249		var elm = wb.init( event, componentName, selector ),
17250			jsSettings = window[ componentName ] || { },
17251			prop,
17252			$elm;
17253
17254		if ( elm ) {
17255
17256			var jsonCoreTypes = [
17257					"before",
17258					"replace",
17259					"replacewith",
17260					"after",
17261					"append",
17262					"prepend"
17263				],
17264				jsonType, jsondata,
17265				i, i_len = jsonCoreTypes.length, i_cache,
17266				lstCall = [],
17267				url;
17268
17269			$elm = $( elm );
17270
17271			for ( i = 0; i !== i_len; i += 1 ) {
17272				jsonType = jsonCoreTypes[ i ];
17273				url = elm.getAttribute( "data-json-" + jsonType );
17274				if ( url !== null ) {
17275					lstCall.push( {
17276						type: jsonType,
17277						url: url
17278					} );
17279				}
17280			}
17281
17282			// Extend but not overwrite the functionTest and the functionForOperand if some was added
17283			if ( !isExtensionRegistered ) {
17284				if ( jsSettings.functionForTest ) {
17285					for ( prop in jsSettings.functionForTest ) {
17286						if ( !functionForTest[ prop ] ) {
17287							functionForTest[ prop ] = jsSettings.functionForTest[ prop ];
17288						}
17289					}
17290				}
17291				if ( jsSettings.functionForOperand ) {
17292					for ( prop in jsSettings.functionForOperand ) {
17293						if ( !functionForOperand[ prop ] ) {
17294							functionForOperand[ prop ] = jsSettings.functionForOperand[ prop ];
17295						}
17296					}
17297				}
17298				isExtensionRegistered = true;
17299			}
17300
17301			// Identify that initialization has completed
17302			wb.ready( $elm, componentName );
17303
17304			jsondata = wb.getData( $elm, shortName );
17305
17306			if ( jsondata && jsondata.url ) {
17307				lstCall.push( jsondata );
17308			} else if ( jsondata && $.isArray( jsondata ) ) {
17309				i_len = jsondata.length;
17310				for ( i = 0; i !== i_len; i += 1 ) {
17311					lstCall.push( jsondata[ i ] );
17312				}
17313			}
17314
17315			// Save it to the dataJSON object.
17316			$elm.data( dataQueue, lstCall );
17317
17318			i_len = lstCall.length;
17319			for ( i = 0; i !== i_len; i += 1 ) {
17320				i_cache = lstCall[ i ];
17321				loadJSON( elm, i_cache.url, i, i_cache.nocache, i_cache.nocachekey, i_cache.data, i_cache.contenttype, i_cache.method );
17322			}
17323
17324		}
17325	},
17326
17327	loadJSON = function( elm, url, refId, nocache, nocachekey, data, contentType, method ) {
17328		var $elm = $( elm ),
17329			fetchObj = {
17330				url: url,
17331				refId: refId,
17332				nocache: nocache,
17333				nocachekey: nocachekey,
17334				data: data,
17335				contentType: contentType,
17336				method: method
17337			};
17338
17339		$elm.trigger( {
17340			type: "json-fetch.wb",
17341			fetch: fetchObj
17342		} );
17343	},
17344
17345
17346	// Manage JSON value After the json data has been fetched. This function can deal with array.
17347	jsonFetched = function( event ) {
17348
17349		var elm = event.target,
17350			$elm = $( elm ),
17351			lstCall = $elm.data( dataQueue ),
17352			fetchObj = event.fetch,
17353			itmSettings = lstCall[ fetchObj.refId ],
17354			jsonType = itmSettings.type,
17355			attrname = itmSettings.prop || itmSettings.attr,
17356			showEmpty = itmSettings.showempty,
17357			content = fetchObj.response,
17358			typeOfContent = typeof content,
17359			jQueryCaching;
17360
17361		if ( showEmpty || typeOfContent !== "undefined" ) {
17362
17363			if ( showEmpty && typeOfContent === "undefined" ) {
17364				content = "";
17365			}
17366
17367			//Prevents the force caching of nested resources
17368			jQueryCaching = jQuery.ajaxSettings.cache;
17369			jQuery.ajaxSettings.cache = true;
17370
17371			// "replace" and "replaceWith" doesn't map to a jQuery function
17372			if ( !jsonType ) {
17373				jsonType = "template";
17374				applyTemplate( elm, itmSettings, content );
17375
17376				// Trigger wet
17377				if ( itmSettings.trigger && !wb.isDisabled ) {
17378					$elm
17379						.find( wb.allSelectors )
17380						.addClass( "wb-init" )
17381						.filter( ":not(#" + elm.id + " .wb-init .wb-init)" )
17382						.trigger( "timerpoke.wb" );
17383				}
17384			} else if ( jsonType === "replace" ) {
17385				$elm.html( content );
17386			} else if ( jsonType === "replacewith" ) {
17387				$elm.replaceWith( content );
17388			} else if ( jsonType === "addclass" ) {
17389				$elm.addClass( content );
17390			} else if ( jsonType === "removeclass" ) {
17391				$elm.removeClass( content );
17392			} else if ( jsonType === "prop" && attrname && allowPropNames.test( attrname ) ) {
17393				$elm.prop( attrname, content );
17394			} else if ( jsonType === "attr" && attrname && allowAttrNames.test( attrname ) ) {
17395				$elm.attr( attrname, content );
17396			} else if ( typeof $elm[ jsonType ] === "function" && allowJsonTypes.indexOf( jsonType ) !== -1 ) {
17397				$elm[ jsonType ]( content );
17398			} else {
17399				throw componentName + " do not support type: " + jsonType;
17400			}
17401
17402			//Resets the initial jQuery caching setting
17403			jQuery.ajaxSettings.cache = jQueryCaching;
17404
17405			$elm.trigger( contentUpdatedEvent, { "json-type": jsonType, "content": content } );
17406		}
17407	},
17408
17409	// Apply the template as per the configuration
17410	applyTemplate = function( elm, settings, content ) {
17411
17412		var elmClass = elm.className,
17413			dataTable,
17414			dataTableAddRow,
17415			template = settings.source ? document.querySelector( settings.source ) : elm.querySelector( "template" );
17416
17417		// If combined with wb-tables plugin
17418		if ( elm.tagName === "TABLE" && elmClass.indexOf( "wb-tables" ) !== -1 ) {
17419
17420			//  Wait for its initialization before to applyTemplate
17421			if ( elmClass.indexOf( "wb-tables-inited" ) === -1 ) {
17422				$( elm ).one( "wb-ready.wb-tables,init.dt", function( ) {
17423					applyTemplate( elm, settings, content );
17424				} );
17425				return;
17426			}
17427
17428			// Edge case, when both plugin are ready at the same time, just wait for the next tick
17429			if ( !$.fn.dataTable.isDataTable( elm ) && elmClass.indexOf( componentName + "-dtwait" ) === -1 ) {
17430				elm.classList.add( componentName + "-dtwait" );
17431				setTimeout( function( ) {
17432					applyTemplate( elm, settings, content );
17433				}, 50 );
17434				return;
17435			}
17436
17437			dataTable = $( elm ).dataTable( { "retrieve": true } ).api();
17438			dataTableAddRow = dataTable.row.add;
17439			settings.tobeclone = "tr"; // Only table row can be added
17440		}
17441
17442		if ( !template ) {
17443			return;
17444		}
17445
17446		// Needed when executing sub-template that wasn't polyfill, like in IE11
17447		if ( !template.content ) {
17448			wb.tmplPolyfill( template );
17449		}
17450
17451		if ( !settings.streamline ) {
17452			dataIterator( elm, content, settings );
17453		} else {
17454			processMapping( elm, elm, content, settings );
17455		}
17456
17457		// Refresh the dataTable display
17458		if ( dataTableAddRow ) {
17459			dataTable.draw();
17460		}
17461	},
17462
17463	// Iterate over the dataset
17464	dataIterator = function( elm, content, mappingConfig, useClone ) {
17465
17466		var i, i_len, i_cache,
17467			elmAppendTo = elm,
17468			clone, template,
17469			dataTable, dataTableAddRow;
17470
17471		if ( mappingConfig.appendto ) {
17472			elmAppendTo = $( mappingConfig.appendto ).get( 0 );
17473		}
17474
17475		// Connection with data table plugin
17476		if ( elm.tagName === "TABLE" && elm.className.indexOf( "wb-tables" ) !== -1 ) {
17477			dataTable = $( elm ).dataTable( { "retrieve": true } ).api();
17478			dataTableAddRow = dataTable.row.add;
17479			mappingConfig.tobeclone = "tr";
17480		}
17481
17482
17483		// if content is object, transform into array @id and @value
17484		if ( !$.isArray( content ) ) {
17485			if ( typeof content !== "object" ) {
17486				content = [ content ];
17487			} else {
17488				content = $.map( content, function( val, index ) {
17489					if ( val && typeof val === "object" && !$.isArray( val ) ) {
17490						if ( !val[ "@id" ] ) {
17491							val[ "@id" ] = index;
17492						}
17493					} else {
17494						val = {
17495							"@id": index,
17496							"@value": val
17497						};
17498					}
17499					return [ val ];
17500				} );
17501			}
17502		}
17503		i_len = content.length;
17504
17505		// Get the template to be iterated.
17506		if ( !useClone && mappingConfig.source ) {
17507			template = document.querySelector( mappingConfig.source );
17508		} else if ( !useClone && mappingConfig.template ) {
17509			template = elm.querySelector( mappingConfig.template );
17510		} else if ( !useClone ) {
17511			template = elm.querySelectorAll( ":scope > template" );
17512			if ( template.length === 1 || template[ 0 ].attributes.length === 0 ) {
17513
17514				// Only when there is one choice or take the first one only if only there is no attribute set on the element
17515				template = template[ 0 ];
17516			} else {
17517
17518				// let the mapping instructions to define which template to use
17519				template = false;
17520			}
17521		}
17522
17523		// Iterate the data array
17524		for ( i = 0; i < i_len; i += 1 ) {
17525			i_cache = content[ i ];
17526
17527
17528			// If the data are filtered. This is deprecated and are only for backward compatible purpose
17529			if ( !filterPassJSON( i_cache, mappingConfig.filter, mappingConfig.filternot ) ) {
17530				continue;
17531			}
17532
17533			// Get the template (if applicable)
17534			if ( !clone && useClone ) {
17535				clone = useClone;
17536			}
17537
17538			// Create a clone if one unique template is found
17539			if ( !useClone && template && !mappingConfig.tobeclone ) {
17540				clone = template.content.cloneNode( true );
17541			} else if ( !useClone && template ) {
17542				clone = template.content.querySelector( mappingConfig.tobeclone ).cloneNode( true );
17543			}
17544
17545			// process the mapping, return value is the new clone object if applicable
17546			var tmpClone;
17547			tmpClone = processMapping( elm, clone, i_cache, mappingConfig );
17548
17549			// Remove the template flag, to ensure we do reuse it for the subsequent iteration
17550			if ( tmpClone ) {
17551				delete mappingConfig.template;
17552				clone = tmpClone;
17553			}
17554
17555			// Add the clone object
17556			if ( dataTableAddRow ) {
17557				dataTableAddRow( $( clone ) ); // If wb-tables, use its API to add rows
17558			} else if ( !useClone && template ) {
17559				elmAppendTo.appendChild( clone );
17560			}
17561		}
17562
17563		// Refresh the dataTable display (if applicable)
17564		if ( dataTableAddRow ) {
17565			dataTable.draw();
17566		}
17567
17568	},
17569
17570	// Check if the mapping are met or not
17571	canProcessMapping = function( content, mappingConfig ) {
17572
17573		var rawValue, value,
17574			testableData,
17575			operand = mappingConfig.operand || "softEq",
17576			operandOutcome;
17577
17578		if ( !mappingConfig.test ) {
17579			return;
17580		}
17581
17582		// Get the value to be tested
17583		try {
17584			rawValue = getRawValue( content, mappingConfig.assess || mappingConfig.value );
17585			value = getValue( rawValue );
17586		} catch ( ex ) {
17587
17588			// If this is an error, the path probably don't exist
17589			rawValue = undefined;
17590			value = undefined;
17591		}
17592
17593		// Get the function to use
17594		if ( !functionForTest[ mappingConfig.test ] ) {
17595			console.error( "The test function '" + mappingConfig.test + "' don't exist. Default to false test result" );
17596			console.error( mappingConfig );
17597			return false;
17598		}
17599		testableData = functionForTest[ mappingConfig.test ].call( mappingConfig, value, rawValue );
17600
17601		// Run the operand
17602		if ( !functionForOperand[ operand ] ) {
17603			console.error( "The operand '" + operand + "' don't exist" );
17604			console.error( mappingConfig );
17605			operand = "softEq";
17606		}
17607		operandOutcome = functionForOperand[ operand ].call( mappingConfig, testableData, mappingConfig.expect );
17608
17609		// If not true, go next
17610		if ( !operandOutcome ) {
17611			return false;
17612		}
17613
17614		// Run mapping if satisfied
17615		return true;
17616	},
17617
17618	// Special mapping typed function
17619	functionForTypedMapping = {
17620
17621		"rdf:Alt": function( elm, clone, content, mappingConfig ) {
17622
17623			var mapping = mappingConfig.mapping,
17624				i, i_cache,
17625				i_len = mapping.length,
17626				value = content;
17627
17628			for ( i = 0; i < i_len || i === 0; i += 1 ) {
17629				i_cache = mapping[ i ];
17630
17631				if ( canProcessMapping( content, i_cache ) ) {
17632
17633					// Clone the object to avoid conflict when it is reused for other data
17634					i_cache = $.extend( true, {}, i_cache );
17635
17636					// Remove the test, because it was checked
17637					delete i_cache.test;
17638
17639					// Navigate the content if specified
17640					if ( i_cache.value ) {
17641						value = getValue( content, i_cache.value );
17642					}
17643
17644					// Process the mapping
17645					processMapping( elm, clone, value, i_cache );
17646
17647					// End
17648					return;
17649				}
17650			}
17651		}
17652	},
17653
17654	// Function called for testing the mapping condition, which can be extend via the js configuration
17655	functionForTest = {
17656
17657		"fn:isArray": function( value ) {
17658			return $.isArray( value );
17659		},
17660
17661		"fn:isLiteral": function( value ) {
17662
17663			// Check if the value are set under the JSON-LD parameter @value
17664			if ( value && value[ "@value" ] ) {
17665				value = value[ "@value" ];
17666			}
17667
17668			if ( value && typeof value !== "object" ) {
17669				return true;
17670			}
17671
17672			return false;
17673		},
17674
17675		"fn:getType": function( value, rawValue ) {
17676
17677			var tp = value[ "@type" ] || rawValue[ "@type" ];
17678
17679			if ( tp === "@json" ) {
17680				return "rdf:JSON";
17681			} else if ( $.isArray( tp ) && tp.indexOf( "@json" ) !== -1 ) {
17682				tp[ tp.indexOf( "@json" ) ] = "rdf:JSON";
17683			}
17684
17685			if ( tp ) {
17686				return tp;
17687			} else {
17688				return typeof value;
17689			}
17690		},
17691
17692		"fn:getValue": function( value ) {
17693
17694			return value;
17695
17696		},
17697
17698		"fn:guessType": function( value, rawValue ) {
17699
17700			var guessType;
17701
17702			if ( !value ) {
17703				guessType = "undefined";
17704			} else if ( value[ "@type" ] ) {
17705				guessType = value[ "@type" ];
17706			} else if ( rawValue[ "@type" ] ) {
17707				guessType = rawValue[ "@type" ];
17708			} else if ( value[ "@value" ] ) {
17709
17710				// Only if we are in JSON ld mode
17711				// Check if the value are set under the JSON-LD parameter @value
17712				value = value[ "@value" ];
17713			}
17714
17715			// Edge case to convert the @json data type into its RDF form
17716			if ( guessType && guessType !== "undefined" ) {
17717				if ( guessType === "@json" ) {
17718					guessType = "rdf:JSON";
17719				} else if ( $.isArray( guessType ) && guessType.indexOf( "@json" ) !== -1 ) {
17720					guessType[ guessType.indexOf( "@json" ) ] = "rdf:JSON";
17721				}
17722			}
17723
17724			if ( !guessType ) {
17725				if ( typeof value === "string" && value.match( /^([a-z][a-z0-9+\-.]*):/i ) ) {
17726					guessType = [ "xsd:anyURI", "rdfs:Literal" ];
17727				} else if ( typeof value === "string" ) {
17728					guessType = [ "xsd:string", "rdfs:Literal" ];
17729				} else if ( typeof value === "boolean" ) {
17730					guessType = [ "xsd:boolean", "rdfs:Literal" ];
17731				} else if ( typeof value === "number" ) {
17732					guessType = [ "xsd:double", "rdfs:Literal" ];
17733				} else if ( typeof value === "undefined" ) {
17734					guessType = "undefined";
17735				} else if ( $.isArray( value ) ) {
17736					guessType = "rdfs:Container";
17737				} else {
17738
17739					// The type is a generic Object
17740					guessType = "rdfs:Resource";
17741				}
17742			}
17743
17744			return guessType;
17745		}
17746
17747	},
17748
17749	// Operand used to evaluate the testable output from functionForTest to determine if the mapping condition is met or not, which can be extend via the js configuration
17750	functionForOperand = {
17751
17752		"softEq": function( value, expect ) {
17753			var i, i_len;
17754
17755			if ( $.isArray( value ) && !$.isArray( expect ) && value.indexOf( expect ) !== -1 ) {
17756				return true;
17757			} else if ( $.isArray( value ) &&  $.isArray( expect ) ) {
17758				i_len = expect.length;
17759				for ( i = 0; i !== i_len; i++ ) {
17760					if ( value.indexOf( expect[ i ] ) ) {
17761						return true;
17762					}
17763				}
17764			} else if ( expect && value === expect ) {
17765				return true;
17766			} else if ( !expect && value ) {
17767				return true;
17768			}
17769
17770			return false;
17771
17772		},
17773
17774		"eq": function( value, expect ) {
17775
17776			if ( _equalsJSON( value, expect ) ) {
17777				return true;
17778			}
17779
17780			return false;
17781		},
17782
17783		"neq": function( value, expect ) {
17784			if ( !_equalsJSON( value, expect ) ) {
17785				return true;
17786			}
17787
17788			return false;
17789		},
17790
17791		"in": function( value, expect ) {
17792
17793			var i;
17794
17795			if ( !expect ) {
17796				console.error( "Expected value is missing. Defaulting to false." );
17797				console.error( this );
17798				return false;
17799			}
17800
17801			if ( $.isArray( value ) && !$.isArray( expect ) && value.indexOf( expect ) !== -1 ) {
17802				return true;
17803			} else if ( $.isArray( value ) &&  $.isArray( expect ) ) {
17804				for ( i = 0; i !== expect.length; i++ ) {
17805					if ( value.indexOf( expect[ i ] ) ) {
17806						return true;
17807					}
17808				}
17809			} else if ( !$.isArray( value ) &&  $.isArray( expect ) && expect.indexOf( value ) !== -1  ) {
17810				return true;
17811			} else if ( value === expect ) {
17812				return true;
17813			}
17814
17815			return false;
17816		},
17817
17818		"nin": function( value, expect ) {
17819			return !functionForOperand.in.call( this, value, expect );
17820		}
17821	},
17822
17823	// Mapping the data into a template or into a node
17824	processMapping = function( elm, clone, content, mappingConfig ) {
17825
17826		var j, j_cache,
17827			cached_node, cached_value,
17828			cached_value_is_HTML, cached_value_is_JSON, cached_value_is_IRI,
17829			queryAll = mappingConfig.queryall,
17830			selElements,
17831			mapping = mappingConfig.mapping,
17832			mapping_len,
17833			upstreamClone, template;
17834
17835
17836		// Is this mapping a special mapping type?
17837		if ( mappingConfig[ "@type" ] ) {
17838			functionForTypedMapping[ mappingConfig[ "@type" ] ].call( content, elm, clone, content, mappingConfig );
17839			return;
17840		}
17841
17842		// Can we proceed?
17843		if ( mappingConfig.test && !canProcessMapping( content, mappingConfig ) ) {
17844			return;
17845		}
17846
17847		// Check if there is some mapping configuration
17848		if ( !mapping && !queryAll && !mappingConfig.template && typeof mapping !== "object" ) {
17849			return;
17850		}
17851
17852		// Clone mappingConfig to ensure it don't interfere with subsequent data iteration
17853		mappingConfig = $.extend( true, {}, mappingConfig );
17854
17855		// If there is no clone, let use the element (parent)
17856		clone = clone || elm;
17857
17858		// If there is a "template" property, get the inner template
17859		if ( mappingConfig.template ) {
17860			template = clone.querySelector( mappingConfig.template );
17861
17862			upstreamClone = clone; // Keep reference of the top clone
17863
17864			clone = template.content.cloneNode( true );
17865
17866			// Ensure we don't recreated it if during a subsequent iteration
17867			delete mappingConfig.template;
17868		}
17869
17870
17871		// Is content an array? then iterate the content
17872		if ( $.isArray( content ) ) {
17873
17874
17875			dataIterator( clone, content, mappingConfig, clone );
17876
17877			// Case of where a template is associated with this mapping action
17878			if ( template ) {
17879				if ( template.parentNode ) {
17880
17881					if ( !mappingConfig.append ) {
17882						template.parentNode.insertBefore( clone, template );
17883					} else {
17884						template.parentNode.appendChild( clone );
17885					}
17886				} else {
17887					upstreamClone.appendChild( clone );
17888				}
17889
17890				return elm;
17891
17892			}
17893			return;
17894		}
17895
17896		// Prepare the mapping object to be iterated
17897		if ( !mapping ) {
17898			mapping = [ {} ];
17899		}
17900		if ( !$.isArray( mapping ) ) {
17901			mapping = [ mapping ];
17902		}
17903		mapping_len = mapping.length;
17904
17905		// Ensure the mapping is an array of Mapping Object
17906		for ( j = 0; j < mapping_len || j === 0; j += 1 ) {
17907			if ( typeof mapping[ j ] === "string" ) {
17908				mapping[ j ] = {
17909					value: mapping[ j ]
17910				};
17911			}
17912		}
17913
17914		if ( queryAll ) {
17915			selElements = clone.querySelectorAll( queryAll );
17916
17917			// Replicate this setting the in the mapping
17918			for ( j = 0; j < selElements.length || j === 0; j += 1 ) {
17919				if ( !mapping[ j ].selector && queryAll.indexOf( "nth-child" ) === -1 ) {
17920					mapping[ j ].selector = queryAll + ":nth-child(" + ( j + 1 ) + ")";
17921				} else if ( !mapping[ j ].selector ) {
17922					mapping[ j ].selector = queryAll;
17923				}
17924			}
17925		}
17926
17927
17928		//
17929		// Process the mapping
17930		//
17931		for ( j = 0; j < mapping_len || j === 0; j += 1 ) {
17932			j_cache = mapping[ j ];
17933
17934			// Reset the cache value special type flag
17935			cached_value_is_IRI = false;
17936			cached_value_is_HTML = false;
17937			cached_value_is_JSON = false;
17938
17939			// Get the element to be updated
17940			if ( j_cache.selector ) {
17941				cached_node = clone.querySelector( j_cache.selector );
17942			} else {
17943				cached_node = clone;
17944			}
17945
17946			// Get the value to be set
17947			try {
17948				cached_value = getRawValue( content, j_cache );
17949			} catch ( ex ) {
17950
17951				// The path don't exist, let continue to the next mapping item
17952				console.info( "JSON selector path for mapping don't exist in content" );
17953				console.info( j_cache );
17954				console.info( content );
17955				continue;
17956			}
17957
17958			// Go to the next mapping if the value of JSON node don't exist to ensure we keep the default text set in the template, but move ahead if empty or null
17959			if ( typeof cached_value === "undefined" ) {
17960				continue;
17961			}
17962
17963			// Do the cache value contain special @type
17964			if ( cached_value && cached_value[ "@value" ] && cached_value[ "@type" ] ) {
17965				if ( !$.isArray( cached_value[ "@type" ] ) ) {
17966					cached_value[ "@type" ] = [ cached_value[ "@type" ] ];
17967				}
17968				cached_value_is_IRI = cached_value[ "@type" ].indexOf( "@id" ) !== -1;
17969				cached_value_is_HTML = cached_value[ "@type" ].indexOf( "rdf:HTML" ) !== -1;
17970				cached_value_is_JSON = cached_value[ "@type" ].indexOf( "rdf:JSON" ) !== -1 || cached_value[ "@type" ].indexOf( "@json" ) !== -1;
17971			}
17972
17973			// Action the value
17974			if ( $.isArray( cached_value ) && ( j_cache.mapping || j_cache.queryall ) ) {
17975
17976				// Deep dive into the content if a mapping exist
17977				dataIterator( cached_node, cached_value, j_cache );
17978
17979			} else if ( j_cache.mapping || j_cache.queryall || !j_cache.mapping && typeof j_cache.mapping === "object" ) {
17980				try {
17981
17982					// Map the inner mapping
17983					processMapping( template || elm, cached_node, cached_value, j_cache );
17984				} catch ( ex ) {
17985
17986					if ( ex === "cached_node: null" && typeof cached_value === "object" ) {
17987
17988						// If it fail, let iterate the cached_value object
17989						dataIterator( cached_node, cached_value, j_cache );
17990					} else {
17991						throw ex;
17992					}
17993				}
17994			} else if ( cached_value_is_IRI && cached_value_is_HTML ) {
17995
17996				// The import file type are expected to be HTML
17997				// Add the data-ajax instruction so the content would be added once the JSON mapping is completed and added on the page.
17998				cached_node.dataset.wbAjax = JSON.stringify( {
17999					url: cached_value[ "@value" ],
18000					type: "replace",
18001					dataType: cached_value_is_JSON ? "json" : null,
18002					encode: j_cache.encode
18003				} );
18004			} else if ( cached_value_is_HTML && cached_value_is_JSON && !cached_value_is_IRI ) {
18005
18006				// Get content from the "@value" property which contain JSON value and use it as a string value
18007				cached_value = JSON.stringify( cached_value[ "@value" ] );
18008
18009				// Map the value in the element
18010				mapValue( cached_node, cached_value, j_cache );
18011
18012			} else if ( !cached_node && typeof cached_value === "object" ) {
18013				throw "cached_node: null";
18014			} else if ( mappingConfig.mapping !== null ) {
18015
18016				cached_value = getValue( cached_value );
18017
18018				// Serialize the value if it is an JS object and its not a null object
18019				if ( typeof cached_value === "object" &&  cached_value !== null ) {
18020					cached_value = JSON.stringify( cached_value );
18021				}
18022
18023				// Map the value in the element
18024				mapValue( cached_node, cached_value, j_cache );
18025			}
18026
18027		}
18028
18029		// Add the template, if applicable
18030		if ( template ) {
18031			if ( template.parentNode ) {
18032
18033				if ( !mappingConfig.append ) {
18034					template.parentNode.insertBefore( clone, template );
18035				} else {
18036					template.parentNode.appendChild( clone );
18037				}
18038			} else {
18039				upstreamClone.appendChild( clone );
18040			}
18041
18042			return elm;
18043		}
18044
18045	},
18046
18047	// Extract the value of an JS object
18048	getValue = function( source, pointer ) {
18049
18050		var value = getRawValue( source, pointer );
18051
18052		// for JSON-LD @value support
18053		if ( typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call( value, "@value" ) ) {
18054			value = value[ "@value" ];
18055		}
18056
18057		return value;
18058	},
18059
18060	// Extract the value without considering it possible JSON-LD value
18061	getRawValue = function( source, pointer ) {
18062
18063		var value;
18064		pointer = pointer || false; // Ensure pointer is defined
18065
18066		// Get the value if source is string or pointer is pointing to root
18067		if ( typeof source === "string" || pointer === "/" || pointer === "/@value" || pointer.value === "/" || pointer.value === "/@value" ) {
18068			value = source;
18069		} else if ( typeof pointer === "string" ) {
18070			value = jsonpointer.get( source, pointer );
18071		} else if ( pointer.value ) {
18072			value = jsonpointer.get( source, pointer.value );
18073		} else {
18074			value = source;
18075		}
18076
18077		return value;
18078	},
18079
18080	// Map a value into an HTML element or attribute
18081	mapValue = function( element, value, mappingConfig ) {
18082
18083		var attributeName, placeholderText;
18084
18085		attributeName = mappingConfig.attr;
18086		if ( attributeName ) {
18087			if ( !element.hasAttribute( attributeName ) ) {
18088				element.setAttribute( attributeName, "" );
18089			}
18090			element = element.getAttributeNode( attributeName );
18091		}
18092
18093		// Placeholder text replacement if any
18094		if ( mappingConfig.placeholder ) {
18095			placeholderText = element.textContent || "";
18096			value = placeholderText.replace( mappingConfig.placeholder, value );
18097		}
18098
18099		// Exclude null values and replace with default text
18100		if ( value !== null ) {
18101			if ( mappingConfig.isHTML ) {
18102				element.innerHTML = value;
18103			} else {
18104				element.textContent = value;
18105			}
18106		}
18107	},
18108
18109
18110	// Filtering a JSON
18111	// Return true if trueness && falseness
18112	// Return false if !( trueness && falseness )
18113	// trueness and falseness is an array of { "path": "", "value": "" } object
18114	filterPassJSON = function( obj, trueness, falseness ) {
18115		var i, i_cache,
18116			trueness_len = trueness ? trueness.length : 0,
18117			falseness_len = falseness ? falseness.length : 0,
18118			compareResult = false,
18119			isEqual;
18120
18121		if ( trueness_len || falseness_len ) {
18122
18123			for ( i = 0; i < trueness_len; i += 1 ) {
18124				i_cache = trueness[ i ];
18125				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
18126
18127				if ( i_cache.optional ) {
18128					compareResult = compareResult || isEqual;
18129				} else if ( !isEqual ) {
18130					return false;
18131				} else {
18132					compareResult = true;
18133				}
18134			}
18135			if ( trueness_len && !compareResult ) {
18136				return false;
18137			}
18138
18139			for ( i = 0; i < falseness_len; i += 1 ) {
18140				i_cache = falseness[ i ];
18141				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
18142
18143				if ( isEqual && !i_cache.optional || isEqual && i_cache.optional ) {
18144					return false;
18145				}
18146			}
18147
18148		}
18149		return true;
18150	},
18151
18152	//
18153	_equalsJSON = function( a, b ) {
18154		switch ( typeof a ) {
18155		case "undefined":
18156			return false;
18157		case "boolean":
18158		case "string":
18159		case "number":
18160			return a === b;
18161		case "object":
18162			if ( a === null ) {
18163				return b === null;
18164			}
18165			var i, l;
18166			if ( $.isArray( a ) ) {
18167				if (  !$.isArray( b ) || a.length !== b.length ) {
18168					return false;
18169				}
18170				for ( i = 0, l = a.length; i < l; i++ ) {
18171					if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
18172						return false;
18173					}
18174				}
18175				return true;
18176			}
18177			var bKeys = _objectKeys( b ),
18178				bLength = bKeys.length;
18179			if ( _objectKeys( a ).length !== bLength ) {
18180				return false;
18181			}
18182			for ( i in a ) {
18183				if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
18184					return false;
18185				}
18186			}
18187			return true;
18188		default:
18189			return false;
18190		}
18191	},
18192	_objectKeys = function( obj ) {
18193		var keys;
18194		if ( $.isArray( obj ) ) {
18195			keys = new Array( obj.length );
18196			for ( var k = 0; k < keys.length; k++ ) {
18197				keys[ k ] = "" + k;
18198			}
18199			return keys;
18200		}
18201		if ( Object.keys ) {
18202			return Object.keys( obj );
18203		}
18204		keys = [];
18205		for ( var i in obj ) {
18206			if ( Object.prototype.hasOwnProperty.call( obj, i ) ) {
18207				keys.push( i );
18208			}
18209		}
18210		return keys;
18211	},
18212
18213	// Manage JSON value After the json data has been fetched
18214	jsonUpdate = function( event ) {
18215		var elm = event.target,
18216			$elm = $( elm ),
18217			lstCall = $elm.data( dataQueue ),
18218			refId = lstCall.length,
18219			wbJsonConfig = event[ "wb-json" ];
18220
18221		if ( !( wbJsonConfig.url && ( wbJsonConfig.type || wbJsonConfig.source ) ) ) {
18222			throw "Data JSON update not configured properly";
18223		}
18224
18225		lstCall.push( wbJsonConfig );
18226		$elm.data( dataQueue, lstCall );
18227
18228		loadJSON( elm, wbJsonConfig.url, refId );
18229	};
18230
18231$document.on( "json-failed.wb", selector, function( event ) {
18232
18233	var elm = event.currentTarget,
18234		$elm = $( elm ),
18235		lstCall = $elm.data( dataQueue ),
18236		fetchObj = event.fetch,
18237		xhrResponse = fetchObj.xhr,
18238		itmSettings = lstCall[ fetchObj.refId ],
18239		failSettings = itmSettings.fail;
18240
18241	if ( failSettings ) {
18242
18243		// Mapping is always streamline because the data structure is a static object not an array
18244		failSettings.streamline = true;
18245
18246		// apply the templaty to display an error message
18247		applyTemplate( elm, failSettings, {
18248			error: fetchObj.error.message || xhrResponse.statusText,
18249			status: fetchObj.status,
18250			url: fetchObj.fetchOpts.url,
18251			response: {
18252				text: xhrResponse.responseText || "",
18253				status: xhrResponse.status,
18254				statusText: xhrResponse.statusText
18255			}
18256		} );
18257	}
18258
18259	console.info( event.currentTarget );
18260	console.error( "Error or bad JSON Fetched from url in " + componentName );
18261} );
18262
18263// Load template polyfill
18264Modernizr.load( {
18265	test: ( "content" in document.createElement( "template" ) ),
18266	nope: "site!deps/template" + wb.getMode() + ".js"
18267} );
18268
18269$document.on( "timerpoke.wb " + initEvent + " " + updateEvent + " json-fetched.wb", selector, function( event ) {
18270
18271	if ( event.currentTarget === event.target ) {
18272		switch ( event.type ) {
18273
18274		case "timerpoke":
18275		case "wb-init":
18276			init( event );
18277			break;
18278		case "wb-update":
18279			jsonUpdate( event );
18280			break;
18281		default:
18282			jsonFetched( event );
18283			break;
18284		}
18285	}
18286
18287	return true;
18288} );
18289
18290// Add the timerpoke to initialize the plugin
18291for ( s = 0; s !== selectorsLength; s += 1 ) {
18292	wb.add( selectors[ s ] );
18293}
18294
18295} )( jQuery, window, wb );
18296
18297/**
18298 * @title WET-BOEW Disable Event
18299 * @overview Event creates the active offer for users that have disabled the event.
18300 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
18301 * @author @gc
18302 */
18303( function( $, window, wb ) {
18304"use strict";
18305
18306/*
18307 * Variable and function definitions.
18308 * These are global to the event - meaning that they will be initialized once per page,
18309 * not once per instance of event on the page.
18310 */
18311var componentName = "wb-disable",
18312	selector = "#wb-tphp",
18313	$document = wb.doc,
18314	allowOnDisableClass = "wb-disable-allow",
18315	allowedPlugins = [
18316		{
18317			selectors: "[data-wb-jsonmanager]",
18318			initEvent: "wb-jsonmanager"
18319		},
18320		{
18321			selectors: "[data-wb-postback]",
18322			initEvent: "wb-postback"
18323		},
18324		{
18325			selectors: [
18326				"[data-ajax-after]",
18327				"[data-ajax-append]",
18328				"[data-ajax-before]",
18329				"[data-ajax-prepend]",
18330				"[data-ajax-replace]",
18331				"[data-wb-ajax]"
18332			],
18333			initEvent: "wb-data-ajax"
18334		},
18335		{
18336			selectors: [
18337				"[data-json-after]",
18338				"[data-json-append]",
18339				"[data-json-before]",
18340				"[data-json-prepend]",
18341				"[data-json-replace]",
18342				"[data-json-replacewith]",
18343				"[data-wb-json]"
18344			],
18345			initEvent: "wb-data-json"
18346		}
18347	],
18348
18349	/**
18350	 * @method init
18351	 * @param {jQuery Event} event Event that triggered the function call
18352	 */
18353	init = function( event ) {
18354
18355		// Start initialization
18356		// returns DOM object = proceed with init
18357		// returns undefined = do not proceed with init (e.g., already initialized)
18358		var elm = wb.init( event, componentName, selector, true ),
18359			nQuery = "?",
18360			$html = wb.html,
18361			i18n = wb.i18n,
18362			pageUrl = wb.pageUrlParts,
18363			param,
18364			noticeHeader = i18n( "disable-notice-h" ),
18365			noticeBody = i18n( "disable-notice" ),
18366			noticehtml = "<section",
18367			noticehtmlend = "</a>.</p></section>",
18368			canonicalUrl,
18369			canonicalLink;
18370
18371		if ( elm ) {
18372
18373			// Rebuild the query string
18374			for ( param in pageUrl.params ) {
18375				if ( param && Object.prototype.hasOwnProperty.call( pageUrl.params, param ) && param !== "wbdisable" ) {
18376					nQuery += param + "=" + pageUrl.params[ param ] + "&";
18377				}
18378			}
18379
18380			try {
18381				if ( wb.isDisabled || wb.ie ) {
18382					$html.addClass( "wb-disable" );
18383
18384					try {
18385
18386						// Store preference for WET plugins and polyfills to be disabled in localStorage
18387						localStorage.setItem( "wbdisable", "true" );
18388					} catch ( e ) {
18389
18390						/* swallow error */
18391					}
18392
18393					// Trigger initialization of plugins that are needed in basic mode
18394					runAllowedPlugins();
18395
18396					// Add canonical link if not already present
18397					if ( !document.querySelector( "link[rel=canonical]" ) ) {
18398
18399						// Remove wbdisable from URL
18400						canonicalUrl = window.location.href.replace( /&?wbdisable=true/gi, "" ).replace( "?&", "?" ).replace( "?#", "#" );
18401
18402						if ( canonicalUrl.indexOf( "?" ) === ( canonicalUrl.length - 1 ) ) {
18403							canonicalUrl = canonicalUrl.replace( "?", "" );
18404						}
18405
18406						canonicalLink = document.createElement( "link" );
18407						canonicalLink.rel = "canonical";
18408						canonicalLink.href = canonicalUrl;
18409
18410						document.head.appendChild( canonicalLink );
18411					}
18412
18413					// Add notice and link to re-enable WET plugins and polyfills
18414					let significantLinkId = wb.getId();
18415					noticehtml = noticehtml + " class='container-fluid bg-warning text-center mrgn-tp-sm py-4'><h2 class='mrgn-tp-0'>" + noticeHeader + "</h2><p>" + noticeBody + "</p><p><a id='" + significantLinkId + "' rel='alternate' href='" + nQuery + "wbdisable=false'>" + i18n( "wb-enable" ) + noticehtmlend;
18416					$( elm ).after( noticehtml );
18417					document.querySelector( "#" + significantLinkId ).setAttribute( "property", "significantLink" );
18418					return true;
18419				} else {
18420					$html.addClass( "wb-enable" );
18421
18422					if ( localStorage ) {
18423
18424						// Store preference for WET plugins and polyfills to be enabled in localStorage
18425						localStorage.setItem( "wbdisable", "false" );
18426					}
18427
18428					// Remove variable from URL
18429					var lc = window.location.href.replace( /&?wbdisable=false/gi, "" ).replace( "?&", "?" ).replace( "?#", "#" );
18430					if ( lc.indexOf( "?" ) === ( lc.length - 1 ) ) {
18431						lc = lc.replace( "?", "" );
18432					}
18433					window.history.replaceState( "", "", lc );
18434				}
18435			} catch ( error ) {
18436
18437				/* swallow error */
18438			}
18439
18440			// Append the Basic HTML version link version
18441			// Add link to disable WET plugins and polyfills
18442			wb.addSkipLink( i18n( "wb-disable" ), {
18443				href: nQuery + "wbdisable=true",
18444				rel: "alternate"
18445			}, false, true );
18446
18447			// Identify that initialization has completed
18448			wb.ready( $document, componentName );
18449		}
18450	},
18451
18452	// Trigger initialization of plugins that are needed in basic mode
18453	// TODO: Remove once basic initialization is implemented at the core level
18454	runAllowedPlugins = function() {
18455		allowedPlugins.forEach( allowedPlugin => {
18456			if ( typeof( allowedPlugin.selectors ) === "object" ) {
18457				allowedPlugin.selectors = allowedPlugin.selectors.join( "." + allowOnDisableClass + "," );
18458			}
18459
18460			allowedPlugin.selectors = allowedPlugin.selectors + "." + allowOnDisableClass;
18461
18462			$( allowedPlugin.selectors ).trigger( "wb-init." + allowedPlugin.initEvent );
18463		} );
18464	};
18465
18466// Bind the events
18467$document.on( "timerpoke.wb", selector, init );
18468
18469// Go through allowed plugins once again when content has been added dynamically
18470$document.on( "wb-contentupdated", function() {
18471	runAllowedPlugins();
18472} );
18473
18474// Add the timer poke to initialize the plugin
18475wb.add( selector );
18476
18477} )( jQuery, window, wb );
18478
18479/**
18480 * @title WET-BOEW Focus
18481 * @overview User agent safe way of assigning focus to an element
18482 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
18483 * @author @pjackson28
18484 */
18485( function( $, wb ) {
18486"use strict";
18487
18488var $document = wb.doc,
18489	$window = wb.win,
18490	clickEvents = "click",
18491	setFocusEvent = "setfocus.wb",
18492	linkSelector = "a[href]",
18493	$linkTarget,
18494
18495	/**
18496	 * @method processHash
18497	 */
18498	processHash = function() {
18499		var hash = wb.pageUrlParts.hash;
18500
18501		if ( hash && ( $linkTarget = $( "#" + wb.jqEscape( hash.substring( 1 ) ) ) ).length !== 0 ) {
18502			$linkTarget.trigger( setFocusEvent );
18503		}
18504	};
18505
18506// Bind the setfocus event
18507$document.on( setFocusEvent, function( event ) {
18508	if ( event.namespace === "wb" ) {
18509		var $elm = $( event.target ),
18510			$closedParents = $elm.not( "summary" ).parents( "details, [role='tabpanel']" ),
18511			$closedPanels, $closedPanel, len, i;
18512
18513		if ( $closedParents.length !== 0 ) {
18514
18515			// Open any closed ancestor details elements
18516			$closedParents.not( "[open]" ).children( "summary" ).trigger( "click" );
18517
18518			// Open any closed tabpanels
18519			$closedPanels = $closedParents.filter( "[aria-hidden='true']" );
18520			len = $closedPanels.length;
18521			for ( i = 0; i !== len; i += 1 ) {
18522				$closedPanel = $closedPanels.eq( i );
18523				$closedPanel.closest( ".wb-tabs" )
18524					.find( "#" + $closedPanel.attr( "aria-labelledby" ) )
18525					.trigger( "click" );
18526			}
18527		}
18528
18529		// Set the tabindex to -1 (as needed) to ensure the element is focusable
18530		$elm
18531			.filter( ":not([tabindex], a[href], button, input, textarea, select)" )
18532			.attr( "tabindex", "-1" );
18533
18534		// Assigns focus to an element (delay allows for revealing of hidden content)
18535		setTimeout( function() {
18536			$elm.trigger( "focus" );
18537
18538			var $topBar = $( ".wb-bar-t[aria-hidden=false]" );
18539
18540			// Ensure the top bar overlay does not conceal the focus target
18541			if ( $topBar.length !== 0 ) {
18542				document.documentElement.scrollTop -= $topBar.outerHeight();
18543			}
18544
18545			return $elm;
18546		}, 100 );
18547	}
18548} );
18549
18550// Set focus to the target of a deep link from a different page
18551// (helps browsers that can't set the focus on their own)
18552$document.on( "wb-ready.wb", processHash );
18553
18554// Handle any changes to the URL hash after the page has loaded
18555$window.on( "hashchange", function() {
18556	wb.pageUrlParts.hash = window.location.hash;
18557	if ( !wb.ignoreHashChange ) {
18558		processHash();
18559	}
18560} );
18561
18562// Helper for browsers that can't change keyboard and/or event focus on a same page link click
18563$document.on( clickEvents, linkSelector, function( event ) {
18564	var testHref = event.currentTarget.getAttribute( "href" );
18565
18566	// Same page links only
18567	if ( testHref.length > 1 && testHref.charAt( 0 ) === "#" && !event.isDefaultPrevented() &&
18568		( $linkTarget = $( "#" + wb.jqEscape( testHref.substring( 1 ) ) ) ).length !== 0 ) {
18569		wb.ignoreHashChange = true;
18570		$linkTarget.trigger( setFocusEvent );
18571	}
18572} );
18573
18574} )( jQuery, wb );
18575
18576/**
18577 * @title WET-BOEW JSON Manager
18578 * @overview Manage JSON dataset, execute JSON patch operation.
18579 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
18580 * @author @duboisp
18581 */
18582/*global jsonpointer, jsonpatch */
18583( function( $, window, wb ) {
18584"use strict";
18585
18586/*
18587 * Variable and function definitions.
18588 * These are global to the plugin - meaning that they will be initialized once per page,
18589 * not once per instance of plugin on the page. So, this is a good place to define
18590 * variables that are common to all instances of the plugin on a page.
18591 */
18592var componentName = "wb-jsonmanager",
18593	selector = "[data-" + componentName + "]",
18594	initEvent = "wb-init." + componentName,
18595	postponeEvent = "postpone." + componentName,
18596	patchesEvent = "patches." + componentName,
18597	jsonFailedClass = "jsonfail",
18598	reloadFlag = "data-" + componentName + "-reload",
18599	dsNameRegistered = [],
18600	datasetCache = {},
18601	datasetCacheSettings = {},
18602	dsDelayed = {},
18603	dsPostponePatches = {},
18604	dsFetching = {},
18605	dsFetchIsArray = {},
18606	dsFetchMerged = {},
18607	$document = wb.doc,
18608	defaults = {
18609		ops: [
18610			{
18611				name: "patches",
18612				fn: function( obj, key, tree ) {
18613					var path = this.path,
18614						patches = this.patches,
18615						newTree = jsonpointer.get( tree, path );
18616
18617					patches.forEach( ( patchConf ) => {
18618						patchConf.mainTree = tree;
18619						patchConf.pathParent = path;
18620						jsonpatch.apply( newTree, [ patchConf ] );
18621					} );
18622				}
18623			},
18624			{
18625				name: "wb-count",
18626				fn: function( obj, key, tree ) {
18627					var countme = obj[ key ],
18628						len = 0, i_len, i,
18629						filter = this.filter || [ ],
18630						filternot = this.filternot || [ ];
18631
18632					if ( !Array.isArray( filter ) ) {
18633						filter = [ filter ];
18634					}
18635					if ( !Array.isArray( filternot ) ) {
18636						filternot = [ filternot ];
18637					}
18638
18639					if ( ( filter.length || filternot.length ) && Array.isArray( countme ) ) {
18640
18641						// Iterate in obj[key] / item and check if is true for the given path is any.
18642						i_len = countme.length;
18643
18644						for ( i = 0; i !== i_len; i = i + 1 ) {
18645							if ( filterPassJSON( countme[ i ], filter, filternot ) ) {
18646								len = len + 1;
18647							}
18648						}
18649					} else if ( Array.isArray( countme ) ) {
18650						len = countme.length;
18651					}
18652					applyPatch( tree, "add", this.set, len );
18653				}
18654			},
18655			{
18656				name: "wb-first",
18657				fn: function( obj, key, tree ) {
18658					var currObj = obj[ key ];
18659					if ( !Array.isArray( currObj ) || currObj.length === 0 ) {
18660						return;
18661					}
18662
18663					applyPatch( tree, "add", this.set, currObj[ 0 ] );
18664				}
18665			},
18666			{
18667				name: "wb-last",
18668				fn: function( obj, key, tree ) {
18669					var currObj = obj[ key ];
18670					if ( !Array.isArray( currObj ) || currObj.length === 0 ) {
18671						return;
18672					}
18673
18674					applyPatch( tree, "add", this.set, currObj[ currObj.length - 1 ] );
18675				}
18676			},
18677			{
18678				name: "wb-nbtolocal",
18679				fn: function( obj, key, tree ) {
18680					var val = obj[ key ],
18681						loc = this.locale || window.wb.lang,
18682						suffix = this.suffix || "",
18683						prefix = this.prefix || "";
18684
18685					if ( typeof val === "string" ) {
18686						val = parseFloat( val );
18687						if ( isNaN( val ) ) {
18688							return;
18689						}
18690					}
18691
18692					applyPatch( tree, "replace", this.path, prefix + val.toLocaleString( loc ) + suffix );
18693				}
18694			},
18695			{
18696				name: "wb-decodeUTF8Base64",
18697				fn: function( obj, key, tree ) {
18698					var val = obj[ key ];
18699
18700					if ( !this.set ) {
18701						applyPatch( tree, "replace", this.path, wb.decodeUTF8Base64( val ) );
18702					} else {
18703						applyPatch( tree, "add", this.set, wb.decodeUTF8Base64( val ) );
18704					}
18705				}
18706			},
18707			{
18708				name: "wb-escapeHTML",
18709				fn: function( obj, key, tree ) {
18710					var val = obj[ key ];
18711
18712					if ( !this.set ) {
18713						applyPatch( tree, "replace", this.path, wb.escapeHTML( val ) );
18714					} else {
18715						applyPatch( tree, "add", this.set, wb.escapeHTML( val ) );
18716					}
18717				}
18718			},
18719			{
18720				name: "wb-toDateISO",
18721				fn: function( obj, key, tree ) {
18722					if ( !this.set ) {
18723						applyPatch( tree, "replace", this.path, wb.date.toDateISO( obj[ key ] ) );
18724					} else {
18725						applyPatch( tree, "add", this.set, wb.date.toDateISO( obj[ key ] ) );
18726					}
18727				}
18728			},
18729			{
18730				name: "wb-toDateTimeISO",
18731				fn: function( obj, key, tree ) {
18732					if ( !this.set ) {
18733						applyPatch( tree, "replace", this.path, wb.date.toDateISO( obj[ key ], true ) );
18734					} else {
18735						applyPatch( tree, "add", this.set, wb.date.toDateISO( obj[ key ], true ) );
18736					}
18737				}
18738			},
18739			{
18740				name: "wb-swap",
18741				fn: function( obj, key, tree ) {
18742					var val = obj[ key ],
18743						ref = this.ref,
18744						mainTree = this.mainTree || obj,
18745						path = this.path,
18746						newVal,
18747						refObject, refIsArray, valWasArray,
18748						i, i_len, i_item,
18749						j, j_len, j_item;
18750
18751					if ( val ) {
18752
18753						refObject = jsonpointer.get( mainTree, ref );
18754						refIsArray = Array.isArray( refObject );
18755						valWasArray = Array.isArray( val );
18756
18757						if ( !valWasArray ) {
18758							val =  [ val ];
18759						}
18760
18761						i_len = val.length;
18762
18763						for ( i = 0; i !== i_len; i++ ) {
18764							i_item = val[ i ];
18765							newVal = undefined; // Reinit
18766							if ( !refIsArray ) {
18767								i_item = i_item.replaceAll( "~", "~0" ).replaceAll( "/", "~1" ); // Escape slashed and tilde in val when the key is an IRI for JSON pointer compatibility
18768								newVal = mainTree ? jsonpointer.get( mainTree, ref + "/" 
18768+ i_item ) : jsonpointer.get( tree, ref + "/" + i_item );
18769							} else {
18770
18771								// Iterate until we found a corresponding value in the property "@id"
18772								j_len = refObject.length;
18773								for ( j = 0; j !== j_len; j++ ) {
18774									j_item = refObject[ j ];
18775									if ( j_item[ "@id" ] && j_item[ "@id" ] === i_item ) {
18776										newVal = j_item;
18777										break;
18778									}
18779								}
18780								if ( !newVal ) {
18781									console.error( "wb-swap: Unable to find a corresponding value for: " + val + " in reference " + ref );
18782									break;
18783								}
18784							}
18785							if ( newVal && !valWasArray ) {
18786								applyPatch( tree, "replace", path, newVal );
18787							} else if ( newVal ) {
18788								applyPatch( tree, "replace", path + "/" + i, newVal );
18789							}
18790						}
18791					}
18792				}
18793			}
18794		],
18795		opsArray: [
18796			{
18797				name: "wb-toDateISO",
18798				fn: function( arr )  {
18799					var setval = this.set,
18800						pathval = this.path,
18801						i, i_len = arr.length;
18802					for ( i = 0; i !== i_len; i += 1 ) {
18803						if ( setval ) {
18804							jsonpatch.apply( arr, [
18805								{ op: "wb-toDateISO", set: "/" + i + setval, path: "/" + i + pathval }
18806							] );
18807						} else {
18808							jsonpatch.apply( arr, [
18809								{ op: "wb-toDateISO", path: "/" + i + pathval }
18810							] );
18811						}
18812					}
18813				}
18814			},
18815			{
18816				name: "wb-toDateTimeISO",
18817				fn: function( arr ) {
18818					var setval = this.set,
18819						pathval = this.path,
18820						i, i_len = arr.length;
18821					for ( i = 0; i !== i_len; i += 1 ) {
18822						if ( setval ) {
18823							jsonpatch.apply( arr, [
18824								{ op: "wb-toDateTimeISO", set: "/" + i + setval, path: "/" + i + pathval }
18825							] );
18826						} else {
18827							jsonpatch.apply( arr, [
18828								{ op: "wb-toDateTimeISO", path: "/" + i + pathval }
18829							] );
18830						}
18831					}
18832				}
18833			},
18834			{
18835				name: "wb-swap",
18836				fn: function( arr ) {
18837					arr.forEach( ( item, i ) => {
18838						jsonpatch.apply( arr, [
18839							{ op: "wb-swap", path: "/" + i + this.path, ref: this.ref, mainTree: this.mainTree }
18840						] );
18841					} );
18842				}
18843			},
18844			{
18845				name: "patches",
18846				fn: function( arr ) {
18847					arr.forEach( ( item, i ) => {
18848						jsonpatch.apply( this.mainTree || arr, [
18849							{ op: "patches", path: ( this.pathParent || "" ) + "/" + i + this.path, patches: this.patches }
18850						] );
18851					} );
18852				}
18853			}
18854		],
18855		opsRoot: [],
18856		settings: { },
18857		docMapKeys: { "referer": document.referrer, "locationHref": location.href }
18858	},
18859
18860	// Add debug information after the JSON manager element
18861	debugPrintOut = function( $elm, name, json, patches ) {
18862		$elm.after( "<p lang=\"en\"><strong>JSON Manager Debug</strong> (" +  name + ")</p><ul lang=\"en\"><li>JSON: <pre><code>" + JSON.stringify( json ) + "</code></pre></li><li>Patches: <pre><code>" + JSON.stringify( patches ) + "</code></pre>" );
18863		console.log( json );
18864	},
18865
18866	/**
18867	 * @method init
18868	 * @param {jQuery Event} event Event that triggered the function call
18869	 */
18870	init = function( event ) {
18871
18872		// Start initialization
18873		// returns DOM object = proceed with init
18874		// returns undefined = do not proceed with init (e.g., already initialized)
18875		var elm = wb.init( event, componentName, selector ),
18876			$elm,
18877			jsSettings = window[ componentName ] || { },
18878			ops, opsArray, opsRoot,
18879			i, i_len, i_cache,
18880			url, urlActual, dsName,
18881			fetchOpts = { };
18882
18883		if ( elm ) {
18884			$elm = $( elm );
18885
18886			// Load handlebars
18887			Modernizr.load( {
18888
18889				// For loading multiple dependencies
18890				load: [
18891					"site!deps/json-patch" + wb.getMode() + ".js",
18892					"site!deps/jsonpointer" + wb.getMode() + ".js"
18893				],
18894				testReady: function() {
18895					return window.jsonpatch && window.jsonpointer;
18896				},
18897				complete: function() {
18898					var elmData = wb.getData( $elm, componentName );
18899
18900					if ( !defaults.registered ) {
18901						ops = defaults.ops.concat( jsSettings.ops || [ ] );
18902						opsArray = defaults.opsArray.concat( jsSettings.opsArray || [ ] );
18903						opsRoot = defaults.opsRoot.concat( jsSettings.opsRoot || [ ] );
18904
18905						if ( ops.length ) {
18906							for ( i = 0, i_len = ops.length; i !== i_len; i++ ) {
18907								i_cache = ops[ i ];
18908								jsonpatch.registerOps( i_cache.name, i_cache.fn );
18909							}
18910						}
18911						if ( opsArray.length ) {
18912							for ( i = 0, i_len = opsArray.length; i !== i_len; i++ ) {
18913								i_cache = opsArray[ i ];
18914								jsonpatch.registerOpsArray( i_cache.name, i_cache.fn );
18915							}
18916						}
18917						if ( opsRoot.length ) {
18918							for ( i = 0, i_len = opsRoot.length; i !== i_len; i++ ) {
18919								i_cache = opsRoot[ i ];
18920								jsonpatch.registerOpsRoot( i_cache.name, i_cache.fn );
18921							}
18922						}
18923						defaults.settings = $.extend( {}, defaults.settings, jsSettings.settings || {} );
18924						defaults.registered = true;
18925					}
18926
18927					dsName = elmData.name;
18928					if ( !dsName || dsName in dsNameRegistered ) {
18929						throw "Dataset name must be unique";
18930					}
18931					dsNameRegistered.push( dsName );
18932
18933					url = elmData.url;
18934
18935					if ( url ) {
18936
18937						url = typeof url === "string" ? [ url ] : url;
18938						i_len = url.length;
18939
18940						dsFetching[ dsName ] = i_len;
18941
18942						for ( i = 0; i !== i_len; i++ ) {
18943
18944							urlActual = url[ i ];
18945
18946							// Fetch default configuration
18947							fetchOpts = {
18948								nocache: elmData.nocache,
18949								nocachekey: elmData.nocachekey,
18950								data: elmData.data,
18951								contentType: elmData.contenttype,
18952								method: elmData.method
18953							};
18954
18955							// When the "url" is an extended configuration
18956							if ( urlActual.url ) {
18957								fetchOpts.savingPath = urlActual.path || "";
18958								fetchOpts.url = urlActual.url;
18959							} else {
18960								fetchOpts.url = urlActual;
18961							}
18962
18963							// Fetch the JSON
18964							$elm.trigger( {
18965								type: "json-fetch.wb",
18966								fetch: fetchOpts
18967							} );
18968
18969							// If the URL is a dataset, make it ready
18970							if ( fetchOpts.url.charCodeAt( 0 ) === 35 && fetchOpts.url.charCodeAt( 1 ) === 91 ) {
18971								wb.ready( $elm, componentName );
18972							}
18973						}
18974					} else if ( !url && elmData.extractor ) {
18975						$elm.trigger( {
18976							type: "json-fetched.wb",
18977							fetch: {
18978								response: {}
18979							}
18980						} );
18981						wb.ready( $elm, componentName );
18982
18983					} else {
18984
18985						$elm.trigger( {
18986							type: "json-fetch.wb"
18987						} );
18988						wb.ready( $elm, componentName );
18989					}
18990
18991				}
18992			} );
18993		}
18994	},
18995	extractData = function( elmObj ) {
18996
18997		var isGroup = false,
18998			selectedTag,
18999			targetTag,
19000			lastIndex = [],
19001			j_tag = "",
19002			group = {},
19003			arrMap = [],
19004			node_children = [],
19005			j_node = 0,
19006			arrRepeatPath = [],
19007			combineToObj = function( cur_obj ) {
19008				if ( cur_obj.selector === j_tag ) {
19009					if ( !lastIndex.includes( j_tag ) ) {
19010						group[ cur_obj.path ] = cur_obj.attr && node_children[ j_node ].getAttributeNode( cur_obj.attr ) ?
19011							node_children[ j_node ].getAttributeNode( cur_obj.attr ).textContent :
19012							node_children[ j_node ].textContent;
19013						lastIndex.push( j_tag );
19014					}
19015				}
19016			},
19017			manageObjDir = function( selector, selectedValue, json_return ) {
19018				var arrPath = selector.path.split( "/" ).filter( Boolean );
19019
19020				// Check if selectedValue is an empty value returned by querySelectorAll
19021				if ( selectedValue && selectedValue instanceof NodeList && selectedValue.length === 0 ) {
19022					selectedValue = null;
19023				}
19024
19025				if ( arrPath.length > 1 ) {
19026					var pointer = "";
19027					pointer = arrPath.pop();
19028
19029					if ( arrPath[ 0 ] && arrPath[ 0 ] !== "" ) {
19030
19031						if ( !json_return[ arrPath[ 0 ] ] && !arrRepeatPath.includes( arrPath[ 0 ] ) ) {
19032							arrRepeatPath.push( arrPath[ 0 ] );
19033							json_return[ arrPath[ 0 ] ] = {};
19034						}
19035						if ( selector.selectAll && !json_return[ arrPath[ 0 ] ] [ pointer ]  ) {
19036							json_return[ arrPath[ 0 ] ] [ pointer ] = [];
19037						}
19038						if ( selector.selectAll ) {
19039							json_return[ arrPath[ 0 ] ] [ pointer ].push( selectedValue );
19040						} else {
19041							json_return[ arrPath[ 0 ] ] [ pointer ] = selectedValue;
19042						}
19043
19044					} else {
19045
19046						if ( selector.selectAll ) {
19047							json_return[ arrPath[ 0 ] ].push( selectedValue );
19048						} else {
19049							json_return[ pointer ] = selectedValue;
19050						}
19051					}
19052				} else {
19053
19054					if ( selector.selectAll ) {
19055						if ( !json_return[ selectedTag.path ] ) {
19056							json_return[ selectedTag.path ] = [];
19057						}
19058						json_return[ selectedTag.path ].push( selectedValue );
19059					} else {
19060						json_return[ selectedTag.path ] = selectedValue;
19061					}
19062				}
19063			},
19064			jsonSource = {};
19065
19066
19067		for ( var tag = 0; tag <= elmObj.length - 1; tag++ ) {
19068
19069			selectedTag = elmObj[ tag ];
19070
19071			if ( !selectedTag.interface ) {
19072
19073				targetTag = document.querySelectorAll( selectedTag.selector || "" );
19074				isGroup = selectedTag.extractor && selectedTag.extractor.length >= 1 ? true : false;
19075
19076				if ( selectedTag.selectAll ) {
19077
19078					for ( var i_node = 0; i_node <= targetTag.length - 1; i_node++ ) {
19079
19080						var selectedTagValue = selectedTag.attr && targetTag [ i_node ].getAttributeNode( selectedTag.attr ) ?
19081							targetTag [ i_node ].getAttributeNode( selectedTag.attr ).textContent :
19082							targetTag [ i_node ].textContent;
19083
19084						manageObjDir( selectedTag, selectedTagValue, jsonSource );
19085					}
19086				}
19087
19088				// extract from combined selectors and group the values e.g dt with dd
19089				if ( isGroup ) {
19090
19091					jsonSource[ selectedTag.path ] = [];
19092
19093					node_children = targetTag[ 0 ].children;
19094
19095					var extractorLength = Object.keys( selectedTag.extractor ).length;
19096
19097					for ( j_node = 0; j_node <= node_children.length - 1; j_node++ ) {
19098
19099						j_tag = node_children[ j_node ].tagName.toLowerCase();
19100
19101						selectedTag.extractor.find( combineToObj );
19102						if ( Object.keys( group ).length === extractorLength ) {
19103							arrMap.push( group );
19104							group = {};
19105							lastIndex = [];
19106						}
19107					}
19108					$.extend( jsonSource[ selectedTag.path ], arrMap );
19109				}
19110
19111				if ( targetTag.length ) {
19112					targetTag = selectedTag.attr && targetTag [ 0 ].getAttributeNode( selectedTag.attr ) ?
19113						targetTag [ 0 ].getAttributeNode( selectedTag.attr ).textContent :
19114						targetTag [ 0 ].textContent;
19115				}
19116
19117			} else {
19118
19119				targetTag = defaults.docMapKeys[ selectedTag.interface ];
19120
19121				manageObjDir( selectedTag, targetTag, jsonSource );
19122			}
19123
19124			if ( !selectedTag.selectAll  ) {
19125				if ( isGroup === false ) {
19126					manageObjDir( selectedTag, targetTag, jsonSource );
19127				}
19128			}
19129		}
19130
19131		return jsonSource;
19132	},
19133
19134	// Filtering a JSON
19135	// Return true if trueness && falseness
19136	// Return false if !( trueness && falseness )
19137	// trueness and falseness is an array of { "path": "", "value": "" } object
19138	filterPassJSON = function( obj, trueness, falseness ) {
19139		var i, i_cache,
19140			trueness_len = trueness.length,
19141			falseness_len = falseness.length,
19142			compareResult = false,
19143			isEqual;
19144
19145		if ( trueness_len || falseness_len ) {
19146
19147			for ( i = 0; i < trueness_len; i += 1 ) {
19148				i_cache = trueness[ i ];
19149				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
19150
19151				if ( i_cache.optional ) {
19152					compareResult = compareResult || isEqual;
19153				} else if ( !isEqual ) {
19154					return false;
19155				} else {
19156					compareResult = true;
19157				}
19158			}
19159			if ( trueness_len && !compareResult ) {
19160				return false;
19161			}
19162
19163			for ( i = 0; i < falseness_len; i += 1 ) {
19164				i_cache = falseness[ i ];
19165				isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value );
19166
19167				if ( isEqual && !i_cache.optional || isEqual && i_cache.optional ) {
19168					return false;
19169				}
19170			}
19171
19172		}
19173		return true;
19174	},
19175
19176	// Utility function to compare two JSON value
19177	_equalsJSON = function( a, b ) {
19178		switch ( typeof a ) {
19179		case "undefined":
19180			return false;
19181		case "boolean":
19182		case "string":
19183		case "number":
19184			return a === b;
19185		case "object":
19186			if ( a === null ) {
19187				return b === null;
19188			}
19189			var i, l;
19190			if ( Array.isArray( a ) ) {
19191				if (  Array.isArray( b ) || a.length !== b.length ) {
19192					return false;
19193				}
19194				for ( i = 0, l = a.length; i < l; i++ ) {
19195					if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
19196						return false;
19197					}
19198				}
19199				return true;
19200			}
19201			var bKeys = _objectKeys( b ),
19202				bLength = bKeys.length;
19203			if ( _objectKeys( a ).length !== bLength ) {
19204				return false;
19205			}
19206			for ( i = 0; i < bLength; i++ ) {
19207				if ( !_equalsJSON( a[ i ], b[ i ] ) ) {
19208					return false;
19209				}
19210			}
19211			return true;
19212		default:
19213			return false;
19214		}
19215	},
19216	_objectKeys = function( obj ) {
19217		var keys;
19218		if ( Array.isArray( obj ) ) {
19219			keys = new Array( obj.length );
19220			for ( var k = 0; k < keys.length; k++ ) {
19221				keys[ k ] = "" + k;
19222			}
19223			return keys;
19224		}
19225		if ( Object.keys ) {
19226			return Object.keys( obj );
19227		}
19228		keys = [];
19229		for ( var i in obj ) {
19230			if ( Object.prototype.hasOwnProperty.call( obj, i ) ) {
19231				keys.push( i );
19232			}
19233		}
19234		return keys;
19235	},
19236
19237	// Utility function to apply a JSON patch
19238	applyPatch = function( tree, op, path, value ) {
19239		jsonpatch.apply( tree, [
19240			{ op: op, path: path, value: value }
19241		] );
19242	},
19243
19244	// Create series of patches for filtering
19245	getPatchesToFilter = function( JSONsource, filterPath, filterTrueness, filterFaslseness ) {
19246		var filterObj,
19247			i, i_len;
19248
19249		if ( !Array.isArray( filterTrueness ) ) {
19250			filterTrueness = [ filterTrueness ];
19251		}
19252		if ( !Array.isArray( filterFaslseness ) ) {
19253			filterFaslseness = [ filterFaslseness ];
19254		}
19255
19256		filterObj = jsonpointer.get( JSONsource, filterPath );
19257		if ( Array.isArray( filterObj ) ) {
19258			i_len = filterObj.length - 1;
19259			for ( i = i_len; i !== -1; i -= 1 ) {
19260				if ( !filterPassJSON( filterObj[ i ], filterTrueness, filterFaslseness ) ) {
19261					jsonpatch.apply( JSONsource, [ { op: "remove", path: filterPath + "/" + i } ] );
19262				}
19263			}
19264		}
19265		return JSONsource;
19266	};
19267
19268$document.on( "json-failed.wb", selector, function( event ) {
19269	var elm = event.target,
19270		$elm;
19271
19272	if ( elm === event.currentTarget ) {
19273		$elm = $( elm );
19274		$elm.addClass( jsonFailedClass );
19275
19276		// Identify that initialization has completed
19277		wb.ready( $elm, componentName );
19278	}
19279} );
19280
19281$document.on( "json-fetched.wb", selector, function( event ) {
19282	var elm = event.target,
19283		$elm = $( elm ),
19284		settings,
19285		fetchedOpts = event.fetch.fetchedOpts,
19286		isReloading = elm.hasAttribute( reloadFlag ),
19287		dsName,
19288		JSONresponse = event.fetch.response,
19289		isArrayResponse,
19290		resultSet,
19291		i, i_len, i_cache, backlog, selector,
19292		objIterator, savingPathSplit,
19293		patches, filterTrueness, filterFaslseness, filterPath, extractor;
19294
19295	if ( elm === event.currentTarget ) {
19296		settings = wb.getData( $elm, componentName );
19297
19298		// Is the fetched JSON need to be wrap in another plain object
19299		if ( fetchedOpts && fetchedOpts.savingPath ) {
19300			savingPathSplit = fetchedOpts.savingPath.split( "/" );
19301
19302			for ( i = savingPathSplit.length - 1; i > 0; i-- ) {
19303				if ( !savingPathSplit[ i ] ) {
19304					continue;
19305				}
19306				objIterator = {};
19307				objIterator[ savingPathSplit[ i ] ] = JSONresponse;
19308				JSONresponse = objIterator;
19309			}
19310		}
19311
19312		// Determine if the response is an array
19313		isArrayResponse = Array.isArray( JSONresponse );
19314
19315		// Ensure the response is an independant clone
19316		if ( isArrayResponse ) {
19317			JSONresponse = $.extend( true, [], JSONresponse );
19318		} else {
19319			JSONresponse = $.extend( true, {}, JSONresponse );
19320		}
19321
19322		dsName = settings.name;
19323		dsFetching[ dsName ]--;
19324
19325		// Ensure that we do have fetched and merged all urls everything before to move ahead
19326		dsFetchIsArray[ dsName ] = dsFetchIsArray[ dsName ] ? dsFetchIsArray[ dsName ] : isArrayResponse;
19327
19328		if ( dsFetchIsArray[ dsName ] !== isArrayResponse ) {
19329			throw "Can't merge, incompatible JSON type (array vs object)";
19330		}
19331
19332		if ( !dsFetchMerged[ dsName ] ) {
19333			dsFetchMerged[ dsName ] = JSONresponse;
19334		} else if ( dsFetchMerged[ dsName ] && isArrayResponse ) {
19335			dsFetchMerged[ dsName ] = dsFetchMerged[ dsName ].concat( JSONresponse );
19336		} else {
19337			dsFetchMerged[ dsName ] = $.extend( dsFetchMerged[ dsName ], JSONresponse );
19338		}
19339
19340		// Quit and wait for the next fetch
19341		if ( !isReloading && dsFetching[ dsName ] ) {
19342			return;
19343		}
19344
19345		JSONresponse = dsFetchMerged[ dsName ];
19346
19347		extractor = settings.extractor;
19348		if ( extractor ) {
19349			if ( !Array.isArray( extractor ) ) {
19350				extractor = [ extractor ];
19351			}
19352			JSONresponse = $.extend( JSONresponse, extractData( extractor ) );
19353
19354		}
19355
19356		dsName = "[" + dsName + "]";
19357		patches = settings.patches || [];
19358		filterPath = settings.fpath;
19359		filterTrueness = settings.filter || [];
19360		filterFaslseness = settings.filternot || [];
19361
19362		if ( !Array.isArray( patches ) ) {
19363			patches = [ patches ];
19364		}
19365
19366		// Apply a filtering
19367		if ( filterPath ) {
19368			JSONresponse = getPatchesToFilter( JSONresponse, filterPath, filterTrueness, filterFaslseness );
19369		}
19370
19371		// Apply the wraproot
19372		if ( settings.wraproot  ) {
19373			i_cache = { };
19374			i_cache[ settings.wraproot ] = JSONresponse;
19375			JSONresponse = i_cache;
19376		}
19377
19378		// Apply the patches
19379		if ( patches.length ) {
19380			jsonpatch.apply( JSONresponse, patches );
19381		}
19382
19383		if ( settings.debug ) {
19384			debugPrintOut( $elm, "initEvent", JSONresponse, patches );
19385		}
19386
19387		try {
19388			datasetCache[ dsName ] = JSONresponse;
19389		} catch ( error ) {
19390			return;
19391		}
19392		datasetCacheSettings[ dsName ] = settings;
19393
19394		if ( isReloading ) {
19395			elm.removeAttribute( reloadFlag );
19396			i_cache = dsPostponePatches[ dsName ];
19397			if ( i_cache ) {
19398				$elm.trigger( i_cache );
19399			}
19400		}
19401
19402		if ( !settings.wait && dsDelayed[ dsName ] ) {
19403			backlog = dsDelayed[ dsName ];
19404			i_len = backlog.length;
19405			for ( i = 0; i !== i_len; i += 1 ) {
19406				i_cache = backlog[ i ];
19407				selector = i_cache.selector;
19408				if ( selector.length ) {
19409					try {
19410						resultSet = jsonpointer.get( JSONresponse, selector );
19411					} catch  ( e ) {
19412						throw dsName + " - JSON selector not found: " + selector;
19413					}
19414				} else {
19415					resultSet = JSONresponse;
19416				}
19417				$( "#" + i_cache.callerId ).trigger( {
19418					type: "json-fetched.wb",
19419					fetch: {
19420						response: resultSet,
19421						status: "200",
19422						refId: i_cache.refId,
19423						xhr: null
19424					}
19425				}, this );
19426			}
19427		}
19428
19429		// Identify that initialization has completed
19430		wb.ready( $elm, componentName );
19431	}
19432} );
19433
19434// Apply patches to a preloaded JSON data
19435$document.on( patchesEvent, selector, function( event ) {
19436	var elm = event.target,
19437		$elm = $( elm ),
19438		patches = event.patches,
19439		filterPath = event.fpath,
19440		filterTrueness = event.filter || [],
19441		filterFaslseness = event.filternot || [],
19442		isCumulative = !!event.cumulative,
19443		settings,
19444		dsName,
19445		dsJSON, resultSet,
19446		delayedLst,
19447		i, i_len, i_cache, pntrSelector;
19448
19449	if ( elm === event.currentTarget && Array.isArray( patches ) ) {
19450		settings = wb.getData( $elm, componentName );
19451
19452		if ( !settings ) {
19453			return true;
19454		}
19455		dsName = "[" + settings.name + "]";
19456
19457		// Check if the patches need to be hold until the next json-fetch event
19458		if ( elm.hasAttribute( reloadFlag ) ) {
19459			dsPostponePatches[ dsName ] = event;
19460			return true;
19461		}
19462
19463		if ( !dsDelayed[ dsName ] ) {
19464			throw "Applying patched on undefined dataset name: " + dsName;
19465		}
19466
19467		dsJSON = datasetCache[ dsName ];
19468		if ( !isCumulative ) {
19469			dsJSON = $.extend( true, ( Array.isArray( dsJSON ) ? [] : {} ), dsJSON );
19470		}
19471
19472		// Apply a filtering
19473		if ( filterPath ) {
19474			dsJSON = getPatchesToFilter( dsJSON, filterPath, filterTrueness, filterFaslseness );
19475		}
19476
19477		jsonpatch.apply( dsJSON, patches );
19478
19479		if ( settings.debug ) {
19480			debugPrintOut( $elm, "patchesEvent", dsJSON, patches );
19481		}
19482
19483		delayedLst = dsDelayed[ dsName ];
19484		i_len = delayedLst.length;
19485		for ( i = 0; i !== i_len; i += 1 ) {
19486			i_cache = delayedLst[ i ];
19487			pntrSelector = i_cache.selector;
19488			if ( pntrSelector.length ) {
19489				try {
19490					resultSet = jsonpointer.get( dsJSON, pntrSelector );
19491				} catch  ( e ) {
19492					throw dsName + " - JSON selector not found: " + pntrSelector;
19493				}
19494			} else {
19495				resultSet = dsJSON;
19496			}
19497			$( "#" + i_cache.callerId ).trigger( {
19498				type: "json-fetched.wb",
19499				fetch: {
19500					response: resultSet,
19501					status: "200",
19502					refId: i_cache.refId,
19503					xhr: null
19504				}
19505			}, this );
19506		}
19507	}
19508} );
19509
19510
19511// Used by the JSON-fetch plugin for when trying fetching a resource that is mapped a dataset name
19512$document.on( postponeEvent, function( event ) {
19513	var jsonPostpone = event.postpone,
19514		dsName = jsonPostpone.dsname,
19515		callerId = jsonPostpone.callerId,
19516		refId = jsonPostpone.refId,
19517		selector = jsonPostpone.selector,
19518		resultSet;
19519
19520	if ( !dsDelayed[ dsName ] ) {
19521		dsDelayed[ dsName ] = [ ];
19522	}
19523
19524	// Add to the delayed updates list
19525	dsDelayed[ dsName ].push( {
19526		"callerId": callerId,
19527		"refId": refId,
19528		"selector": selector
19529	} );
19530
19531	// Send the data if the dataset is ready?
19532	if ( datasetCache[ dsName ] && !datasetCacheSettings[ dsName ].wait ) {
19533		resultSet = datasetCache[ dsName ];
19534		if ( selector.length ) {
19535			try {
19536				resultSet = jsonpointer.get( resultSet, selector );
19537			} catch  ( e ) {
19538				throw dsName + " - JSON selector not found: " + selector;
19539			}
19540		}
19541		$( "#" + callerId ).trigger( {
19542			type: "json-fetched.wb",
19543			fetch: {
19544				response: resultSet,
19545				status: "200",
19546				refId: refId,
19547				xhr: null
19548			}
19549		}, this );
19550	}
19551
19552} );
19553
19554/*
19555 * Integration with wb-fieldflow
19556 *
19557 */
19558function pushData( $elm, prop, data, reset ) {
19559	var dtCache = $elm.data( prop );
19560	if ( !dtCache || reset ) {
19561		dtCache = [];
19562	}
19563	dtCache.push( data );
19564	return $elm.data( prop, dtCache );
19565}
19566
19567// Fieldflow "op" action
19568$document.on( "op.action.wb-fieldflow", ".wb-fieldflow", function( event, data ) {
19569
19570	if ( !data.op ) {
19571		return;
19572	}
19573
19574	// Postpone the event for form submission
19575	data.preventSubmit = true;
19576	pushData( $( data.provEvt ), "wb-fieldflow-submit", data );
19577} );
19578
19579// Fieldflow "op" submit
19580$document.on( "op.submit.wb-fieldflow", ".wb-fieldflow", function( event, data ) {
19581
19582	// Get the hbs Plugin
19583	var op = data.op,
19584		source = data.source,
19585		ops;
19586
19587	if ( !op ) {
19588		return true;
19589	}
19590
19591	if ( !Array.isArray( op ) ) {
19592		ops = [];
19593		ops.push( op );
19594	} else {
19595		ops = op;
19596	}
19597
19598	$( source ).trigger( {
19599		type: "patches.wb-jsonmanager",
19600		patches: ops
19601	} );
19602} );
19603
19604// Bind the init event of the plugin
19605$document.on( "timerpoke.wb " + initEvent, selector, init );
19606
19607
19608// Add the timer poke to initialize the plugin
19609wb.add( selector );
19610
19611} )( jQuery, window, wb );
19612
19613/**
19614 * @title WET-BOEW wb-postback
19615 * @overview This plugin implements AJAX request for form data to submit on same page without refresh
19616 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
19617 * @author @masterbee, @namjohn920, @GormFrank
19618 **/
19619( function( $, wb ) {
19620"use strict";
19621
19622var $document = wb.doc,
19623	componentName = "wb-postback",
19624	selector = "." + componentName,
19625	initEvent = "wb-init" + selector,
19626	failEvent = "fail" + selector,
19627	successEvent = "success" + selector,
19628	defaults = {},
19629
19630	init = function( event ) {
19631		var elm = wb.init( event, componentName, selector );
19632
19633		if ( elm ) {
19634			var $elm = $( elm ),
19635				settings = $.extend(
19636					true,
19637					{},
19638					defaults,
19639					wb.getData( $elm, componentName )
19640				),
19641				multiple = typeof $elm.data( componentName + "-multiple" ) !== "undefined",
19642				classToggle = settings.toggle || "hide",
19643				selectorSuccess = settings.success,
19644				selectorFailure = settings.failure || selectorSuccess;
19645			const attrBlocked = "data-wb-blocked",
19646				attrSending = "data-wb-sending";
19647
19648			elm.addEventListener( "submit", function( e ) {
19649
19650				// Prevent regular form submit
19651				e.preventDefault();
19652
19653				//Check if the form use the validation plugin
19654				if ( elm.parentElement.classList.contains( "wb-frmvld" ) ) {
19655
19656					// Block invalid forms and allow valid ones
19657					if ( !$elm.valid() ) {
19658						$( this ).attr( attrBlocked, "true" );
19659					} else {
19660						$( this ).removeAttr( attrBlocked );
19661					}
19662				}
19663
19664				// Submit the form unless it's blocked or currently being sent
19665				if ( !$( this ).attr( attrBlocked ) && !$( this ).attr( attrSending ) ) {
19666					var data = $elm.serializeArray(),
19667						btn = e.submitter,
19668						$selectorSuccess = $( selectorSuccess ),
19669						$selectorFailure = $( selectorFailure );
19670
19671					// Indicate that the form is currently being sent (to prevent multiple submissions in parallel)
19672					$( this ).attr( attrSending, true );
19673
19674					// If the submit button contains a variable, add it to the form's paramaters
19675					// Note: Submitting a form via Enter will act as if the FIRST submit button was pressed. Therefore, that button's variable will be added (as opposed to nothing). This is in line with default form submission behaviour.
19676					if ( btn && btn.name ) {
19677						data.push( { name: btn.name, value: btn.value } );
19678					}
19679
19680					// Hide feedback messages
19681					$selectorFailure.addClass( classToggle );
19682					$selectorSuccess.addClass( classToggle );
19683
19684					// Send the form through ajax and ignore the response body.
19685					$.ajax( {
19686						type: this.method,
19687						url: this.action,
19688						data: $.param( data )
19689					} )
19690						.done( function() {
19691							$elm.trigger( successEvent );
19692							$selectorSuccess.removeClass( classToggle );
19693						} )
19694						.fail( function( response ) {
19695							$elm.trigger( failEvent, response );
19696							$selectorFailure.removeClass( classToggle );
19697						} )
19698						.always( function() {
19699
19700							// Hide the form unless multiple submits are allowed
19701							if ( !multiple ) {
19702								$elm.addClass( classToggle );
19703							}
19704
19705							// Remove the sending indicator now that submission is fully complete (i.e. HTTP response code has been received)
19706							$elm.removeAttr( attrSending );
19707						} );
19708				}
19709			} );
19710
19711			wb.ready( $( elm ), componentName );
19712		}
19713	};
19714
19715// Bind the init event of the plugin
19716$document.on( "timerpoke.wb " + initEvent, selector, init );
19717
19718// Add the timer poke to initialize the plugin
19719wb.add( selector );
19720
19721} )( jQuery, wb );
19722
19723/**
19724 * @title WET-BOEW Randomize
19725 * @overview This plugin randomly picks one of the child component to be shown on the browser.
19726 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
19727 * @author @masterbee @namjohn920
19728 */
19729( function( $, window, wb ) {
19730"use strict";
19731
19732var $document = wb.doc,
19733	componentName = "wb-randomize",
19734	selector = "[data-wb-randomize]",
19735	initEvent = "wb-init" + selector,
19736	defaults = {},
19737
19738	init = function( event ) {
19739		var elm = wb.init( event, componentName, selector ),
19740			$elm, settings, $selectedElm, valuesList;
19741
19742		if ( elm ) {
19743			$elm = $( elm );
19744			settings = $.extend(
19745				true,
19746				{},
19747				defaults,
19748				window[ componentName ],
19749				wb.getData( $elm, componentName )
19750			);
19751
19752			if ( settings.attribute ) {
19753				if ( settings.values && Array.isArray( settings.values ) ) {
19754					valuesList = settings.values;
19755					shuffleArray( valuesList );
19756					elm.setAttribute( settings.attribute, valuesList[ 0 ] );
19757				} else {
19758					throw componentName + ": You must define the property \"values\" to an array of str
19758ings when \"attribute\" property is defined.";
19759				}
19760			} else {
19761				$selectedElm = settings.selector ? $( settings.selector, $elm ) : $elm.children();
19762
19763				if ( !$selectedElm.length ) {
19764					throw componentName + " selector setting is invalid or no children";
19765				}
19766
19767				if ( settings.shuffle ) {
19768					$selectedElm = wb.shuffleDOM( $selectedElm );
19769				}
19770
19771				if ( settings.toggle ) {
19772					$selectedElm = wb.pickElements( $selectedElm, settings.number );
19773					$selectedElm.toggleClass( settings.toggle );
19774				}
19775			}
19776
19777			wb.ready( $elm, componentName );
19778		}
19779	},
19780
19781	shuffleArray = function( array ) {
19782		for ( let i = array.length - 1; i > 0; i-- ) {
19783			const j = Math.floor( Math.random() * ( i + 1 ) );
19784			[ array[ i ], array[ j ] ] = [ array[ j ], array[ i ] ];
19785		}
19786	};
19787
19788// Bind the init event of the plugin
19789$document.on( "timerpoke.wb " + initEvent, selector, init );
19790
19791// Add the timer poke to initialize the plugin
19792wb.add( selector );
19793
19794} )( jQuery, window, wb );
19795
19796/**
19797 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW)
19798 * @title Zebra
19799 * @overview Apply Zebra stripping on a complex data table and simulate column hovering effect
19800 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html
19801 * @author @duboisp
19802 *
19803 */
19804( function( $, window, document, wb ) {
19805"use strict";
19806
19807/**
19808 * Variable and function definitions.
19809 * These are global to the plugin - meaning that they will be initialized once per page,
19810 * not once per instance of plugin on the page. So, this is a good place to define
19811 * variables that are common to all instances of the plugin on a page.
19812 */
19813var componentName = "wb-zebra",
19814	selector = "." + componentName,
19815	hoverColClass = componentName + "-col-hover",
19816	selectorHoverCol = "." + hoverColClass + " td, " + hoverColClass + " th",
19817	initEvent = "wb-init" + selector,
19818	tableParsingEvent = "passiveparse.wb-tableparser",
19819	tableParsingCompleteEvent = "parsecomplete.wb-tableparser",
19820	$document = wb.doc,
19821	idCount = 0,
19822	i18n, i18nText,
19823
19824	/**
19825	 * Main Entry function to apply the complex zebra stripping
19826	 * @method zebraTable
19827	 * @param {jQuery DOM element} $elm table element use to apply complex zebra stripping
19828	 */
19829	zebraTable = function( $elm ) {
19830		var i, iLength, tblGroup,
19831
19832			// Cache the table parsed results
19833			tblparser = $elm.data().tblparser;
19834
19835		function addCellClass( arr, className ) {
19836			var i, iLength;
19837
19838			for ( i = 0, iLength = arr.length; i !== iLength; i += 1 ) {
19839				$( arr[ i ].elem ).addClass( className );
19840			}
19841		}
19842
19843		// Key Cell
19844		if ( tblparser.keycell ) {
19845			addCellClass( tblparser.keycell, "wb-cell-key" );
19846		}
19847
19848		// Description Cell
19849		if ( tblparser.desccell ) {
19850			addCellClass( tblparser.desccell, "wb-cell-desc" );
19851		}
19852
19853		// Layout Cell
19854		if ( tblparser.layoutCell ) {
19855			addCellClass( tblparser.layoutCell, "wb-cell-layout" );
19856		}
19857
19858		// Summary Row Group
19859		if ( tblparser.lstrowgroup ) {
19860			for ( i = 0, iLength = tblparser.lstrowgroup.length; i !== iLength; i += 1 ) {
19861				tblGroup = tblparser.lstrowgroup[ i ];
19862
19863				// Add a class to the row
19864				if ( tblGroup.type === 3 || tblGroup.row[ 0 ].type === 3 ) {
19865					$( tblGroup.elem ).addClass( "wb-group-summary" );
19866				}
19867			}
19868		}
19869
19870		// Summary Column Group
19871		if ( tblparser.colgroup ) {
19872			for ( i = 0, iLength = tblparser.colgroup.length; i !== iLength; i += 1 ) {
19873				tblGroup = tblparser.colgroup[ i ];
19874
19875				// Add a class to the row
19876				if ( tblGroup.type === 3 ) {
19877					$( tblGroup.elem ).addClass( "wb-group-summary" );
19878				}
19879			}
19880		}
19881
19882		// Identify that initialization has completed
19883		wb.ready( $elm, componentName );
19884	},
19885
19886	/**
19887	 * @method init
19888	 * @param {jQuery Event} event Event that triggered the function call
19889	 */
19890	init = function( event ) {
19891
19892		// Start initialization
19893		// returns DOM object = proceed with init
19894		// returns undefined = do not proceed with init (e.g., already initialized)
19895		var elm = wb.init( event, componentName, selector ),
19896			deps = [
19897				"site!deps/tableparser" + wb.getMode() + ".js"
19898			],
19899			elmId;
19900
19901		if ( elm ) {
19902			elmId = elm.id;
19903
19904			// Ensure there is a unique id on the element
19905			if ( !elmId ) {
19906				elmId = componentName + "-id-" + idCount;
19907				idCount += 1;
19908				elm.id = elmId;
19909			}
19910
19911			// Only initialize the i18nText once
19912			if ( !i18nText ) {
19913				i18n = wb.i18n;
19914				i18nText = {
19915					tableMention: i18n( "hyphen" ) + i18n( "tbl-txt" ),
19916					tableFollowing: i18n( "hyphen" ) + i18n( "tbl-dtls" )
19917				};
19918			}
19919
19920			// Load the required dependencies
19921			Modernizr.load( {
19922
19923				// For loading multiple dependencies
19924				load: deps,
19925				complete: function() {
19926
19927					// Let's parse the table
19928					$( "#" + elmId ).trigger( tableParsingEvent );
19929				}
19930			} );
19931		}
19932	};
19933
19934// Bind the init event of the plugin
19935$document.on( "timerpoke.wb " + initEvent + " " + tableParsingCompleteEvent, selector, function( event ) {
19936	var eventTarget = event.target;
19937
19938	switch ( event.type ) {
19939
19940	/*
19941	 * Init
19942	 */
19943	case "timerpoke":
19944	case "wb-init":
19945		init( event );
19946		break;
19947
19948	/*
19949	 * Data table parsed
19950	 */
19951	case "parsecomplete":
19952		if ( event.currentTarget === eventTarget ) {
19953			zebraTable( $( eventTarget ) );
19954		}
19955		break;
19956	}
19957
19958	/*
19959	 * Since we are working with events we want to ensure that we are being passive about our control,
19960	 * so returning true allows for events to always continue
19961	 */
19962	return true;
19963} );
19964
19965// Applying the hover, Simulate Column Hovering Effect
19966$document.on( "mouseenter focusin", selectorHoverCol, function( event ) {
19967	var tblparserCell = $( event.currentTarget ).data().tblparser;
19968
19969	if ( tblparserCell.col && tblparserCell.col.elem ) {
19970		$( tblparserCell.col.elem ).addClass( "table-hover" );
19971	}
19972} );
19973
19974// Removing the hover, Simulate Column Hovering Effect
19975$document.on( "mouseleave focusout", selectorHoverCol, function( event ) {
19976	var tblparserCell = $( event.currentTarget ).data().tblparser;
19977
19978	if ( tblparserCell.col && tblparserCell.col.elem ) {
19979		$( tblparserCell.col.elem ).removeClass( "table-hover" );
19980	}
19981} );
19982
19983// Add the timer poke to initialize the plugin
19984wb.add( selector );
19985
19986} )( jQuery, window, document, wb );

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.