PageSourceSearch

https://signup.eqtiming.com/_Plugins/Q3Webshop/Templates/Q3Standar…/cookieconsent_plugin.js?1750175718

js eqtiming.com collected 2026-09-24 09:25:32 UTC 69,917 bytes, 2,351 lines download raw bytes

1/*!
2 * CookieConsent v2.9.2
3 * https://www.github.com/orestbida/cookieconsent
4 * Author Orest Bida
5 * Released under the MIT License
6 */
7(function ()
8{
9	'use strict';
10	/**
11	 * @param {HTMLElement} [root] - [optional] element where the cookieconsent will be appended
12	 * @returns {Object} cookieconsent object with API
13	 */
14	var CookieConsent = function (root)
15	{
16
17		/**
18		 * CHANGE THIS FLAG FALSE TO DISABLE console.log()
19		 */
20		var ENABLE_LOGS = false;
21		var OPT_IN = 'opt-in';
22		var OPT_OUT = 'opt-out';
23
24		var _config = {
25			'mode': OPT_OUT,                         // 'opt-in', 'opt-out'
26			'current_lang': 'en',
27			'auto_language': null,
28			'autorun': true,                          // run as soon as loaded
29			'page_scripts': true,
30			'hide_from_bots': true,
31			'cookie_name': 'cc_cookie',
32			'cookie_expiration': 182,                 // default: 6 months (in days)
33			'cookie_domain': location.hostname,       // default: current domain
34			'cookie_path': '/',
35			'cookie_same_site': 'Lax',
36			'use_rfc_cookie': false,
37			'autoclear_cookies': true,
38			'revision': 0,
39			'script_selector': 'data-cookiecategory'
40		};
41
42		var
43			/**
44			 * Object which holds the main methods/API (.show, .run, ...)
45			 */
46			_cookieconsent = {},
47
48			/**
49			 * Global user configuration object
50			 */
51			user_config,
52
53			/**
54			 * Internal state variables
55			 */
56			saved_cookie_content = {},
57			cookie_data = null,
58
59			/**
60			 * @type {Date}
61			 */
62			consent_date,
63
64			/**
65			 * @type {Date}
66			 */
67			last_consent_update,
68
69			/**
70			 * @type {string}
71			 */
72			consent_uuid,
73
74			/**
75			 * @type {boolean}
76			 */
77			invalid_consent = true,
78
79			consent_modal_exists = false,
80			consent_modal_visible = false,
81
82			settings_modal_visible = false,
83
84			/**
85			 * @type {HTMLElement[]}
86			 */
87			focusable_edges,
88
89			/**
90			 * @type {HTMLDivElement}
91			 */
92			focused_modal,
93
94			/**
95			 * @type {HTMLSpanElement}
96			 */
97			cmFocusSpan,
98
99			/**
100			 * @type {HTMLSpanElement}
101			 */
102			smFocusSpan,
103
104			all_table_headers,
105
106			/**
107			 * @type {{}[]}
108			 */
109			all_blocks,
110
111			// Helper callback functions
112			// (avoid calling "user_config['onAccept']" all the time)
113			onAccept,
114			onChange,
115			onFirstAction,
116
117			revision_enabled = false,
118			valid_revision = true,
119			revision_message = '',
120
121			// State variables for the autoclearCookies function
122			changed_settings = [],
123			reload_page = false;
124
125		/**
126		 * Accept type:
127		 *  - "all"
128		 *  - "necessary"
129		 *  - "custom"
130		 * @type {string}
131		 */
132		var accept_type;
133
134		/**
135		 * Contains all accepted categories
136		 * @type {string[]}
137		 */
138		var accepted_categories = [];
139
140		/**
141		 * Contains all non-accepted (rejected) categories
142		 * @type {string[]}
143		 */
144		var rejected_categories = [];
145
146		/**
147		 * Contains all categories enabled by default
148		 * @type {string[]}
149		 */
150		var default_enabled_categories = [];
151
152		// Don't run plugin (to avoid indexing its text content) if bot detected
153		var is_bot = false;
154
155		/**
156		 * Save reference to the last focused element on the page
157		 * (used later to restore focus when both modals are closed)
158		 */
159		var last_elem_before_modal;
160		var last_consent_modal_btn_focus;
161
162		/**
163		 * Both of the arrays below have the same structure:
164		 * [0] => holds reference to the FIRST focusable element inside modal
165		 * [1] => holds reference to the LAST focusable element inside modal
166		 */
167		var consent_modal_focusable = [];
168		var settings_modal_focusable = [];
169
170		/**
171		 * Keep track of enabled/disabled categories
172		 * @type {boolean[]}
173		 */
174		var toggle_states = [];
175
176		/**
177		 * Stores all available categories
178		 * @type {string[]}
179		 */
180		var all_categories = [];
181
182		/**
183		 * Keep track of readonly toggles
184		 * @type {string[]}
185		 */
186		var readonly_categories = [];
187
188		/**
189		 * Pointers to main dom elements (to avoid retrieving them later using document.getElementById)
190		 */
191		var
192			/** @type {HTMLElement} */ html_dom = document.documentElement,
193			/** @type {HTMLElement} */ main_container,
194			/** @type {HTMLElement} */ all_modals_container,
195
196			/** @type {HTMLElement} */ consent_modal,
197			/** @type {HTMLElement} */ consent_modal_title,
198			/** @type {HTMLElement} */ consent_modal_description,
199			/** @type {HTMLElement} */ consent_primary_btn,
200			/** @type {HTMLElement} */ consent_secondary_btn,
201			/** @type {HTMLElement} */ consent_buttons,
202			/** @type {HTMLElement} */ consent_modal_inner,
203
204			/** @type {HTMLElement} */ settings_container,
205			/** @type {HTMLElement} */ settings_inner,
206			/** @type {HTMLElement} */ settings_title,
207			/** @type {HTMLElement} */ settings_close_btn,
208			/** @type {HTMLElement} */ settings_blocks,
209			/** @type {HTMLElement} */ new_settings_blocks,
210			/** @type {HTMLElement} */ settings_buttons,
211			/** @type {HTMLElement} */ settings_save_btn,
212			/** @type {HTMLElement} */ settings_accept_all_btn,
213			/** @type {HTMLElement} */ settings_reject_all_btn;
214
215		/**
216		 * Update config settings
217		 * @param {Object} user_config
218		 */
219		var _setConfig = function (_user_config)
220		{
221
222			/**
223			 * Make user configuration globally available
224			 */
225			user_config = _user_config;
226
227			_log("CookieConsent [CONFIG]: received_config_settings ", user_config);
228
229			if (typeof user_config['cookie_expiration'] === "number")
230				_config.cookie_expiration = user_config['cookie_expiration'];
231
232			if (typeof user_config['cookie_necessary_only_expiration'] === "number")
233				_config.cookie_necessary_only_expiration = user_config['cookie_necessary_only_expiration'];
234
235			if (typeof user_config['autorun'] === "boolean")
236				_config.autorun = user_config['autorun'];
237
238			if (typeof user_config['cookie_domain'] === "string")
239				_config.cookie_domain = user_config['cookie_domain'];
240
241			if (typeof user_config['cookie_same_site'] === "string")
242				_config.cookie_same_site = user_config['cookie_same_site'];
243
244			if (typeof user_config['cookie_path'] === "string")
245				_config.cookie_path = user_config['cookie_path'];
246
247			if (typeof user_config['cookie_name'] === "string")
248				_config.cookie_name = user_config['cookie_name'];
249
250			if (typeof user_config['onAccept'] === "function")
251				onAccept = user_config['onAccept'];
252
253			if (typeof user_config['onFirstAction'] === "function")
254				onFirstAction = user_config['onFirstAction'];
255
256			if (typeof user_config['onChange'] === "function")
257				onChange = user_config['onChange'];
258
259			if (user_config['mode'] === OPT_OUT)
260				_config.mode = OPT_OUT;
261
262			if (typeof user_config['revision'] === "number") {
263				user_config['revision'] > -1 && (_config.revision = user_config['revision']);
264				revision_enabled = true;
265			}
266
267			if (typeof user_config['autoclear_cookies'] === "boolean")
268				_config.autoclear_cookies = user_config['autoclear_cookies'];
269
270			if (user_config['use_rfc_cookie'] === true)
271				_config.use_rfc_cookie = true;
272
273			if (typeof user_config['hide_from_bots'] === "boolean") {
274				_config.hide_from_bots = user_config['hide_from_bots'];
275			}
276
277			if (_config.hide_from_bots) {
278				is_bot = navigator &&
279					((navigator.userAgent && /bot|crawl|spider|slurp|teoma/i.test(navigator.userAgent)) || navigator.webdriver);
280			}
281
282			_config.page_scripts = user_config['page_scripts'] === true;
283
284			if (user_config['auto_language'] === 'browser' || user_config['auto_language'] === true) {
285				_config.auto_language = 'browser';
286			} else if (user_config['auto_language'] === 'document') {
287				_config.auto_language = 'document';
288			}
289
290			_log("CookieConsent [LANG]: auto_language strategy is '" + _config.auto_language + "'");
291
292			_config.current_lang = _resolveCurrentLang(user_config.languages, user_config['current_lang']);
293		}
294
295		/**
296		 * Add an onClick listeners to all html elements with data-cc attribute
297		 */
298		var _addDataButtonListeners = function (elem)
299		{
300
301			var _a = 'accept-';
302
303			var show_settings = _getElements('c-settings');
304			var accept_all = _getElements(_a + 'all');
305			var accept_necessary = _getElements(_a + 'necessary');
306			var accept_custom_selection = _getElements(_a + 'custom');
307
308			for (var i = 0; i < show_settings.length; i++) {
309				show_settings[i].setAttribute('aria-haspopup', 'dialog');
310				_addEvent(show_settings[i], 'click', function (event)
311				{
312					event.preventDefault();
313					_cookieconsent.showSettings(0);
314				});
315			}
316
317			for (i = 0; i < accept_all.length; i++) {
318				_addEvent(accept_all[i], 'click', function (event)
319				{
320					_acceptAction(event, 'all');
321				});
322			}
323
324			for (i = 0; i < accept_custom_selection.length; i++) {
325				_addEvent(accept_custom_selection[i], 'click', function (event)
326				{
327					_acceptAction(event);
328				});
329			}
330
331			for (i = 0; i < accept_necessary.length; i++) {
332				_addEvent(accept_necessary[i], 'click', function (event)
333				{
334					_acceptAction(event, []);
335				});
336			}
337
338			/**
339			 * Return all elements with given data-cc role
340			 * @param {string} data_role
341			 * @returns {NodeListOf<Element>}
342			 */
343			function _getElements(data_role)
344			{
345				return (elem || document).querySelectorAll('[data-cc="' + data_role + '"]');
346			}
347
348			/**
349			 * Helper function: accept and then hide modals
350			 * @param {PointerEvent} e source event
351			 * @param {string} [accept_type]
352			 */
353			function _acceptAction(e, accept_type)
354			{
355				e.preventDefault();
356				_cookieconsent.accept(accept_type);
357				_cookieconsent.hideSettings();
358				_cookieconsent.hide();
359			}
360		}
361
362		/**
363		 * Get a valid language (at least 1 must be defined)
364		 * @param {string} lang - desired language
365		 * @param {{}} all_languages - all defined languages
366		 * @returns {string} validated language
367		 */
368		var _getValidatedLanguage = function (lang, all_languages)
369		{
370			if (all_languages.hasOwnProperty(lang)) {
371				return lang;
372			} else if (_getKeys(all_languages).length > 0) {
373				if (all_languages.hasOwnProperty(_config.current_lang)) {
374					return _config.current_lang;
375				} else {
376					return _getKeys(all_languages)[0];
377				}
378			}
379		}
380
381		/**
382		 * Save reference to first and last focusable elements inside each modal
383		 * to prevent losing focus while navigating with TAB
384		 */
385		var _getModalFocusableData = function ()
386		{
387
388			/**
389			 * Note: any of the below focusable elements, which has the attribute tabindex="-1" AND is either
390			 * the first or last element of the modal, won't receive focus during "open/close" modal
391			 */
392			var allowed_focusable_types = ['[href]', 'button', 'input', 'details', '[tabindex="0"]'];
393
394			function _getAllFocusableElements(modal, _array)
395			{
396
397				// ie might throw exception due to complex unsupported selector => a:not([tabindex="-1"])
398				try {
399					var focusable_elems = modal.querySelectorAll(allowed_focusable_types.join(':not([tabindex="-1"]), '));
400				} catch (e) {
401					return modal.querySelectorAll(allowed_focusable_types.join(', '));
402				}
403
404				/**
405				 * Save first and last elements (used to lock/trap focus inside modal)
406				 */
407				_array[0] = focusable_elems[0];
408				_array[1] = focusable_elems[focusable_elems.length - 1];
409			}
410
411			/**
412			 * Get settings modal'S all focusable elements
413			 * Save first and last elements (used to lock/trap focus inside modal)
414			 */
415			_getAllFocusableElements(settings_inner, settings_modal_focusable);
416
417			/**
418			 * If consent modal exists, do the same
419			 */
420			if (consent_modal_exists) {
421				_getAllFocusableElements(consent_modal, consent_modal_focusable);
422			}
423		}
424
425		var _createConsentModal = function (lang)
426		{
427
428			if (user_config['force_consent'] === true)
429				_addClass(html_dom, 'force--consent');
430
431			// Create modal if it doesn't exist
432			if (!consent_modal) {
433
434				consent_modal = _createNode('div');
435				var consent_modal_inner_inner = _createNode('div');
436				var overlay = _createNode('div');
437
438				consent_modal.id = 'cm';
439				consent_modal_inner_inner.id = 'c-inr-i';
440				overlay.id = 'cm-ov';
441
442				consent_modal.tabIndex = -1;
443				consent_modal.setAttribute('role', 'dialog');
444				consent_modal.setAttribute('aria-modal', 'true');
445				consent_modal.setAttribute('aria-hidden', 'false');
446				consent_modal.setAttribute('aria-labelledby', 'c-ttl');
447				consent_modal.setAttribute('aria-describedby', 'c-txt');
448
449				// Append consent modal to main container
450				all_modals_container.appendChild(consent_modal);
451				all_modals_container.appendChild(overlay);
452
453				/**
454				 * Make modal by default hidden to prevent weird page jumps/flashes (shown only once css is loaded)
455				 */
456				consent_modal.style.visibility = overlay.style.visibility = "hidden";
457				overlay.style.opacity = 0;
458			}
459
460			// Use insertAdjacentHTML instead of innerHTML
461			var consent_modal_title_value = user_config.languages[lang]['consent_modal']['title'];
462
463			// Add title (if valid)
464			if (consent_modal_title_value) {
465
466				if (!consent_modal_title) {
467					consent_modal_title = _createNode('h2');
468					consent_modal_title.id = 'c-ttl';
469					consent_modal_inner_inner.appendChild(consent_modal_title);
470				}
471
472				consent_modal_title.innerHTML = consent_modal_title_value;
473			}
474
475			var description = user_config.languages[lang]['consent_modal']['description'];
476
477			if (revision_enabled) {
478				if (!valid_revision) {
479					description = description.replace("{{revision_message}}", revision_message || user_config.languages[lang]['consent_modal']['revision_message'] || "");
480				} else {
481					description = description.replace("{{revision_message}}", "");
482				}
483			}
484
485			if (!consent_modal_description) {
486				consent_modal_description = _createNode('div');
487				consent_modal_description.id = 'c-txt';
488				consent_modal_inner_inner.appendChild(consent_modal_description);
489			}
490
491			// Set description content
492			consent_modal_description.innerHTML = description;
493
494			var primary_btn_data = user_config.languages[lang]['consent_modal']['primary_btn'],   // accept current selection
495				secondary_btn_data = user_config.languages[lang]['consent_modal']['secondary_btn'];
496
497			// Add primary button if not falsy
498			if (primary_btn_data) {
499
500				if (!consent_primary_btn) {
501					consent_primary_btn = _createNode('button');
502					consent_primary_btn.id = 'c-p-bn';
503					consent_primary_btn.className = "c-bn";
504					consent_primary_btn.appendChild(generateFocusSpan(1))
505
506					var _accept_type;
507
508					if (primary_btn_data['role'] === 'accept_all')
509						_accept_type = 'all'
510
511					_addEvent(consent_primary_btn, "click", function ()
512					{
513						_cookieconsent.hide();
514						_log("CookieConsent [ACCEPT]: cookie_consent was accepted!");
515						_cookieconsent.accept(_accept_type);
516					});
517				}
518
519				consent_primary_btn.firstElementChild.innerHTML = user_config.languages[lang]['consent_modal']['primary_btn']['text'];
520			}
521
522			// Add secondary button if not falsy
523			if (secondary_btn_data) {
524
525				if (!consent_secondary_btn) {
526					consent_secondary_btn = _createNode('button');
527					consent_secondary_btn.id = 'c-s-bn';
528					consent_secondary_btn.className = "c-bn c_link";
529					consent_secondary_btn.appendChild(generateFocusSpan(1))
530
531					if (secondary_btn_data['role'] === 'accept_necessary') {
532						_addEvent(consent_secondary_btn, 'click', function ()
533						{
534							_cookieconsent.hide();
535							_cookieconsent.accept([]); // accept necessary only
536						});
537					} else {
538						_addEvent(consent_secondary_btn, 'click', function ()
539						{
540							_cookieconsent.showSettings(0);
541						});
542					}
543				}
544
545				consent_secondary_btn.firstElementChild.innerHTML = user_config.languages[lang]['consent_modal']['secondary_btn']['text'];
546			}
547
548			// Swap buttons
549			var gui_options_data = user_config['gui_options'];
550
551			if (!consent_modal_inner) {
552				consent_modal_inner = _createNode('div');
553				consent_modal_inner.id = 'c-inr';
554
555				consent_modal_inner.appendChild(consent_modal_inner_inner);
556			}
557
558			if (!consent_buttons) {
559				consent_buttons = _createNode('div');
560				consent_buttons.id = "c-bns";
561
562				if (gui_options_data && gui_options_data['consent_modal'] && gui_options_data['consent_modal']['swap_buttons'] === true) {
563					secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn);
564					primary_btn_data && consent_buttons.appendChild(consent_primary_btn);
565					consent_buttons.className = 'swap';
566				} else {
567					primary_btn_data && consent_buttons.appendChild(consent_primary_btn);
568					secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn);
569				}
570
571				(primary_btn_data || secondary_btn_data) && consent_modal_inner.appendChild(consent_buttons);
572				consent_modal.appendChild(consent_modal_inner);
573			}
574
575			consent_modal_exists = true;
576
577			_addDataButtonListeners(consent_modal_inner);
578		}
579
580		var _createSettingsModal = function (lang)
581		{
582
583			/**
584			 * Create all consent_modal elements
585			 */
586			if (!settings_container) {
587				settings_container = _createNode('div');
588				settings_container.tabIndex = -1;
589				var settings_container_valign = _createNode('div');
590				var settings = _createNode('div');
591				var settings_container_inner = _createNode('div');
592				settings_inner = _createNode('div');
593				settings_title = _createNode('h2');
594				var settings_header = _createNode('div');
595				settings_close_btn = _createNode('button');
596				settings_close_btn.appendChild(generateFocusSpan(2));
597				var settings_close_btn_container = _createNode('div');
598				settings_blocks = _createNode('div');
599				var overlay = _createNode('div');
600
601				var clickedInsideModal = false;
602
603				_addEvent(settings_container, 'mouseup', function (e)
604				{
605
606					/**
607					 * Hide modal if mouse pointer is outside modal
608					 */
609					if (
610						settings_modal_visible
611						&& !clickedInsideModal
612						&& !settings_inner.contains(e.target)
613					) {
614						_cookieconsent.hideSettings();
615					}
616
617				});
618
619				_addEvent(settings_container, 'mousedown', function (e)
620				{
621
622					/**
623					 * Check if click started inside modal or not
624					 */
625					if (settings_modal_visible) {
626						clickedInsideModal = settings_inner.contains(e.target);
627					}
628
629				});
630
631				/**
632				 * Set ids
633				 */
634				settings_container.id = 's-cnt';
635				settings_container_valign.id = "c-vln";
636				settings_container_inner.id = "c-s-in";
637				settings.id = "cs";
638				settings_title.id = 's-ttl';
639				settings_inner.id = 's-inr';
640				settings_header.id = "s-hdr";
641				settings_blocks.id = 's-bl';
642				settings_close_btn.id = 's-c-bn';
643				overlay.id = 'cs-ov';
644				settings_close_btn_container.id = 's-c-bnc';
645				settings_close_btn.className = 'c-bn';
646
647				settings_container.setAttribute('role', 'dialog');
648				settings_container.setAttribute('aria-modal', 'true');
649				settings_container.setAttribute('aria-hidden', 'true');
650				settings_container.setAttribute('aria-labelledby', 's-ttl');
651				settings_container.style.visibility = overlay.style.visibility = "hidden";
652				overlay.style.opacity = 0;
653
654				settings_close_btn_container.appendChild(settings_close_btn);
655
656				// If 'esc' key is pressed inside settings_container div => hide settings
657				_addEvent(document, 'keydown', function (evt)
658				{
659					if (evt.keyCode === 27 && settings_modal_visible) {
660						_cookieconsent.hideSettings();
661					}
662				}, true);
663
664				_addEvent(settings_close_btn, 'click', function ()
665				{
666					_cookieconsent.hideSettings();
667				});
668			} else {
669				new_settings_blocks = _createNode('div');
670				new_settings_blocks.id = 's-bl';
671			}
672
673			var settings_modal_config = user_config.languages[lang]['settings_modal'];
674
675			// Add label to close button
676			settings_close_btn.setAttribute('aria-label', settings_modal_config['close_btn_label'] || 'Close');
677
678			all_blocks = settings_modal_config['blocks'];
679			all_table_headers = settings_modal_config['cookie_table_headers'];
680			var table_caption = settings_modal_config['cookie_table_caption'];
681
682			var n_blocks = all_blocks.length;
683
684			// Set settings modal title
685			settings_title.innerHTML = settings_modal_config['title'];
686
687			// Create settings modal content (blocks)
688			for (var i = 0; i < n_blocks; ++i) {
689
690				var title_data = all_blocks[i]['title'],
691					description_data = all_blocks[i]['description'],
692					toggle_data = all_blocks[i]['toggle'],
693					cookie_table_data = all_blocks[i]['cookie_table'],
694					remove_cookie_tables = user_config['remove_cookie_tables'] === true,
695					isExpandable = !!description_data || (!remove_cookie_tables && !!cookie_table_data);
696
697				// Create title
698				var block_section = _createNode('div');
699				var block_table_container = _createNode('div');
700
701				// Create description
702				if (description_data) {
703					var block_desc = _createNode('div');
704					block_desc.className = 'p';
705					block_desc.insertAdjacentHTML('beforeend', description_data);
706				}
707
708				var block_title_container = _createNode('div');
709				block_title_container.className = 'title';
710
711				block_section.className = 'c-bl';
712				block_table_container.className = 'desc';
713
714				// Create toggle if specified (opt in/out)
715				if (typeof toggle_data !== 'undefined') {
716
717					var accordion_id = "c-ac-" + i;
718
719					// Create button (to collapse/expand block description)
720					var block_title_btn = isExpandable ? _createNode('button') : _createNode('div');
721					var block_switch_label = _createNode('label');
722					var block_switch = _createNode('input');
723					var block_switch_span = _createNode('span');
724					var label_text_span = _createNode('span');
725
726					// These 2 spans will contain each 2 pseudo-elements to generate 'tick' and 'x' icons
727					var block_switch_span_on_icon = _createNode('span');
728					var block_switch_span_off_icon = _createNode('span');
729
730					block_title_btn.className = isExpandable ? 'b-tl exp' : 'b-tl';
731					block_switch_label.className = 'b-tg';
732					block_switch.className = 'c-tgl';
733					block_switch_span_on_icon.className = 'on-i';
734					block_switch_span_off_icon.className = 'off-i';
735					block_switch_span.className = 'c-tg';
736					label_text_span.className = "t-lb";
737
738					if (isExpandable) {
739						block_title_btn.setAttribute('aria-expanded', 'false');
740						block_title_btn.setAttribute('aria-controls', accordion_id);
741					}
742
743					block_switch.type = 'checkbox';
744					block_switch_span.setAttribute('aria-hidden', 'true');
745
746					var cookie_category = toggle_data.value;
747					block_switch.value = cookie_category;
748
749					label_text_span.textContent = title_data;
750					block_title_btn.insertAdjacentHTML('beforeend', title_data);
751
752					block_title_container.appendChild(block_title_btn);
753					block_switch_span.appendChild(block_switch_span_on_icon);
754					block_switch_span.appendChild(block_switch_span_off_icon);
755
756					/**
757					 * If consent is valid => retrieve category states from cookie
758					 * Otherwise use states defined in the user_config. object
759					 */
760					if (!invalid_consent) {
761						if (_inArray(saved_cookie_content['categories'], cookie_category) > -1) {
762							block_switch.checked = true;
763							!new_settings_blocks && toggle_states.push(true);
764						} else {
765							!new_settings_blocks && toggle_states.push(false);
766						}
767					} else if (toggle_data['enabled'] && _config.mode === OPT_OUT) {
768						block_switch.checked = true;
769						!new_settings_blocks && toggle_states.push(true);
770
771						/**
772						 * Keep track of categories enabled by default (useful when mode=='opt-out')
773						 */
774						!new_settings_blocks && default_enabled_categories.push(cookie_category);
775
776					} else {
777						!new_settings_blocks && toggle_states.push(false);
778					}
779
780					!new_settings_blocks && all_categories.push(cookie_category);
781
782					/**
783					 * Set toggle as readonly if true (disable checkbox)
784					 */
785					if (toggle_data['readonly']) {
786						block_switch.disabled = true;
787						_addClass(block_switch_span, 'c-ro');
788						!new_settings_blocks && readonly_categories.push(cookie_category);
789					}
790
791					_addClass(block_table_container, 'b-acc');
792					_addClass(block_title_container, 'b-bn');
793					_addClass(block_section, 'b-ex');
794
795					block_table_container.id = accordion_id;
796					block_table_container.setAttribute('aria-hidden', 'true');
797
798					block_switch_label.appendChild(block_switch);
799					block_switch_label.appendChild(block_switch_span);
800					block_switch_label.appendChild(label_text_span);
801					block_title_container.appendChild(block_switch_label);
802
803					/**
804					 * On button click handle the following :=> aria-expanded, aria-hidden and act class for current block
805					 */
806					isExpandable && (function (accordion, block_section, btn)
807					{
808						_addEvent(block_title_btn, 'click', function ()
809						{
810							if (!_hasClass(block_section, 'act')) {
811								_addClass(block_section, 'act');
812								btn.setAttribute('aria-expanded', 'true');
813								accordion.setAttribute('aria-hidden', 'false');
814							} else {
815								_removeClass(block_section, 'act');
816								btn.setAttribute('aria-expanded', 'false');
817								accordion.setAttribute('aria-hidden', 'true');
818							}
819						}, false);
820					})(block_table_container, block_section, block_title_btn);
821
822				} else {
823					/**
824					 * If block is not a button (no toggle defined),
825					 * create a simple div instead
826					 */
827					if (title_data) {
828						var block_title = _createNode('div');
829						block_title.className = 'b-tl';
830						block_title.setAttribute('role', 'heading');
831						block_title.setAttribute('aria-level', '3');
832						block_title.insertAdjacentHTML('beforeend', title_data);
833						block_title_container.appendChild(block_title);
834					}
835				}
836
837				title_data && block_section.appendChild(block_title_container);
838				description_data && block_table_container.appendChild(block_desc);
839
840				// if cookie table found, generate table for this block
841				if (!remove_cookie_tables && typeof cookie_table_data !== 'undefined') {
842					var tr_tmp_fragment = document.createDocumentFragment();
843
844					/**
845					 * Use custom table headers
846					 */
847					for (var p = 0; p < all_table_headers.length; ++p) {
848						// create new header
849						var th1 = _createNode('th');
850						var obj = all_table_headers[p];
851						th1.setAttribute('scope', 'col');
852
853						// get custom header content
854						if (obj) {
855							var new_column_key = obj && _getKeys(obj)[0];
856							th1.textContent = all_table_headers[p][new_column_key];
857							tr_tmp_fragment.appendChild(th1);
858						}
859					}
860
861					var tr_tmp = _createNode('tr');
862					tr_tmp.appendChild(tr_tmp_fragment);
863
864					// create table header & append fragment
865					var thead = _createNode('thead');
866					thead.appendChild(tr_tmp);
867
868					var block_table = _createNode('table');
869
870					if (table_caption) {
871						var caption = _createNode('caption');
872						caption.innerHTML = table_caption;
873						block_table.appendChild(caption);
874					}
875
876					// append header to table
877					block_table.appendChild(thead);
878
879					var tbody_fragment = document.createDocumentFragment();
880
881					// create table content
882					for (var n = 0; n < cookie_table_data.length; n++) {
883						var tr = _createNode('tr');
884
885						for (var g = 0; g < all_table_headers.length; ++g) {
886							// get custom header content
887							obj = all_table_headers[g];
888							if (obj) {
889								new_column_key = _getKeys(obj)[0];
890
891								var td_tmp = _createNode('td');
892
893								// Allow html inside table cells
894								td_tmp.insertAdjacentHTML('beforeend', cookie_table_data[n][new_column_key]);
895								td_tmp.setAttribute('data-column', obj[new_column_key]);
896
897								tr.appendChild(td_tmp);
898							}
899						}
900
901						tbody_fragment.appendChild(tr);
902					}
903
904					// append tbody_fragment to tbody & append the latter into the table
905					var tbody = _createNode('tbody');
906					tbody.appendChild(tbody_fragment);
907					block_table.appendChild(tbody);
908
909					block_table_container.appendChild(block_table);
910				}
911
912				/**
913				 * Append only if is either:
914				 * - togglable div with title
915				 * - a simple div with at least a title or description
916				 */
917				if (toggle_data && title_data || (!toggle_data && (title_data || description_data))) {
918					block_section.appendChild(block_table_container);
919
920					if (new_settings_blocks)
921						new_settings_blocks.appendChild(block_section);
922					else
923						settings_blocks.appendChild(block_section);
924				}
925			}
926
927			// Create settings buttons
928			if (!settings_buttons) {
929				settings_buttons = _createNode('div');
930				settings_buttons.id = 's-bns';
931			}
932
933			if (!settings_accept_all_btn) {
934				settings_accept_all_btn = _createNode('button');
935				settings_accept_all_btn.id = 's-all-bn';
936				settings_accept_all_btn.className = 'c-bn';
937				settings_buttons.appendChild(settings_accept_all_btn);
938
939				_addEvent(settings_accept_all_btn, 'click', function ()
940				{
941					_cookieconsent.accept('all');
942					_cookieconsent.hideSettings();
943					_cookieconsent.hide();
944				});
945			}
946
947			settings_accept_all_btn.innerHTML = settings_modal_config['accept_all_btn'];
948
949			var reject_all_btn_text = settings_modal_config['reject_all_btn'];
950
951			// Add third [optional] reject all button if provided
952			if (reject_all_btn_text) {
953
954				if (!settings_reject_all_btn) {
955					settings_reject_all_btn = _createNode('button');
956					settings_reject_all_btn.id = 's-rall-bn';
957					settings_reject_all_btn.className = 'c-bn';
958
959					_addEvent(settings_reject_all_btn, 'click', function ()
960					{
961						_cookieconsent.accept([]);
962						_cookieconsent.hideSettings();
963						_cookieconsent.hide();
964					});
965
966					settings_inner.className = "bns-t";
967					settings_buttons.appendChild(settings_reject_all_btn);
968				}
969
970				settings_reject_all_btn.innerHTML = reject_all_btn_text;
971			}
972
973
974			if (!settings_save_btn) {
975				settings_save_btn = _createNode('button');
976				settings_save_btn.id = 's-sv-bn';
977				settings_save_btn.className = 'c-bn';
978				settings_buttons.appendChild(settings_save_btn);
979
980				// Add save preferences button onClick event
981				// Hide both settings modal and consent modal
982				_addEvent(settings_save_btn, 'click', function ()
983				{
984					_cookieconsent.accept();
985					_cookieconsent.hideSettings();
986					_cookieconsent.hide();
987				});
988			}
989
990			settings_save_btn.innerHTML = settings_modal_config['save_settings_btn'];
991
992
993			if (new_settings_blocks) {
994				// replace entire existing cookie category blocks with the new cookie categories new blocks (in a different language)
995				settings_inner.replaceChild(new_settings_blocks, settings_blocks);
996				settings_blocks = new_settings_blocks;
997				return;
998			}
999			;
1000
1001			settings_header.appendChild(settings_title);
1002			settings_header.appendChild(settings_close_btn_container);
1003			settings_inner.appendChild(settings_header);
1004			settings_inner.appendChild(settings_blocks);
1005			settings_inner.appendChild(settings_buttons);
1006			settings_container_inner.appendChild(settings_inner);
1007
1008			settings.appendChild(settings_container_inner);
1009			settings_container_valign.appendChild(settings);
1010			settings_container.appendChild(settings_container_valign);
1011
1012			all_modals_container.appendChild(settings_container);
1013			all_modals_container.appendChild(overlay);
1014		}
1015
1016		/**
1017		 * Generate cookie consent html markup
1018		 */
1019		var _createCookieConsentHTML = function ()
1020		{
1021
1022			// Create main container which holds both consent modal & settings modal
1023			main_container = _createNode('div');
1024			main_container.id = 'cc--main';
1025
1026			// Fix layout flash
1027			main_container.style.position = "fixed";
1028			main_container.innerHTML = '<div id="cc_div" class="cc_div"></div>'
1029			all_modals_container = main_container.children[0];
1030
1031			// Get current language
1032			var lang = _config.current_lang;
1033
1034			// Create consent modal
1035			if (consent_modal_exists)
1036				_createConsentModal(lang);
1037
1038			// Always create settings modal
1039			_createSettingsModal(lang);
1040
1041			// Finally append everything (main_container holds both modals)
1042			(root || document.body).appendChild(main_container);
1043		}
1044
1045		/**
1046		 * Update/change modals language
1047		 * @param {String} lang new language
1048		 * @param {Boolean} [force] update language fields forcefully
1049		 * @returns {Boolean}
1050		 */
1051		_cookieconsent.updateLanguage = function (lang, force)
1052		{
1053
1054			if (typeof lang !== 'string') return;
1055
1056			/**
1057			 * Validate language to avoid errors
1058			 */
1059			var new_validated_lang = _getValidatedLanguage(lang, user_config.languages);
1060
1061			/**
1062			 * Set language only if it differs from current
1063			 */
1064			if (new_validated_lang !== _config.current_lang || force === true) {
1065				_config.current_lang = new_validated_lang;
1066
1067				if (consent_modal_exists) {
1068					_createConsentModal(new_validated_lang);
1069				}
1070
1071				_createSettingsModal(new_validated_lang);
1072
1073				_log("CookieConsent [LANGUAGE]: curr_lang: '" + new_validated_lang + "'");
1074
1075				return true;
1076			}
1077
1078			return false;
1079		}
1080
1081		/**
1082		 * Delete all cookies which are unused (based on selected preferences)
1083		 *
1084		 * @param {boolean} [clearOnFirstAction]
1085		 */
1086		var _autoclearCookies = function (clearOnFirstAction)
1087		{
1088
1089			// Get number of blocks
1090			var len = all_blocks.length;
1091			var count = -1;
1092
1093			// reset reload state
1094			reload_page = false;
1095
1096			// Retrieve all cookies
1097			var all_cookies_array = _getCookie('', 'all');
1098
1099			// delete cookies on 'www.domain.com' and '.www.domain.com' (can also be without www)
1100			var domains = [_config.cookie_domain, '.' + _config.cookie_domain];
1101
1102			// if domain has www, delete cookies also for 'domain.com' and '.domain.com'
1103			if (_config.cookie_domain.slice(0, 4) === 'www.') {
1104				var non_www_domain = _config.cookie_domain.substr(4);  // remove first 4 chars (www.)
1105				domains.push(non_www_domain);
1106				domains.push('.' + non_www_domain);
1107			}
1108
1109			// For each block
1110			for (var i = 0; i < len; i++) {
1111
1112				// Save current block (local scope & less accesses -> ~faster value retrieval)
1113				var curr_block = all_blocks[i];
1114
1115				// If current block has a toggle for opt in/out
1116				if (curr_block.hasOwnProperty('toggle')) {
1117
1118					// if current block has a cookie table, an off toggle,
1119					// and its preferences were just changed => delete cookies
1120					var category_just_disabled = _inArray(changed_settings, curr_block['toggle']['value']) > -1;
1121					if (
1122						!toggle_states[++count] &&
1123						curr_block.hasOwnProperty('cookie_table') &&
1124						(clearOnFirstAction || category_just_disabled)
1125					) {
1126						var curr_cookie_table = curr_block['cookie_table'];
1127
1128						// Get first property name
1129						var ckey = _getKeys(all_table_headers[0])[0];
1130
1131						// Get number of cookies defined in cookie_table
1132						var clen = curr_cookie_table.length;
1133
1134						// set "reload_page" to true if reload=on_disable
1135						if (curr_block['toggle']['reload'] === 'on_disable')
1136							category_just_disabled && (reload_page = true);
1137
1138						// for each row defined in the cookie table
1139						for (var j = 0; j < clen; j++) {
1140							var curr_domains = domains;
1141
1142							// Get current row of table (corresponds to all cookie params)
1143							var curr_row = curr_cookie_table[j], found_cookies = [];
1144							var curr_cookie_name = curr_row[ckey];
1145							var is_regex = curr_row['is_regex'] || false;
1146							var curr_cookie_domain = curr_row['domain'] || null;
1147							var curr_cookie_path = curr_row['path'] || false;
1148
1149							// set domain to the specified domain
1150							curr_cookie_domain && (curr_domains = [curr_cookie_domain, '.' + curr_cookie_domain]);
1151
1152							// If regex provided => filter cookie array
1153							if (is_regex) {
1154								for (var n = 0; n < all_cookies_array.length; n++) {
1155									if (all_cookies_array[n].match(curr_cookie_name)) {
1156										found_cookies.push(all_cookies_array[n]);
1157									}
1158								}
1159							} else {
1160								var found_index = _inArray(all_cookies_array, curr_cookie_name);
1161								if (found_index > -1) found_cookies.push(all_cookies_array[found_index]);
1162							}
1163
1164							_log("CookieConsent [AUTOCLEAR]: search cookie: '" + curr_cookie_name + "', found:", found_cookies);
1165
1166							// If cookie exists -> delete it
1167							if (found_cookies.length > 0) {
1168								_eraseCookies(found_cookies, curr_cookie_path, curr_domains);
1169								curr_block['toggle']['reload'] === 'on_clear' && (reload_page = true);
1170							}
1171						}
1172					}
1173				}
1174			}
1175		}
1176
1177		/**
1178		 * Set toggles/checkboxes based on accepted categories and save cookie
1179		 * @param {string[]} accepted_categories - Array of categories to accept
1180		 */
1181		var _saveCookiePreferences = function (accepted_categories)
1182		{
1183
1184			changed_settings = [];
1185
1186			// Retrieve all toggle/checkbox values
1187			var category_toggles = settings_container.querySelectorAll('.c-tgl') || [];
1188
1189			// If there are opt in/out toggles ...
1190			if (category_toggles.length > 0) {
1191
1192				for (var i = 0; i < category_toggles.length; i++) {
1193					if (_inArray(accepted_categories, all_categories[i]) !== -1) {
1194						category_toggles[i].checked = true;
1195						if (!toggle_states[i]) {
1196							changed_settings.push(all_categories[i]);
1197							toggle_states[i] = true;
1198						}
1199					} else {
1200						category_toggles[i].checked = false;
1201						if (toggle_states[i]) {
1202							changed_settings.push(all_categories[i]);
1203							toggle_states[i] = false;
1204						}
1205					}
1206				}
1207			}
1208
1209			/**
1210			 * Clear cookies when settings/preferences change
1211			 */
1212			if (!invalid_consent && _config.autoclear_cookies && changed_settings.length > 0)
1213				_autoclearCookies();
1214
1215			if (!consent_date) consent_date = new Date();
1216			if (!consent_uuid) consent_uuid = _uuidv4();
1217
1218			saved_cookie_content = {
1219				"categories": accepted_categories,
1220				"level": accepted_categories, // Copy of the `categories` property for compatibility purposes with version v2.8.0 and below.
1221				"revision": _config.revision,
1222				"data": cookie_data,
1223				"rfc_cookie": _config.use_rfc_cookie,
1224				"consent_date": consent_date.toISOString(),
1225				"consent_uuid": consent_uuid
1226			}
1227
1228			// save cookie with preferences 'categories' (only if never accepted or settings were updated)
1229			if (invalid_consent || changed_settings.length > 0) {
1230				valid_revision = true;
1231
1232				/**
1233				 * Update "last_consent_update" only if it is invalid (after t)
1234				 */
1235				if (!last_consent_update)
1236					last_consent_update = consent_date;
1237				else
1238					last_consent_update = new Date();
1239
1240				saved_cookie_content['last_consent_update'] = last_consent_update.toISOString();
1241
1242				/**
1243				 * Update accept type
1244				 */
1245				accept_type = _getAcceptType(_getCurrentCategoriesState());
1246
1247				_setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content));
1248				_manageExistingScripts();
1249			}
1250
1251			if (invalid_consent) {
1252
1253				/**
1254				 * Delete unused/"zombie" cookies if consent is not valid (not yet expressed or cookie has expired)
1255				 */
1256				if (_config.autoclear_cookies)
1257					_autoclearCookies(true);
1258
1259				if (isFunction(onFirstAction))
1260					onFirstAction(_cookieconsent.getUserPreferences(), saved_cookie_content);
1261
1262				if (isFunction(onAccept))
1263					onAccept(saved_cookie_content);
1264
1265				/**
1266				 * Set consent as valid
1267				 */
1268				invalid_consent = false;
1269
1270				if (_config.mode === OPT_IN) return;
1271			}
1272
1273			// fire onChange only if settings were changed
1274			if (typeof onChange === "function" && changed_settings.length > 0)
1275				onChange(saved_cookie_content, changed_settings);
1276
1277			/**
1278			 * reload page if needed
1279			 */
1280			if (reload_page)
1281				location.reload();
1282		}
1283
1284		/**
1285		 * Returns index of found element inside array, otherwise -1
1286		 * @param {Array} arr
1287		 * @param {Object} value
1288		 * @returns {number}
1289		 */
1290		var _inArray = function (arr, value)
1291		{
1292			return arr.indexOf(value);
1293		}
1294
1295		/**
1296		 * Helper function which prints info (console.log())
1297		 * @param {Object} print_msg
1298		 * @param {Object} [optional_param]
1299		 */
1300		var _log = function (print_msg, optional_param, error)
1301		{
1302			ENABLE_LOGS && (!error ? console.log(print_msg, optional_param !== undefined ? optional_param : ' ') : console.error(print_msg, optional_param || ""));
1303		}
1304
1305		/**
1306		 * Helper function which creates an HTMLElement object based on 'type' and returns it.
1307		 * @param {string} type
1308		 * @returns {HTMLElement}
1309		 */
1310		var _createNode = function (type)
1311		{
1312			var el = document.createElement(type);
1313			if (type === 'button') {
1314				el.setAttribute('type', type);
1315			}
1316			return el;
1317		}
1318
1319		/**
1320		 * Generate RFC4122-compliant UUIDs.
1321		 * https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid?page=1&tab=votes#tab-top
1322		 * @returns {string}
1323		 */
1324		var _uuidv4 = function ()
1325		{
1326			return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, function (c)
1327			{
1328				try {
1329					return (c ^ (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
1330				} catch (e) {
1331					return '';
1332				}
1333			});
1334		}
1335
1336		/**
1337		 * Resolve which language should be used.
1338		 *
1339		 * @param {Object} languages Object with language translations
1340		 * @param {string} [requested_language] Language specified by given configuration parameters
1341		 * @returns {string}
1342		 */
1343		var _resolveCurrentLang = function (languages, requested_language)
1344		{
1345
1346			if (_config.auto_language === 'browser') {
1347				return _getValidatedLanguage(_getBrowserLang(), languages);
1348			} else if (_config.auto_language === 'document') {
1349				return _getValidatedLanguage(document.documentElement.lang, languages);
1350			} else {
1351				if (typeof requested_language === 'string') {
1352					return _config.current_lang = _getValidatedLanguage(requested_language, languages);
1353				}
1354			}
1355
1356			_log("CookieConsent [LANG]: setting current_lang = '" + _config.current_lang + "'");
1357			return _config.current_lang; // otherwise return default
1358		}
1359
1360		/**
1361		 * Get current client's browser language
1362		 * @returns {string}
1363		 */
1364		var _getBrowserLang = function ()
1365		{
1366			var browser_lang = navigator.language || navigator.browserLanguage;
1367			browser_lang.length > 2 && (browser_lang = browser_lang[0] + browser_lang[1]);
1368			_log("CookieConsent [LANG]: detected_browser_lang = '" + browser_lang + "'");
1369			return browser_lang.toLowerCase()
1370		}
1371
1372		/**
1373		 * Trap focus inside modal and focus the first
1374		 * focusable element of current active modal
1375		 */
1376		var _handleFocusTrap = function ()
1377		{
1378
1379			_addEvent(document, 'keydown', function (e)
1380			{
1381
1382				// If is tab key => ok
1383				if (e.key !== 'Tab')
1384					return;
1385
1386				if (!consent_modal_visible && !settings_modal_visible)
1387					return;
1388
1389				// If there is any modal to focus
1390				if (focusable_edges) {
1391
1392					var activeElement = getActiveElement();
1393
1394					// If reached natural end of the tab sequence => restart
1395					// If modal is not focused => focus modal
1396					if (e.shiftKey) {
1397						if (activeElement === focusable_edges[0] || !focused_modal.contains(activeElement)) {
1398							e.preventDefault();
1399							setFocus(focusable_edges[1])
1400						}
1401					} else {
1402						if (activeElement === focusable_edges[1] || !focused_modal.contains(activeElement)) {
1403							e.preventDefault();
1404							setFocus(focusable_edges[0]);
1405						}
1406					}
1407				}
1408			});
1409
1410		}
1411
1412		/**
1413		 * Manage each modal's layout
1414		 * @param {Object} gui_options
1415		 */
1416		var _guiManager = function (gui_options, only_consent_modal)
1417		{
1418
1419			// If gui_options is not object => exit
1420			if (typeof gui_options !== 'object') return;
1421
1422			var consent_modal_options = gui_options['consent_modal'];
1423			var settings_modal_options = gui_options['settings_modal'];
1424
1425			/**
1426			 * Helper function which adds layout and
1427			 * position classes to given modal
1428			 *
1429			 * @param {HTMLElement} modal
1430			 * @param {string[]} allowed_layouts
1431			 * @param {string[]} allowed_positions
1432			 * @param {string} layout
1433			 * @param {string[]} position
1434			 */
1435			function _setLayout(modal, allowed_layouts, allowed_positions, allowed_transitions, layout, position, transition)
1436			{
1437				position = (position && position.split(" ")) || [];
1438
1439				// Check if specified layout is valid
1440				if (_inArray(allowed_layouts, layout) > -1) {
1441
1442					// Add layout classes
1443					_addClass(modal, layout);
1444
1445					// Add position class (if specified)
1446					if (!(layout === 'bar' && position[0] === 'middle') && _inArray(allowed_positions, position[0]) > -1) {
1447						for (var i = 0; i < position.length; i++) {
1448							_addClass(modal, position[i]);
1449						}
1450					}
1451				}
1452
1453				// Add transition class
1454				(_inArray(allowed_transitions, transition) > -1) && _addClass(modal, transition);
1455			}
1456
1457			if (consent_modal_exists && consent_modal_options) {
1458				_setLayout(
1459					consent_modal,
1460					['box', 'bar', 'cloud'],
1461					['top', 'middle', 'bottom'],
1462					['zoom', 'slide'],
1463					consent_modal_options['layout'],
1464					consent_modal_options['position'],
1465					consent_modal_options['transition']
1466				);
1467			}
1468
1469			if (!only_consent_modal && settings_modal_options) {
1470				_setLayout(
1471					settings_container,
1472					['bar'],
1473					['left', 'right'],
1474					['zoom', 'slide'],
1475					settings_modal_options['layout'],
1476					settings_modal_options['position'],
1477					settings_modal_options['transition']
1478				);
1479			}
1480		}
1481
1482		/**
1483		 * Returns true if cookie category is accepted by the user
1484		 * @param {string} cookie_category
1485		 * @returns {boolean}
1486		 */
1487		_cookieconsent.allowedCategory = function (cookie_category)
1488		{
1489
1490			if (!invalid_consent || _config.mode === OPT_IN)
1491				var allowed_categories = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || '{}')['categories'] || []
1492			else  // mode is 'opt-out'
1493				var allowed_categories = default_enabled_categories;
1494
1495			return _inArray(allowed_categories, cookie_category) > -1;
1496		}
1497
1498		/**
1499		 * "Init" method. Will run once and only if modals do not exist
1500		 */
1501		_cookieconsent.run = function (user_config)
1502		{
1503			if (!document.getElementById('cc_div')) {
1504
1505				// configure all parameters
1506				_setConfig(user_config);
1507
1508				// if is bot, don't run plugin
1509				if (is_bot) return;
1510
1511				// Retrieve cookie value (if set)
1512				saved_cookie_content = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || "{}");
1513
1514				// Retrieve "consent_uuid"
1515				consent_uuid = saved_cookie_content['consent_uuid'];
1516
1517				// If "consent_uuid" is present => assume that consent was previously given
1518				var cookie_consent_accepted = consent_uuid !== undefined;
1519
1520				// Retrieve "consent_date"
1521				consent_date = saved_cookie_content['consent_date'];
1522				consent_date && (consent_date = new Date(consent_date));
1523
1524				// Retrieve "last_consent_update"
1525				last_consent_update = saved_cookie_content['last_consent_update'];
1526				last_consent_update && (last_consent_update = new Date(last_consent_update));
1527
1528				// Retrieve "data"
1529				cookie_data = saved_cookie_content['data'] !== undefined ? saved_cookie_content['data'] : null;
1530
1531				// If revision is enabled and current value !== saved value inside the cookie =>
1531 revision is not valid
1532				if (revision_enabled && saved_cookie_content['revision'] !== _config.revision) {
1533					valid_revision = false;
1534				}
1535
1536				// If consent is not valid => create consent modal
1537				consent_modal_exists = invalid_consent = (!cookie_consent_accepted || !valid_revision || !consent_date || !last_consent_update || !consent_uuid);
1538
1539				// Generate cookie-settings dom (& consent modal)
1540				_createCookieConsentHTML();
1541
1542				_getModalFocusableData();
1543				_guiManager(user_config['gui_options']);
1544				_addDataButtonListeners();
1545
1546				if (_config.autorun && consent_modal_exists) {
1547					_cookieconsent.show(user_config['delay'] || 0);
1548				}
1549
1550				// Add class to enable animations/transitions
1551				setTimeout(function ()
1552				{
1553					_addClass(main_container, 'c--anim');
1554				}, 30);
1555
1556				// Accessibility :=> if tab pressed => trap focus inside modal
1557				setTimeout(function ()
1558				{
1559					_handleFocusTrap();
1560				}, 100);
1561
1562				// If consent is valid
1563				if (!invalid_consent) {
1564					var rfc_prop_exists = typeof saved_cookie_content['rfc_cookie'] === "boolean";
1565
1566					/*
1567					 * Convert cookie to rfc format (if `use_rfc_cookie` is enabled)
1568					 */
1569					if (!rfc_prop_exists || (rfc_prop_exists && saved_cookie_content['rfc_cookie'] !== _config.use_rfc_cookie)) {
1570						saved_cookie_content['rfc_cookie'] = _config.use_rfc_cookie;
1571						_setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content));
1572					}
1573
1574					/**
1575					 * Update accept type
1576					 */
1577					accept_type = _getAcceptType(_getCurrentCategoriesState());
1578
1579					_manageExistingScripts();
1580
1581					if (isFunction(onAccept))
1582						onAccept(saved_cookie_content);
1583
1584					_log("CookieConsent [NOTICE]: consent already given!", saved_cookie_content);
1585
1586				} else {
1587					if (_config.mode === OPT_OUT) {
1588						_log("CookieConsent [CONFIG] mode='" + _config.mode + "', default enabled categories:", default_enabled_categories);
1589						_manageExistingScripts();
1590					}
1591					_log("CookieConsent [NOTICE]: ask for consent!");
1592				}
1593			} else {
1594				_log("CookieConsent [NOTICE]: cookie consent already attached to body!");
1595			}
1596		}
1597
1598		/**
1599		 * Handles the activation logic of script elements
1600		 * based on enabled categories
1601		 */
1602		var _manageExistingScripts = function ()
1603		{
1604
1605			if (!_config.page_scripts) return;
1606
1607			/**
1608			 * @type {string[]}
1609			 */
1610			var enabled_categories = saved_cookie_content['categories'] || [];
1611
1612			if (invalid_consent && _config.mode === OPT_OUT)
1613				enabled_categories = default_enabled_categories;
1614
1615			// get all the scripts with "cookie-category" attribute
1616			var scripts = document.querySelectorAll('script[' + _config.script_selector + ']');
1617
1618			/**
1619			 * Load scripts (sequentially), using a recursive function
1620			 * which loops through the scripts array
1621			 * @param {Element[]} scripts scripts to load
1622			 * @param {number} index current script to load
1623			 */
1624			var _loadScripts = function (scripts, index)
1625			{
1626				if (index < scripts.length) {
1627
1628					var curr_script = scripts[index];
1629					var curr_script_category = curr_script.getAttribute(_config.script_selector);
1630
1631					/**
1632					 * If current script's category is on the array of categories
1633					 * accepted by the user => load script
1634					 */
1635					if (_inArray(enabled_categories, curr_script_category) > -1) {
1636
1637						curr_script.type = curr_script.getAttribute('data-type') || 'text/javascript';
1638						curr_script.removeAttribute(_config.script_selector);
1639
1640						// get current script data-src
1641						var src = curr_script.getAttribute('data-src');
1642
1643						// some scripts (like ga) might throw warning if data-src is present
1644						src && curr_script.removeAttribute('data-src');
1645
1646						// create fresh script (with the same code)
1647						var fresh_script = _createNode('script');
1648						fresh_script.textContent = curr_script.innerHTML;
1649
1650						// Copy attributes over to the new "revived" script
1651						(function (destination, source)
1652						{
1653							var attributes = source.attributes;
1654							var len = attributes.length;
1655							for (var i = 0; i < len; i++) {
1656								var attr_name = attributes[i].nodeName;
1657								destination.setAttribute(attr_name, source[attr_name] || source.getAttribute(attr_name));
1658							}
1659						})(fresh_script, curr_script);
1660
1661						// set src (if data-src found)
1662						src ? (fresh_script.src = src) : (src = curr_script.src);
1663
1664						// if script has "src" attribute
1665						// try loading it sequentially
1666						if (src) {
1667							// load script sequentially => the next script will not be loaded
1668							// until the current's script onload event triggers
1669							if (fresh_script.readyState) {  // only required for IE <9
1670								fresh_script.onreadystatechange = function ()
1671								{
1672									if (fresh_script.readyState === "loaded" || fresh_script.readyState === "complete") {
1673										fresh_script.onreadystatechange = null;
1674										_loadScripts(scripts, ++index);
1675									}
1676								};
1677							} else {  // others
1678								fresh_script.onload = function ()
1679								{
1680									fresh_script.onload = null;
1681									_loadScripts(scripts, ++index);
1682								};
1683							}
1684						}
1685
1686						// Replace current "sleeping" script with the new "revived" one
1687						curr_script.parentNode.replaceChild(fresh_script, curr_script);
1688
1689						/**
1690						 * If we managed to get here and scr is still set, it means that
1691						 * the script is loading/loaded sequentially so don't go any further
1692						 */
1693						if (src) return;
1694					}
1695
1696					// Go to next script right away
1697					_loadScripts(scripts, ++index);
1698				}
1699			}
1700
1701			_loadScripts(scripts, 0);
1702		}
1703
1704		/**
1705		 * Save custom data inside cookie
1706		 * @param {object|string} new_data
1707		 * @param {string} [mode]
1708		 * @returns {boolean}
1709		 */
1710		var _setCookieData = function (new_data, mode)
1711		{
1712
1713			var set = false;
1714			/**
1715			 * If mode is 'update':
1716			 * add/update only the specified props.
1717			 */
1718			if (mode === 'update') {
1719				cookie_data = _cookieconsent.get('data');
1720				var same_type = typeof cookie_data === typeof new_data;
1721
1722				if (same_type && typeof cookie_data === "object") {
1723					!cookie_data && (cookie_data = {});
1724
1725					for (var prop in new_data) {
1726						if (cookie_data[prop] !== new_data[prop]) {
1727							cookie_data[prop] = new_data[prop]
1728							set = true;
1729						}
1730					}
1731				} else if ((same_type || !cookie_data) && cookie_data !== new_data) {
1732					cookie_data = new_data;
1733					set = true;
1734				}
1735			} else {
1736				cookie_data = new_data;
1737				set = true;
1738			}
1739
1740			if (set) {
1741				saved_cookie_content['data'] = cookie_data;
1742				_setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content));
1743			}
1744
1745			return set;
1746		}
1747
1748		/**
1749		 * Helper method to set a variety of fields
1750		 * @param {string} field
1751		 * @param {object} data
1752		 * @returns {boolean}
1753		 */
1754		_cookieconsent.set = function (field, data)
1755		{
1756			switch (field) {
1757				case 'data':
1758					return _setCookieData(data['value'], data['mode']);
1759				default:
1760					return false;
1761			}
1762		}
1763
1764		/**
1765		 * Retrieve data from existing cookie
1766		 * @param {string} field
1767		 * @param {string} [cookie_name]
1768		 * @returns {any}
1769		 */
1770		_cookieconsent.get = function (field, cookie_name)
1771		{
1772			var cookie = JSON.parse(_getCookie(cookie_name || _config.cookie_name, 'one', true) || "{}");
1773
1774			return cookie[field];
1775		}
1776
1777		/**
1778		 * Read current configuration value
1779		 * @returns {any}
1780		 */
1781		_cookieconsent.getConfig = function (field)
1782		{
1783			return _config[field] || user_config[field];
1784		}
1785
1786		/**
1787		 * Obtain accepted and rejected categories
1788		 * @returns {{accepted: string[], rejected: string[]}}
1789		 */
1790		var _getCurrentCategoriesState = function ()
1791		{
1792
1793			// get accepted categories
1794			accepted_categories = saved_cookie_content['categories'] || [];
1795
1796			// calculate rejected categories (all_categories - accepted_categories)
1797			rejected_categories = all_categories.filter(function (category)
1798			{
1799				return (_inArray(accepted_categories, category) === -1);
1800			});
1801
1802			return {
1803				accepted: accepted_categories,
1804				rejected: rejected_categories
1805			}
1806		}
1807
1808		/**
1809		 * Calculate "accept type" given current categories state
1810		 * @param {{accepted: string[], rejected: string[]}} currentCategoriesState
1811		 * @returns {string}
1812		 */
1813		var _getAcceptType = function (currentCategoriesState)
1814		{
1815
1816			var type = 'custom';
1817
1818			// number of categories marked as necessary/readonly
1819			var necessary_categories_length = readonly_categories.length;
1820
1821			// calculate accept type based on accepted/rejected categories
1822			if (currentCategoriesState.accepted.length === all_categories.length)
1823				type = 'all';
1824			else if (currentCategoriesState.accepted.length === necessary_categories_length)
1825				type = 'necessary'
1826
1827			return type;
1828		}
1829
1830		/**
1831		 * @typedef {object} userPreferences
1832		 * @property {string} accept_type
1833		 * @property {string[]} accepted_categories
1834		 * @property {string[]} rejected_categories
1835		 */
1836
1837		/**
1838		 * Retrieve current user preferences (summary)
1839		 * @returns {userPreferences}
1840		 */
1841		_cookieconsent.getUserPreferences = function ()
1842		{
1843			var currentCategoriesState = _getCurrentCategoriesState();
1844			var accept_type = _getAcceptType(currentCategoriesState);
1845
1846			return {
1847				'accept_type': accept_type,
1848				'accepted_categories': currentCategoriesState.accepted,
1849				'rejected_categories': currentCategoriesState.rejected
1850			}
1851		}
1852
1853		/**
1854		 * Function which will run after script load
1855		 * @callback scriptLoaded
1856		 */
1857
1858		/**
1859		 * Dynamically load script (append to head)
1860		 * @param {string} src
1861		 * @param {scriptLoaded} callback
1862		 * @param {object[]} [attrs] Custom attributes
1863		 */
1864		_cookieconsent.loadScript = function (src, callback, attrs)
1865		{
1866
1867			var function_defined = isFunction(callback);
1868
1869			// Load script only if not already loaded
1870			if (!document.querySelector('script[src="' + src + '"]')) {
1871
1872				var script = _createNode('script');
1873
1874				// if an array is provided => add custom attributes
1875				if (attrs && attrs.length > 0) {
1876					for (var i = 0; i < attrs.length; ++i) {
1877						attrs[i] && script.setAttribute(attrs[i]['name'], attrs[i]['value']);
1878					}
1879				}
1880
1881				// if callback function defined => run callback onload
1882				if (function_defined) {
1883					script.onload = callback;
1884				}
1885
1886				script.src = src;
1887
1888				/**
1889				 * Append script to head
1890				 */
1891				document.head.appendChild(script);
1892			} else {
1893				function_defined && callback();
1894			}
1895		}
1896
1897		/**
1898		 * Manage dynamically loaded scripts: https://github.com/orestbida/cookieconsent/issues/101
1899		 * If plugin has already run, call this method to enable
1900		 * the newly added scripts based on currently selected preferences
1901		 */
1902		_cookieconsent.updateScripts = function ()
1903		{
1904			_manageExistingScripts();
1905		}
1906
1907		/**
1908		 * Show cookie consent modal (with delay parameter)
1909		 * @param {number} [delay]
1910		 * @param {boolean} [create_modal] create modal if it doesn't exist
1911		 */
1912		_cookieconsent.show = function (delay, create_modal)
1913		{
1914
1915			if (create_modal === true)
1916				_createConsentModal(_config.current_lang);
1917
1918			if (!consent_modal_exists)
1919				return;
1920
1921			last_elem_before_modal = getActiveElement();
1922			focusable_edges = consent_modal_focusable;
1923			focused_modal = consent_modal;
1924
1925			consent_modal_visible = true;
1926			consent_modal.removeAttribute('aria-hidden');
1927
1928			setTimeout(function ()
1929			{
1930				_addClass(html_dom, "show--consent");
1931				_log("CookieConsent [MODAL]: show consent_modal");
1932			}, delay > 0 ? delay : (create_modal ? 30 : 0));
1933
1934		}
1935
1936		/**
1937		 * Hide consent modal
1938		 */
1939		_cookieconsent.hide = function ()
1940		{
1941
1942			if (!consent_modal_exists)
1943				return;
1944
1945			consent_modal_visible = false;
1946
1947			setFocus(cmFocusSpan);
1948
1949			consent_modal.setAttribute('aria-hidden', 'true');
1950			_removeClass(html_dom, "show--consent");
1951
1952			setFocus(last_elem_before_modal);
1953			last_elem_before_modal = null;
1954
1955			_log("CookieConsent [MODAL]: hide");
1956		}
1957
1958		/**
1959		 * Show settings modal (with optional delay)
1960		 * @param {number} delay
1961		 */
1962		_cookieconsent.showSettings = function (delay)
1963		{
1964
1965			settings_modal_visible = true;
1966			settings_container.removeAttribute('aria-hidden');
1967
1968			if (consent_modal_visible) {
1969				last_consent_modal_btn_focus = getActiveElement();
1970			} else {
1971				last_elem_before_modal = getActiveElement();
1972			}
1973
1974			focused_modal = settings_container;
1975			focusable_edges = settings_modal_focusable;
1976
1977			setTimeout(function ()
1978			{
1979				_addClass(html_dom, "show--settings");
1980				_log("CookieConsent [SETTINGS]: show settings_modal");
1981			}, delay > 0 ? delay : 0);
1982		}
1983
1984		/**
1985		 * Hide settings modal
1986		 */
1987		_cookieconsent.hideSettings = function ()
1988		{
1989
1990			settings_modal_visible = false;
1991
1992			discardUnsavedToggles();
1993
1994			setFocus(smFocusSpan);
1995
1996			settings_container.setAttribute('aria-hidden', 'true');
1997			_removeClass(html_dom, "show--settings");
1998
1999			if (consent_modal_visible) {
2000
2001				setFocus(last_consent_modal_btn_focus);
2002				last_consent_modal_btn_focus = null;
2003
2004				focused_modal = consent_modal;
2005				focusable_edges = consent_modal_focusable;
2006			} else {
2007				setFocus(last_elem_before_modal);
2008				last_elem_before_modal = null;
2009			}
2010
2011			_log("CookieConsent [SETTINGS]: hide settings_modal");
2012		}
2013
2014		/**
2015		 * Accept cookieconsent function API
2016		 * @param {string[]|string} _categories - Categories to accept
2017		 * @param {string[]} [_exclusions] - Excluded categories [optional]
2018		 */
2019		_cookieconsent.accept = function (_categories, _exclusions)
2020		{
2021			var categories = _categories || undefined;
2022			var exclusions = _exclusions || [];
2023			var to_accept = [];
2024
2025			/**
2026			 * Get all accepted categories
2027			 * @returns {string[]}
2028			 */
2029			var _getCurrentPreferences = function ()
2030			{
2031				var toggles = document.querySelectorAll('.c-tgl') || [];
2032				var states = [];
2033
2034				for (var i = 0; i < toggles.length; i++) {
2035					if (toggles[i].checked) {
2036						states.push(toggles[i].value);
2037					}
2038				}
2039				return states;
2040			}
2041
2042			if (!categories) {
2043				to_accept = _getCurrentPreferences();
2044			} else {
2045				if (
2046					typeof categories === "object" &&
2047					typeof categories.length === "number"
2048				) {
2049					for (var i = 0; i < categories.length; i++) {
2050						if (_inArray(all_categories, categories[i]) !== -1)
2051							to_accept.push(categories[i]);
2052					}
2053				} else if (typeof categories === "string") {
2054					if (categories === 'all')
2055						to_accept = all_categories.slice();
2056					else {
2057						if (_inArray(all_categories, categories) !== -1)
2058							to_accept.push(categories);
2059					}
2060				}
2061			}
2062
2063			// Remove excluded categories
2064			if (exclusions.length >= 1) {
2065				for (i = 0; i < exclusions.length; i++) {
2066					to_accept = to_accept.filter(function (item)
2067					{
2068						return item !== exclusions[i]
2069					})
2070				}
2071			}
2072
2073			// Add back all the categories set as "readonly/required"
2074			for (i = 0; i < all_categories.length; i++) {
2075				if (
2076					readonly_categories.includes(all_categories[i]) === true &&
2077					_inArray(to_accept, all_categories[i]) === -1
2078				) {
2079					to_accept.push(all_categories[i]);
2080				}
2081			}
2082
2083			_saveCookiePreferences(to_accept);
2084		}
2085
2086		/**
2087		 * API function to easily erase cookies
2088		 * @param {(string|string[])} _cookies
2089		 * @param {string} [_path] - optional
2090		 * @param {string} [_domain] - optional
2091		 */
2092		_cookieconsent.eraseCookies = function (_cookies, _path, _domain)
2093		{
2094			var cookies = [];
2095			var domains = _domain
2096				? [_domain, "." + _domain]
2097				: [_config.cookie_domain, "." + _config.cookie_domain];
2098
2099			if (typeof _cookies === "object" && _cookies.length > 0) {
2100				for (var i = 0; i < _cookies.length; i++) {
2101					this.validCookie(_cookies[i]) && cookies.push(_cookies[i]);
2102				}
2103			} else {
2104				this.validCookie(_cookies) && cookies.push(_cookies);
2105			}
2106
2107			_eraseCookies(cookies, _path, domains);
2108		}
2109
2110		/**
2111		 * Set cookie, by specifying name and value
2112		 * @param {string} name
2113		 * @param {string} value
2114		 */
2115		var _setCookie = function (name, value)
2116		{
2117
2118			var cookie_expiration = _config.cookie_expiration;
2119
2120			if (typeof _config.cookie_necessary_only_expiration === 'number' && accept_type === 'necessary')
2121				cookie_expiration = _config.cookie_necessary_only_expiration;
2122
2123			value = _config.use_rfc_cookie ? encodeURIComponent(value) : value;
2124
2125			var date = new Date();
2126			date.setTime(date.getTime() + (1000 * (cookie_expiration * 24 * 60 * 60)));
2127			var expires = "; expires=" + date.toUTCString();
2128
2129			var cookieStr = name + "=" + (value || "") + expires + "; Path=" + _config.cookie_path + ";";
2130			cookieStr += " SameSite=" + _config.cookie_same_site + ";";
2131
2132			// assures cookie works with localhost (=> don't specify domain if on localhost)
2133			if (location.hostname.indexOf(".") > -1 && _config.cookie_domain) {
2134				cookieStr += " Domain=" + _config.cookie_domain + ";";
2135			}
2136
2137			if (location.protocol === "https:") {
2138				cookieStr += " Secure;";
2139			}
2140
2141			document.cookie = cookieStr;
2142
2143			_log("CookieConsent [SET_COOKIE]: '" + name + "' expires after " + cookie_expiration + " day(s)");
2144		}
2145
2146		/**
2147		 * Get cookie value by name,
2148		 * returns the cookie value if found (or an array
2149		 * of cookies if filter provided), otherwise empty string: ""
2150		 * @param {string} name
2151		 * @param {string} filter 'one' or 'all'
2152		 * @param {boolean} [get_value] set to true to obtain its value
2153		 * @returns {string|string[]}
2154		 */
2155		var _getCookie = function (name, filter, get_value)
2156		{
2157			var found;
2158
2159			if (filter === 'one') {
2160				found = document.cookie.match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)");
2161				found = found ? (get_value ? found.pop() : name) : "";
2162
2163				if (found && name === _config.cookie_name) {
2164					try {
2165						found = JSON.parse(found)
2166					} catch (e) {
2167						try {
2168							found = JSON.parse(decodeURIComponent(found))
2169						} catch (e) {
2170							// if I got here => cookie value is not a valid json string
2171							found = {};
2172						}
2173					}
2174					found = JSON.stringify(found);
2175				}
2176			} else if (filter === 'all') {
2177				// array of names of all existing cookies
2178				var cookies = document.cookie.split(/;\s*/);
2179				found = [];
2180				for (var i = 0; i < cookies.length; i++) {
2181					found.push(cookies[i].split("=")[0]);
2182				}
2183			}
2184
2185			return found;
2186		}
2187
2188		/**
2189		 * Delete cookie by name & path
2190		 * @param {string[]} cookies
2191		 * @param {string} [custom_path] - optional
2192		 * @param {string[]} domains - example: ['domain.com', '.domain.com']
2193		 */
2194		var _eraseCookies = function (cookies, custom_path, domains)
2195		{
2196			var path = custom_path ? custom_path : '/';
2197			var expires = 'Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
2198
2199			for (var i = 0; i < cookies.length; i++) {
2200				for (var j = 0; j < domains.length; j++) {
2201					document.cookie = cookies[i] + '=; path=' + path +
2202						(domains[j].indexOf('.') == 0 ? '; domain=' + domains[j] : "") + '; ' + expires;
2203				}
2204				_log("CookieConsent [AUTOCLEAR]: deleting cookie: '" + cookies[i] + "' path: '" + path + "' domain:", domains);
2205			}
2206		}
2207
2208		/**
2209		 * Returns true if cookie was found and has valid value (not empty string)
2210		 * @param {string} cookie_name
2211		 * @returns {boolean}
2212		 */
2213		_cookieconsent.validCookie = function (cookie_name)
2214		{
2215			return _getCookie(cookie_name, 'one', true) !== "";
2216		}
2217
2218		_cookieconsent.validConsent = function ()
2219		{
2220			return !invalid_consent;
2221		}
2222
2223		/**
2224		 * Function to run when event is fired
2225		 * @callback eventFired
2226		 */
2227
2228		/**
2229		 * Add event listener to dom object (cross browser function)
2230		 * @param {Element} elem
2231		 * @param {string} event
2232		 * @param {eventFired} fn
2233		 * @param {boolean} [isPassive]
2234		 */
2235		var _addEvent = function (elem, event, fn, isPassive)
2236		{
2237			elem.addEventListener(event, fn, isPassive === true ? {passive: true} : false);
2238		}
2239
2240		/**
2241		 * Get all prop. keys defined inside object
2242		 * @param {Object} obj
2243		 */
2244		var _getKeys = function (obj)
2245		{
2246			if (typeof obj === "object") {
2247				return Object.keys(obj);
2248			}
2249		}
2250
2251		/**
2252		 * Append class to the specified dom element
2253		 * @param {HTMLElement} elem
2254		 * @param {string} classname
2255		 */
2256		var _addClass = function (elem, classname)
2257		{
2258			elem.classList.add(classname);
2259		}
2260
2261		/**
2262		 * Remove specified class from dom element
2263		 * @param {HTMLElement} elem
2264		 * @param {string} classname
2265		 */
2266		var _removeClass = function (el, className)
2267		{
2268			el.classList.remove(className);
2269		}
2270
2271		/**
2272		 * Check if html element has class
2273		 * @param {HTMLElement} el
2274		 * @param {string} className
2275		 */
2276		var _hasClass = function (el, className)
2277		{
2278			return el.classList.contains(className);
2279		}
2280
2281		/**
2282		 * @param {1 | 2} modal_id
2283		 */
2284		var generateFocusSpan = function (modal_id)
2285		{
2286			var span = _createNode('span');
2287			span.tabIndex = -1;
2288
2289			if (modal_id === 1)
2290				cmFocusSpan = span;
2291			else
2292				smFocusSpan = span;
2293
2294			return span;
2295		}
2296
2297		/**
2298		 * @param {HTMLElement} el
2299		 */
2300		var setFocus = function (el)
2301		{
2302			el && el instanceof HTMLElement && el.focus();
2303		}
2304
2305		/**
2306		 * https://github.com/orestbida/cookieconsent/issues/481
2307		 */
2308		var discardUnsavedToggles = function ()
2309		{
2310
2311			/**
2312			 * @type {NodeListOf<HTMLInputElement>}
2313			 */
2314			var toggles = settings_inner.querySelectorAll('.c-tgl');
2315
2316			for (var i = 0; i < toggles.length; i++) {
2317				var category = toggles[i].value;
2318				var is_readonly = readonly_categories.includes(category);
2319
2320				toggles[i].checked = is_readonly || _cookieconsent.allowedCategory(category);
2321			}
2322
2323		}
2324
2325		/**
2326		 * @returns {Element | null}
2327		 */
2328		var getActiveElement = function ()
2329		{
2330			return document.activeElement;
2331		}
2332
2333		/**
2334		 * @param {any} fn
2335		 */
2336		var isFunction = function (fn)
2337		{
2338			return typeof fn === 'function';
2339		}
2340
2341		return _cookieconsent;
2342	};
2343
2344	var init = 'initCookieConsent';
2345	/**
2346	 * Make CookieConsent object accessible globally
2347	 */
2348	if (typeof window !== 'undefined' && typeof window[init] !== 'function') {
2349		window[init] = CookieConsent
2350	}
2351})();

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.