PageSourceSearch

https://npu.ac.th/scripts/cookie/cookieconsent.js

js npu.ac.th collected 2026-10-02 12:13:15 UTC 87,705 bytes, 2,265 lines download raw bytes

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

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.