source: main/trunk/greenstone3/web/interfaces/default/cc/cookieconsent.js@ 37732

Last change on this file since 37732 was 37732, checked in by davidb, 12 months ago

Updating to newer version, and then also including some changes that were beneficial in helping Safari an a Mac set the 'cc_concent' cookie

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