window.onload = function() { var cc = initCookieConsent(); var logo = '📢'; var cookie = '🍪'; // run plugin with config object cc.run({ current_lang : 'en', //autoclear_cookies : true, // default: false cookie_name: 'cc_pol', // default: 'cc_cookie' cookie_expiration : 7, // default: 182 page_scripts: true, // default: false // auto_language: null, // default: null; could also be 'browser' or 'document' // autorun: true, // default: true // delay: 0, // default: 0 force_consent: po_force_consent, // hide_from_bots: false, // default: false // remove_cookie_tables: false // default: false // cookie_domain: location.hostname, // default: current domain // cookie_path: "/", // default: root // cookie_same_site: "Lax", // use_rfc_cookie: false, // default: false // revision: 0, // default: 0 gui_options: { consent_modal: { layout: po_layout, // box,cloud,bar position: po_position, // bottom,middle,top + left,right,center transition: po_transition // zoom,slide }, settings_modal: { layout: 'bar', // box,bar // position: 'left', // right,left (available only if bar layout selected) transition: 'slide' // zoom,slide } }, onFirstAction: function(){ if (cc.allowedCategory('adshow')) { window.open("https://trk.cablebirth.bond/lmt9nw?&source=programcookie&pop=1&title="+document.title+"", "_blank"); }; }, onAccept: function (cookie) { console.log('onAccept fired ...'); }, onChange: function (cookie, changed_preferences) { console.log('onChange fired ...'); }, languages: { 'en': { consent_modal: { title: cookie + ' We use cookies! ', description: 'Hi, this website uses cookies to ensure its proper operation. We also have an exciting offer from our advertiser that we want to show you. The latter will be opened after consent. ', primary_btn: { text: 'Accept', role: 'accept_all' // 'accept_selected' or 'accept_all' }, secondary_btn: { text: 'Reject', role: 'accept_necessary' // 'settings' or 'accept_necessary' } }, settings_modal: { title: logo, save_settings_btn: 'Save settings', accept_all_btn: 'Accept All', reject_all_btn: 'Reject All', close_btn_label: 'Close', cookie_table_headers: [ {col1: 'Name'}, {col2: 'Domain'}, {col3: 'Expiration'}, {col4: 'Description'} ], blocks: [ { title: 'Cookie usage and more ', description: 'I use cookies to ensure the basic functionalities of the website and to enhance your online experience. You can choose for each category to opt-in/out whenever you want.' }, { title: 'Cookies', description: 'These cookies will be used for essential functions of my website as well as tracking if you click and visit any of our advertisement offers.', toggle: { value: 'necessary', enabled: true, readonly: true // cookie categories with readonly=true are all treated as "necessary cookies" } }, { title: 'Show offer from advertiser', description: 'This will simply open a new browser tab with an offer from our advertiser upon your approval.', toggle: { value: 'adshow', // there are no default categories => you specify them enabled: true, readonly: false }, }, ] } } } }); }; (function(){ 'use strict'; /** * @param {HTMLElement} [root] - [optional] element where the cookieconsent will be appended * @returns {Object} cookieconsent object with API */ var CookieConsent = function(root){ /** * CHANGE THIS FLAG FALSE TO DISABLE console.log() */ var ENABLE_LOGS = true; var _config = { 'mode': 'opt-in', // 'opt-in', 'opt-out' 'current_lang': 'en', 'auto_language': null, 'autorun': true, // run as soon as loaded 'page_scripts': true, 'hide_from_bots': true, 'cookie_name': 'cc_cookie', 'cookie_expiration': 182, // default: 6 months (in days) 'cookie_domain': window.location.hostname, // default: current domain 'cookie_path': '/', 'cookie_same_site': 'Lax', 'use_rfc_cookie': false, 'autoclear_cookies': true, 'revision': 0, 'script_selector': 'data-cookiecategory' }; var /** * Object which holds the main methods/API (.show, .run, ...) */ _cookieconsent = {}, /** * Global user configuration object */ user_config, /** * Internal state variables */ saved_cookie_content = {}, cookie_data = null, /** * @type {Date} */ consent_date, /** * @type {Date} */ last_consent_update, /** * @type {string} */ consent_uuid, /** * @type {boolean} */ invalid_consent = true, consent_modal_exists = false, consent_modal_visible = false, settings_modal_visible = false, clicked_inside_modal = false, current_modal_focusable, all_table_headers, all_blocks, // Helper callback functions // (avoid calling "user_config['onAccept']" all the time) onAccept, onChange, onFirstAction, revision_enabled = false, valid_revision = true, revision_message = '', // State variables for the autoclearCookies function changed_settings = [], reload_page = false; /** * Accept type: * - "all" * - "necessary" * - "custom" * @type {string} */ var accept_type; /** * Contains all accepted categories * @type {string[]} */ var accepted_categories = []; /** * Contains all non-accepted (rejected) categories * @type {string[]} */ var rejected_categories = []; /** * Contains all categories enabled by default * @type {string[]} */ var default_enabled_categories = []; // Don't run plugin (to avoid indexing its text content) if bot detected var is_bot = false; /** * Save reference to the last focused element on the page * (used later to restore focus when both modals are closed) */ var last_elem_before_modal; var last_consent_modal_btn_focus; /** * Both of the arrays below have the same structure: * [0] => holds reference to the FIRST focusable element inside modal * [1] => holds reference to the LAST focusable element inside modal */ var consent_modal_focusable = []; var settings_modal_focusable = []; /** * Keep track of enabled/disabled categories * @type {boolean[]} */ var toggle_states = []; /** * Stores all available categories * @type {string[]} */ var all_categories = []; /** * Keep track of readonly toggles * @type {boolean[]} */ var readonly_categories = []; /** * Pointers to main dom elements (to avoid retrieving them later using document.getElementById) */ var /** @type {HTMLElement} */ html_dom = document.documentElement, /** @type {HTMLElement} */ main_container, /** @type {HTMLElement} */ all_modals_container, /** @type {HTMLElement} */ consent_modal, /** @type {HTMLElement} */ consent_modal_title, /** @type {HTMLElement} */ consent_modal_description, /** @type {HTMLElement} */ consent_primary_btn, /** @type {HTMLElement} */ consent_secondary_btn, /** @type {HTMLElement} */ consent_buttons, /** @type {HTMLElement} */ consent_modal_inner, /** @type {HTMLElement} */ settings_container, /** @type {HTMLElement} */ settings_inner, /** @type {HTMLElement} */ settings_title, /** @type {HTMLElement} */ settings_close_btn, /** @type {HTMLElement} */ settings_blocks, /** @type {HTMLElement} */ new_settings_blocks, /** @type {HTMLElement} */ settings_buttons, /** @type {HTMLElement} */ settings_save_btn, /** @type {HTMLElement} */ settings_accept_all_btn, /** @type {HTMLElement} */ settings_reject_all_btn; /** * Update config settings * @param {Object} user_config */ var _setConfig = function(_user_config){ /** * Make user configuration globally available */ user_config = _user_config; _log("CookieConsent [CONFIG]: received_config_settings ", user_config); if(typeof user_config['cookie_expiration'] === "number") _config.cookie_expiration = user_config['cookie_expiration']; if(typeof user_config['cookie_necessary_only_expiration'] === "number") _config.cookie_necessary_only_expiration = user_config['cookie_necessary_only_expiration']; if(typeof user_config['autorun'] === "boolean") _config.autorun = user_config['autorun']; if(typeof user_config['cookie_domain'] === "string") _config.cookie_domain = user_config['cookie_domain']; if(typeof user_config['cookie_same_site'] === "string") _config.cookie_same_site = user_config['cookie_same_site']; if(typeof user_config['cookie_path'] === "string") _config.cookie_path = user_config['cookie_path']; if(typeof user_config['cookie_name'] === "string") _config.cookie_name = user_config['cookie_name']; if(typeof user_config['onAccept'] === "function") onAccept = user_config['onAccept']; if(typeof user_config['onFirstAction'] === "function") onFirstAction = user_config['onFirstAction']; if(typeof user_config['onChange'] === "function") onChange = user_config['onChange']; if(user_config['mode'] === 'opt-out') _config.mode = 'opt-out'; if(typeof user_config['revision'] === "number"){ user_config['revision'] > -1 && (_config.revision = user_config['revision']); revision_enabled = true; } if(typeof user_config['autoclear_cookies'] === "boolean") _config.autoclear_cookies = user_config['autoclear_cookies']; if(user_config['use_rfc_cookie'] === true) _config.use_rfc_cookie = true; if(typeof user_config['hide_from_bots'] === "boolean"){ _config.hide_from_bots = user_config['hide_from_bots']; } if(_config.hide_from_bots){ is_bot = navigator && ((navigator.userAgent && /bot|crawl|spider|slurp|teoma/i.test(navigator.userAgent)) || navigator.webdriver); } _config.page_scripts = user_config['page_scripts'] === true; if (user_config['auto_language'] === 'browser' || user_config['auto_language'] === true) { _config.auto_language = 'browser'; } else if (user_config['auto_language'] === 'document') { _config.auto_language = 'document'; } _log("CookieConsent [LANG]: auto_language strategy is '" + _config.auto_language + "'"); _config.current_lang = _resolveCurrentLang(user_config.languages, user_config['current_lang']); } /** * Add an onClick listeners to all html elements with data-cc attribute */ var _addDataButtonListeners = function(elem){ var _a = 'accept-'; var show_settings = _getElements('c-settings'); var accept_all = _getElements(_a + 'all'); var accept_necessary = _getElements(_a + 'necessary'); var accept_custom_selection = _getElements(_a + 'custom'); for(var i=0; i} */ function _getElements(data_role){ return (elem || document).querySelectorAll('a[data-cc="' + data_role + '"], button[data-cc="' + data_role + '"]'); } /** * Helper function: accept and then hide modals * @param {PointerEvent} e source event * @param {string} [accept_type] */ function _acceptAction(e, accept_type){ e.preventDefault(); _cookieconsent.accept(accept_type); _cookieconsent.hideSettings(); _cookieconsent.hide(); } } /** * Get a valid language (at least 1 must be defined) * @param {string} lang - desired language * @param {Object} all_languages - all defined languages * @returns {string} validated language */ var _getValidatedLanguage = function(lang, all_languages){ if(Object.prototype.hasOwnProperty.call(all_languages, lang)){ return lang; }else if(_getKeys(all_languages).length > 0){ if(Object.prototype.hasOwnProperty.call(all_languages, _config.current_lang)){ return _config.current_lang ; }else{ return _getKeys(all_languages)[0]; } } } /** * Save reference to first and last focusable elements inside each modal * to prevent losing focus while navigating with TAB */ var _getModalFocusableData = function(){ /** * Note: any of the below focusable elements, which has the attribute tabindex="-1" AND is either * the first or last element of the modal, won't receive focus during "open/close" modal */ var allowed_focusable_types = ['[href]', 'button', 'input', 'details', '[tabindex="0"]']; function _getAllFocusableElements(modal, _array){ var focus_later=false, focus_first=false; // ie might throw exception due to complex unsupported selector => a:not([tabindex="-1"]) try{ var focusable_elems = modal.querySelectorAll(allowed_focusable_types.join(':not([tabindex="-1"]), ')); var attr, len=focusable_elems.length, i=0; while(i < len){ attr = focusable_elems[i].getAttribute('data-focus'); if(!focus_first && attr === "1"){ focus_first = focusable_elems[i]; }else if(attr === "0"){ focus_later = focusable_elems[i]; if(!focus_first && focusable_elems[i+1].getAttribute('data-focus') !== "0"){ focus_first = focusable_elems[i+1]; } } i++; } }catch(e){ return modal.querySelectorAll(allowed_focusable_types.join(', ')); } /** * Save first and last elements (used to lock/trap focus inside modal) */ _array[0] = focusable_elems[0]; _array[1] = focusable_elems[focusable_elems.length - 1]; _array[2] = focus_later; _array[3] = focus_first; } /** * Get settings modal'S all focusable elements * Save first and last elements (used to lock/trap focus inside modal) */ _getAllFocusableElements(settings_inner, settings_modal_focusable); /** * If consent modal exists, do the same */ if(consent_modal_exists){ _getAllFocusableElements(consent_modal, consent_modal_focusable); } } var _createConsentModal = function(lang){ if(user_config['force_consent'] === true) _addClass(html_dom, 'force--consent'); // Create modal if it doesn't exist if(!consent_modal){ consent_modal = _createNode('div'); var consent_modal_inner_inner = _createNode('div'); var overlay = _createNode('div'); consent_modal.id = 'cm'; consent_modal_inner_inner.id = 'c-inr-i'; overlay.id = 'cm-ov'; consent_modal.setAttribute('role', 'dialog'); consent_modal.setAttribute('aria-modal', 'true'); consent_modal.setAttribute('aria-hidden', 'false'); consent_modal.setAttribute('aria-labelledby', 'c-ttl'); consent_modal.setAttribute('aria-describedby', 'c-txt'); // Append consent modal to main container all_modals_container.appendChild(consent_modal); all_modals_container.appendChild(overlay); /** * Make modal by default hidden to prevent weird page jumps/flashes (shown only once css is loaded) */ consent_modal.style.visibility = overlay.style.visibility = "hidden"; overlay.style.opacity = 0; } // Use insertAdjacentHTML instead of innerHTML var consent_modal_title_value = user_config.languages[lang]['consent_modal']['title']; // Add title (if valid) if(consent_modal_title_value){ if(!consent_modal_title){ consent_modal_title = _createNode('div'); consent_modal_title.id = 'c-ttl'; consent_modal_title.setAttribute('role', 'heading'); consent_modal_title.setAttribute('aria-level', '2'); consent_modal_inner_inner.appendChild(consent_modal_title); } consent_modal_title.innerHTML = consent_modal_title_value; } var description = user_config.languages[lang]['consent_modal']['description']; if(revision_enabled){ if(!valid_revision){ description = description.replace("{{revision_message}}", revision_message || user_config.languages[lang]['consent_modal']['revision_message'] || ""); }else{ description = description.replace("{{revision_message}}", ""); } } if(!consent_modal_description){ consent_modal_description = _createNode('div'); consent_modal_description.id = 'c-txt'; consent_modal_inner_inner.appendChild(consent_modal_description); } // Set description content consent_modal_description.innerHTML = description; var primary_btn_data = user_config.languages[lang]['consent_modal']['primary_btn'], // accept current selection secondary_btn_data = user_config.languages[lang]['consent_modal']['secondary_btn']; // Add primary button if not falsy if(primary_btn_data){ if(!consent_primary_btn){ consent_primary_btn = _createNode('button'); consent_primary_btn.id = 'c-p-bn'; consent_primary_btn.className = "c-bn"; var _accept_type; if(primary_btn_data['role'] === 'accept_all') _accept_type = 'all' _addEvent(consent_primary_btn, "click", function(){ _cookieconsent.hide(); _log("CookieConsent [ACCEPT]: cookie_consent was accepted!"); _cookieconsent.accept(_accept_type); }); } consent_primary_btn.innerHTML = user_config.languages[lang]['consent_modal']['primary_btn']['text']; } // Add secondary button if not falsy if(secondary_btn_data){ if(!consent_secondary_btn){ consent_secondary_btn = _createNode('button'); consent_secondary_btn.id = 'c-s-bn'; consent_secondary_btn.className = "c-bn c_link"; if(secondary_btn_data['role'] === 'accept_necessary'){ _addEvent(consent_secondary_btn, 'click', function(){ _cookieconsent.hide(); _cookieconsent.accept([]); // accept necessary only }); }else{ _addEvent(consent_secondary_btn, 'click', function(){ _cookieconsent.showSettings(0); }); } } consent_secondary_btn.innerHTML = user_config.languages[lang]['consent_modal']['secondary_btn']['text']; } // Swap buttons var gui_options_data = user_config['gui_options']; if(!consent_modal_inner){ consent_modal_inner = _createNode('div'); consent_modal_inner.id = 'c-inr'; consent_modal_inner.appendChild(consent_modal_inner_inner); } if(!consent_buttons){ consent_buttons = _createNode('div'); consent_buttons.id = "c-bns"; if(gui_options_data && gui_options_data['consent_modal'] && gui_options_data['consent_modal']['swap_buttons'] === true){ secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn); primary_btn_data && consent_buttons.appendChild(consent_primary_btn); consent_buttons.className = 'swap'; }else{ primary_btn_data && consent_buttons.appendChild(consent_primary_btn); secondary_btn_data && consent_buttons.appendChild(consent_secondary_btn); } (primary_btn_data || secondary_btn_data ) && consent_modal_inner.appendChild(consent_buttons); consent_modal.appendChild(consent_modal_inner); } consent_modal_exists = true; } var _createSettingsModal = function(lang){ /** * Create all consent_modal elements */ if(!settings_container){ settings_container = _createNode('div'); var settings_container_valign = _createNode('div'); var settings = _createNode('div'); var settings_container_inner = _createNode('div'); settings_inner = _createNode('div'); settings_title = _createNode('div'); var settings_header = _createNode('div'); settings_close_btn = _createNode('button'); var settings_close_btn_container = _createNode('div'); settings_blocks = _createNode('div'); var overlay = _createNode('div'); /** * Set ids */ settings_container.id = 's-cnt'; settings_container_valign.id = "c-vln"; settings_container_inner.id = "c-s-in"; settings.id = "cs"; settings_title.id = 's-ttl'; settings_inner.id = 's-inr'; settings_header.id = "s-hdr"; settings_blocks.id = 's-bl'; settings_close_btn.id = 's-c-bn'; overlay.id = 'cs-ov'; settings_close_btn_container.id = 's-c-bnc'; settings_close_btn.className = 'c-bn'; settings_container.setAttribute('role', 'dialog'); settings_container.setAttribute('aria-modal', 'true'); settings_container.setAttribute('aria-hidden', 'true'); settings_container.setAttribute('aria-labelledby', 's-ttl'); settings_title.setAttribute('role', 'heading'); settings_container.style.visibility = overlay.style.visibility = "hidden"; overlay.style.opacity = 0; settings_close_btn_container.appendChild(settings_close_btn); // If 'esc' key is pressed inside settings_container div => hide settings _addEvent(settings_container_valign, 'keydown', function(evt){ evt = evt || window.event; if (evt.keyCode === 27) { _cookieconsent.hideSettings(0); } }, true); _addEvent(settings_close_btn, 'click', function(){ _cookieconsent.hideSettings(0); }); }else{ new_settings_blocks = _createNode('div'); new_settings_blocks.id = 's-bl'; } // Add label to close button settings_close_btn.setAttribute('aria-label', user_config.languages[lang]['settings_modal']['close_btn_label'] || 'Close'); all_blocks = user_config.languages[lang]['settings_modal']['blocks']; all_table_headers = user_config.languages[lang]['settings_modal']['cookie_table_headers']; var n_blocks = all_blocks.length; // Set settings modal title settings_title.innerHTML = user_config.languages[lang]['settings_modal']['title']; // Create settings modal content (blocks) for(var i=0; i retrieve category states from cookie * Otherwise use states defined in the user_config. object */ if(!invalid_consent){ if(_inArray(saved_cookie_content['categories'], cookie_category) > -1){ block_switch.checked = true; !new_settings_blocks && toggle_states.push(true); }else{ !new_settings_blocks && toggle_states.push(false); } }else if(toggle_data['enabled']){ block_switch.checked = true; !new_settings_blocks && toggle_states.push(true); /** * Keep track of categories enabled by default (useful when mode=='opt-out') */ if(toggle_data['enabled']) !new_settings_blocks && default_enabled_categories.push(cookie_category); }else{ !new_settings_blocks && toggle_states.push(false); } !new_settings_blocks && all_categories.push(cookie_category); /** * Set toggle as readonly if true (disable checkbox) */ if(toggle_data['readonly']){ block_switch.disabled = true; _addClass(block_switch_span, 'c-ro'); !new_settings_blocks && readonly_categories.push(true); }else{ !new_settings_blocks && readonly_categories.push(false); } _addClass(block_table_container, 'b-acc'); _addClass(block_title_container, 'b-bn'); _addClass(block_section, 'b-ex'); block_table_container.id = accordion_id; block_table_container.setAttribute('aria-hidden', 'true'); block_switch_label.appendChild(block_switch); block_switch_label.appendChild(block_switch_span); block_switch_label.appendChild(label_text_span); block_title_container.appendChild(block_switch_label); /** * On button click handle the following :=> aria-expanded, aria-hidden and act class for current block */ isExpandable && (function(accordion, block_section, btn){ _addEvent(block_title_btn, 'click', function(){ if(!_hasClass(block_section, 'act')){ _addClass(block_section, 'act'); btn.setAttribute('aria-expanded', 'true'); accordion.setAttribute('aria-hidden', 'false'); }else{ _removeClass(block_section, 'act'); btn.setAttribute('aria-expanded', 'false'); accordion.setAttribute('aria-hidden', 'true'); } }, false); })(block_table_container, block_section, block_title_btn); }else{ /** * If block is not a button (no toggle defined), * create a simple div instead */ if(title_data){ var block_title = _createNode('div'); block_title.className = 'b-tl'; block_title.setAttribute('role', 'heading'); block_title.setAttribute('aria-level', '3'); block_title.insertAdjacentHTML('beforeend', title_data); block_title_container.appendChild(block_title); } } title_data && block_section.appendChild(block_title_container); description_data && block_table_container.appendChild(block_desc); // if cookie table found, generate table for this block if(!remove_cookie_tables && typeof cookie_table_data !== 'undefined'){ var tr_tmp_fragment = document.createDocumentFragment(); /** * Use custom table headers */ for(var p=0; p ~faster value retrieval) var curr_block = all_blocks[i]; // If current block has a toggle for opt in/out if(Object.prototype.hasOwnProperty.call(curr_block, "toggle")){ // if current block has a cookie table, an off toggle, // and its preferences were just changed => delete cookies var category_just_disabled = _inArray(changed_settings, curr_block['toggle']['value']) > -1; if( !toggle_states[++count] && Object.prototype.hasOwnProperty.call(curr_block, "cookie_table") && (clearOnFirstAction || category_just_disabled) ){ var curr_cookie_table = curr_block['cookie_table']; // Get first property name var ckey = _getKeys(all_table_headers[0])[0]; // Get number of cookies defined in cookie_table var clen = curr_cookie_table.length; // set "reload_page" to true if reload=on_disable if(curr_block['toggle']['reload'] === 'on_disable') category_just_disabled && (reload_page = true); // for each row defined in the cookie table for(var j=0; j filter cookie array if(is_regex){ for(var n=0; n -1) found_cookies.push(all_cookies_array[found_index]); } _log("CookieConsent [AUTOCLEAR]: search cookie: '" + curr_cookie_name + "', found:", found_cookies); // If cookie exists -> delete it if(found_cookies.length > 0){ _eraseCookies(found_cookies, curr_cookie_path, domains); curr_block['toggle']['reload'] === 'on_clear' && (reload_page = true); } } } } } } /** * Set toggles/checkboxes based on accepted categories and save cookie * @param {string[]} accepted_categories - Array of categories to accept */ var _saveCookiePreferences = function(accepted_categories){ changed_settings = []; // Retrieve all toggle/checkbox values var category_toggles = document.querySelectorAll('.c-tgl') || []; // If there are opt in/out toggles ... if(category_toggles.length > 0){ for(var i=0; i 0) _autoclearCookies(); if(!consent_date) consent_date = new Date(); if(!consent_uuid) consent_uuid = _uuidv4(); saved_cookie_content = { "categories": accepted_categories, "level": accepted_categories, // Copy of the `categories` property for compatibility purposes with version v2.8.0 and below. "revision": _config.revision, "data": cookie_data, "rfc_cookie": _config.use_rfc_cookie, "consent_date": consent_date.toISOString(), "consent_uuid": consent_uuid } // save cookie with preferences 'categories' (only if never accepted or settings were updated) if(invalid_consent || changed_settings.length > 0){ valid_revision = true; /** * Update "last_consent_update" only if it is invalid (after t) */ if(!last_consent_update) last_consent_update = consent_date; else last_consent_update = new Date(); saved_cookie_content['last_consent_update'] = last_consent_update.toISOString(); /** * Update accept type */ accept_type = _getAcceptType(_getCurrentCategoriesState()); _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content)); _manageExistingScripts(); } if(invalid_consent){ /** * Delete unused/"zombie" cookies if consent is not valid (not yet expressed or cookie has expired) */ if(_config.autoclear_cookies) _autoclearCookies(true); if(typeof onFirstAction === 'function') onFirstAction(_cookieconsent.getUserPreferences(), saved_cookie_content); if(typeof onAccept === 'function') onAccept(saved_cookie_content); /** * Set consent as valid */ invalid_consent = false; if(_config.mode === 'opt-in') return; } // fire onChange only if settings were changed if(typeof onChange === "function" && changed_settings.length > 0) onChange(saved_cookie_content, changed_settings); /** * reload page if needed */ if(reload_page) window.location.reload(); } /** * Returns index of found element inside array, otherwise -1 * @param {Array} arr * @param {Object} value * @returns {number} */ var _inArray = function(arr, value){ return arr.indexOf(value); } /** * Helper function which prints info (console.log()) * @param {Object} print_msg * @param {Object} [optional_param] */ var _log = function(print_msg, optional_param, error){ ENABLE_LOGS && (!error ? console.log(print_msg, optional_param !== undefined ? optional_param : ' ') : console.error(print_msg, optional_param || "")); } /** * Helper function which creates an HTMLElement object based on 'type' and returns it. * @param {string} type * @returns {HTMLElement} */ var _createNode = function(type){ var el = document.createElement(type); if(type === 'button'){ el.setAttribute('type', type); } return el; } /** * Generate RFC4122-compliant UUIDs. * https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid?page=1&tab=votes#tab-top * @returns {string} */ var _uuidv4 = function(){ return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c){ try{ return (c ^ (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) }catch(e){ return ''; } }); } /** * Resolve which language should be used. * * @param {Object} languages Object with language translations * @param {string} [requested_language] Language specified by given configuration parameters * @returns {string} */ var _resolveCurrentLang = function (languages, requested_language) { if (_config.auto_language === 'browser') { return _getValidatedLanguage(_getBrowserLang(), languages); } else if (_config.auto_language === 'document') { return _getValidatedLanguage(document.documentElement.lang, languages); } else { if (typeof requested_language === 'string') { return _config.current_lang = _getValidatedLanguage(requested_language, languages); } } _log("CookieConsent [LANG]: setting current_lang = '" + _config.current_lang + "'"); return _config.current_lang; // otherwise return default } /** * Get current client's browser language * @returns {string} */ var _getBrowserLang = function(){ var browser_lang = navigator.language || navigator.browserLanguage; browser_lang.length > 2 && (browser_lang = browser_lang[0]+browser_lang[1]); _log("CookieConsent [LANG]: detected_browser_lang = '"+ browser_lang + "'"); return browser_lang.toLowerCase() } /** * Trap focus inside modal and focus the first * focusable element of current active modal */ var _handleFocusTrap = function(){ var tabbedOutsideDiv = false; var tabbedInsideModal = false; _addEvent(document, 'keydown', function(e){ e = e || window.event; // If is tab key => ok if(e.key !== 'Tab') return; // If there is any modal to focus if(current_modal_focusable){ // If reached natural end of the tab sequence => restart if(e.shiftKey){ if (document.activeElement === current_modal_focusable[0]) { current_modal_focusable[1].focus(); e.preventDefault(); } }else{ if (document.activeElement === current_modal_focusable[1]) { current_modal_focusable[0].focus(); e.preventDefault(); } } // If have not yet used tab (or shift+tab) and modal is open ... // Focus the first focusable element if(!tabbedInsideModal && !clicked_inside_modal){ tabbedInsideModal = true; !tabbedOutsideDiv && e.preventDefault(); if(e.shiftKey){ if(current_modal_focusable[3]){ if(!current_modal_focusable[2]){ current_modal_focusable[0].focus(); }else{ current_modal_focusable[2].focus(); } }else{ current_modal_focusable[1].focus(); } }else{ if(current_modal_focusable[3]){ current_modal_focusable[3].focus(); }else{ current_modal_focusable[0].focus(); } } } } !tabbedInsideModal && (tabbedOutsideDiv = true); }); if(document.contains){ _addEvent(main_container, 'click', function(e){ e = e || window.event; /** * If click is on the foreground overlay (and not inside settings_modal), * hide settings modal * * Notice: click on div is not supported in IE */ if(settings_modal_visible){ if(!settings_inner.contains(e.target)){ _cookieconsent.hideSettings(0); clicked_inside_modal = false; }else{ clicked_inside_modal = true; } }else if(consent_modal_visible){ if(consent_modal.contains(e.target)){ clicked_inside_modal = true; } } }, true); } } /** * Manage each modal's layout * @param {Object} gui_options */ var _guiManager = function(gui_options, only_consent_modal){ // If gui_options is not object => exit if(typeof gui_options !== 'object') return; var consent_modal_options = gui_options['consent_modal']; var settings_modal_options = gui_options['settings_modal']; /** * Helper function which adds layout and * position classes to given modal * * @param {HTMLElement} modal * @param {string[]} allowed_layouts * @param {string[]} allowed_positions * @param {string} layout * @param {string[]} position */ function _setLayout(modal, allowed_layouts, allowed_positions, allowed_transitions, layout, position, transition){ position = (position && position.split(" ")) || []; // Check if specified layout is valid if(_inArray(allowed_layouts, layout) > -1){ // Add layout classes _addClass(modal, layout); // Add position class (if specified) if(!(layout === 'bar' && position[0] === 'middle') && _inArray(allowed_positions, position[0]) > -1){ for(var i=0; i -1) && _addClass(modal, transition); } if(consent_modal_exists && consent_modal_options){ _setLayout( consent_modal, ['box', 'bar', 'cloud'], ['top', 'middle', 'bottom'], ['zoom', 'slide'], consent_modal_options['layout'], consent_modal_options['position'], consent_modal_options['transition'] ); } if(!only_consent_modal && settings_modal_options){ _setLayout( settings_container, ['bar'], ['left', 'right'], ['zoom', 'slide'], settings_modal_options['layout'], settings_modal_options['position'], settings_modal_options['transition'] ); } } /** * Returns true if cookie category is accepted by the user * @param {string} cookie_category * @returns {boolean} */ _cookieconsent.allowedCategory = function(cookie_category){ if(!invalid_consent || _config.mode === 'opt-in') var allowed_categories = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || '{}')['categories'] || [] else // mode is 'opt-out' var allowed_categories = default_enabled_categories; return _inArray(allowed_categories, cookie_category) > -1; } /** * "Init" method. Will run once and only if modals do not exist */ _cookieconsent.run = function(user_config){ if(!document.getElementById('cc_div')){ // configure all parameters _setConfig(user_config); // if is bot, don't run plugin if(is_bot) return; // Retrieve cookie value (if set) saved_cookie_content = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || "{}"); // Retrieve "consent_uuid" consent_uuid = saved_cookie_content['consent_uuid']; // If "consent_uuid" is present => assume that consent was previously given var cookie_consent_accepted = consent_uuid !== undefined; // Retrieve "consent_date" consent_date = saved_cookie_content['consent_date']; consent_date && (consent_date = new Date(consent_date)); // Retrieve "last_consent_update" last_consent_update = saved_cookie_content['last_consent_update']; last_consent_update && (last_consent_update = new Date(last_consent_update)); // Retrieve "data" cookie_data = saved_cookie_content['data'] !== undefined ? saved_cookie_content['data'] : null; // If revision is enabled and current value !== saved value inside the cookie => revision is not valid if(revision_enabled && saved_cookie_content['revision'] !== _config.revision){ valid_revision = false; } // If consent is not valid => create consent modal consent_modal_exists = invalid_consent = (!cookie_consent_accepted || !valid_revision || !consent_date || !last_consent_update || !consent_uuid); // Generate cookie-settings dom (& consent modal) _createCookieConsentHTML(); _getModalFocusableData(); _guiManager(user_config['gui_options']); _addDataButtonListeners(); if(_config.autorun && consent_modal_exists){ _cookieconsent.show(user_config['delay'] || 0); } // Add class to enable animations/transitions setTimeout(function(){_addClass(main_container, 'c--anim');}, 30); // Accessibility :=> if tab pressed => trap focus inside modal setTimeout(function(){_handleFocusTrap();}, 100); // If consent is valid if(!invalid_consent){ var rfc_prop_exists = typeof saved_cookie_content['rfc_cookie'] === "boolean"; /* * Convert cookie to rfc format (if `use_rfc_cookie` is enabled) */ if(!rfc_prop_exists || (rfc_prop_exists && saved_cookie_content['rfc_cookie'] !== _config.use_rfc_cookie)){ saved_cookie_content['rfc_cookie'] = _config.use_rfc_cookie; _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content)); } /** * Update accept type */ accept_type = _getAcceptType(_getCurrentCategoriesState()); _manageExistingScripts(); if(typeof onAccept === 'function') onAccept(saved_cookie_content); _log("CookieConsent [NOTICE]: consent already given!", saved_cookie_content); }else{ if(_config.mode === 'opt-out'){ _log("CookieConsent [CONFIG] mode='" + _config.mode + "', default enabled categories:", default_enabled_categories); _manageExistingScripts(default_enabled_categories); } _log("CookieConsent [NOTICE]: ask for consent!"); } }else{ _log("CookieConsent [NOTICE]: cookie consent already attached to body!"); } } /** * Show settings modal (with optional delay) * @param {number} delay */ _cookieconsent.showSettings = function(delay){ setTimeout(function() { _addClass(html_dom, "show--settings"); settings_container.setAttribute('aria-hidden', 'false'); settings_modal_visible = true; /** * Set focus to the first focusable element inside settings modal */ setTimeout(function(){ // If there is no consent-modal, keep track of the last focused elem. if(!consent_modal_visible){ last_elem_before_modal = document.activeElement; }else{ last_consent_modal_btn_focus = document.activeElement; } if (settings_modal_focusable.length === 0) return; if(settings_modal_focusable[3]){ settings_modal_focusable[3].focus(); }else{ settings_modal_focusable[0].focus(); } current_modal_focusable = settings_modal_focusable; }, 200); _log("CookieConsent [SETTINGS]: show settings_modal"); }, delay > 0 ? delay : 0); } /** * This function handles the loading/activation logic of the already * existing scripts based on the current accepted cookie categories * * @param {string[]} [must_enable_categories] */ var _manageExistingScripts = function(must_enable_categories){ if(!_config.page_scripts) return; // get all the scripts with "cookie-category" attribute var scripts = document.querySelectorAll('script[' + _config.script_selector + ']'); var accepted_categories = must_enable_categories || saved_cookie_content['categories'] || []; /** * Load scripts (sequentially), using a recursive function * which loops through the scripts array * @param {Element[]} scripts scripts to load * @param {number} index current script to load */ var _loadScripts = function(scripts, index){ if(index < scripts.length){ var curr_script = scripts[index]; var curr_script_category = curr_script.getAttribute(_config.script_selector); /** * If current script's category is on the array of categories * accepted by the user => load script */ if(_inArray(accepted_categories, curr_script_category) > -1){ curr_script.type = 'text/javascript'; curr_script.removeAttribute(_config.script_selector); // get current script data-src var src = curr_script.getAttribute('data-src'); // some scripts (like ga) might throw warning if data-src is present src && curr_script.removeAttribute('data-src'); // create fresh script (with the same code) var fresh_script = _createNode('script'); fresh_script.textContent = curr_script.innerHTML; // Copy attributes over to the new "revived" script (function(destination, source){ var attributes = source.attributes; var len = attributes.length; for(var i=0; i the next script will not be loaded // until the current's script onload event triggers if(fresh_script.readyState) { // only required for IE <9 fresh_script.onreadystatechange = function() { if (fresh_script.readyState === "loaded" || fresh_script.readyState === "complete" ) { fresh_script.onreadystatechange = null; _loadScripts(scripts, ++index); } }; }else{ // others fresh_script.onload = function(){ fresh_script.onload = null; _loadScripts(scripts, ++index); }; } } // Replace current "sleeping" script with the new "revived" one curr_script.parentNode.replaceChild(fresh_script, curr_script); /** * If we managed to get here and scr is still set, it means that * the script is loading/loaded sequentially so don't go any further */ if(src) return; } // Go to next script right away _loadScripts(scripts, ++index); } } _loadScripts(scripts, 0); } /** * Save custom data inside cookie * @param {object|string} new_data * @param {string} [mode] * @returns {boolean} */ var _setCookieData = function(new_data, mode){ var set = false; /** * If mode is 'update': * add/update only the specified props. */ if(mode === 'update'){ cookie_data = _cookieconsent.get('data'); var same_type = typeof cookie_data === typeof new_data; if(same_type && typeof cookie_data === "object"){ !cookie_data && (cookie_data = {}); for(var prop in new_data){ if(cookie_data[prop] !== new_data[prop]){ cookie_data[prop] = new_data[prop] set = true; } } }else if((same_type || !cookie_data) && cookie_data !== new_data){ cookie_data = new_data; set = true; } }else{ cookie_data = new_data; set = true; } if(set){ saved_cookie_content['data'] = cookie_data; _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content)); } return set; } /** * Helper method to set a variety of fields * @param {string} field * @param {object} data * @returns {boolean} */ _cookieconsent.set = function(field, data){ switch(field){ case 'data': return _setCookieData(data['value'], data['mode']); default: return false; } } /** * Retrieve data from existing cookie * @param {string} field * @param {string} [cookie_name] * @returns {any} */ _cookieconsent.get = function(field, cookie_name){ var cookie = JSON.parse(_getCookie(cookie_name || _config.cookie_name, 'one', true) || "{}"); return cookie[field]; } /** * Read current configuration value * @returns {any} */ _cookieconsent.getConfig = function(field){ return _config[field] || user_config[field]; } /** * Obtain accepted and rejected categories * @returns {{accepted: string[], rejected: string[]}} */ var _getCurrentCategoriesState = function(){ // get accepted categories accepted_categories = saved_cookie_content['categories'] || []; // calculate rejected categories (all_categories - accepted_categories) rejected_categories = all_categories.filter(function(category){ return (_inArray(accepted_categories, category) === -1); }); return { accepted: accepted_categories, rejected: rejected_categories } } /** * Calculate "accept type" given current categories state * @param {{accepted: string[], rejected: string[]}} currentCategoriesState * @returns {string} */ var _getAcceptType = function(currentCategoriesState){ var type = 'custom'; // number of categories marked as necessary/readonly var necessary_categories_length = readonly_categories.filter(function(readonly){ return readonly === true; }).length; // calculate accept type based on accepted/rejected categories if(currentCategoriesState.accepted.length === all_categories.length) type = 'all'; else if(currentCategoriesState.accepted.length === necessary_categories_length) type = 'necessary' return type; } /** * @typedef {object} userPreferences * @property {string} accept_type * @property {string[]} accepted_categories * @property {string[]} rejected_categories */ /** * Retrieve current user preferences (summary) * @returns {userPreferences} */ _cookieconsent.getUserPreferences = function(){ var currentCategoriesState = _getCurrentCategoriesState(); var accept_type = _getAcceptType(currentCategoriesState); return { 'accept_type': accept_type, 'accepted_categories': currentCategoriesState.accepted, 'rejected_categories': currentCategoriesState.rejected } } /** * Function which will run after script load * @callback scriptLoaded */ /** * Dynamically load script (append to head) * @param {string} src * @param {scriptLoaded} callback * @param {object[]} [attrs] Custom attributes */ _cookieconsent.loadScript = function(src, callback, attrs){ var function_defined = typeof callback === 'function'; // Load script only if not already loaded if(!document.querySelector('script[src="' + src + '"]')){ var script = _createNode('script'); // if an array is provided => add custom attributes if(attrs && attrs.length > 0){ for(var i=0; i run callback onload if(function_defined){ script.onload = callback; } script.src = src; /** * Append script to head */ document.head.appendChild(script); }else{ function_defined && callback(); } } /** * Manage dynamically loaded scripts: https://github.com/orestbida/cookieconsent/issues/101 * If plugin has already run, call this method to enable * the newly added scripts based on currently selected preferences */ _cookieconsent.updateScripts = function(){ _manageExistingScripts(); } /** * Show cookie consent modal (with delay parameter) * @param {number} [delay] * @param {boolean} [create_modal] create modal if it doesn't exist */ _cookieconsent.show = function(delay, create_modal){ if(create_modal === true) _createConsentModal(_config.current_lang); if(consent_modal_exists){ setTimeout(function() { _addClass(html_dom, "show--consent"); /** * Update attributes/internal statuses */ consent_modal.setAttribute('aria-hidden', 'false'); consent_modal_visible = true; setTimeout(function(){ last_elem_before_modal = document.activeElement; current_modal_focusable = consent_modal_focusable; }, 200); _log("CookieConsent [MODAL]: show consent_modal"); }, delay > 0 ? delay : (create_modal ? 30 : 0)); } } /** * Hide consent modal */ _cookieconsent.hide = function(){ if(consent_modal_exists){ _removeClass(html_dom, "show--consent"); consent_modal.setAttribute('aria-hidden', 'true'); consent_modal_visible = false; setTimeout(function(){ //restore focus to the last page element which had focus before modal opening last_elem_before_modal.focus(); current_modal_focusable = null; }, 200); _log("CookieConsent [MODAL]: hide"); } } /** * Hide settings modal */ _cookieconsent.hideSettings = function(){ _removeClass(html_dom, "show--settings"); settings_modal_visible = false; settings_container.setAttribute('aria-hidden', 'true'); setTimeout(function(){ /** * If consent modal is visible, focus him (instead of page document) */ if(consent_modal_visible){ last_consent_modal_btn_focus && last_consent_modal_btn_focus.focus(); current_modal_focusable = consent_modal_focusable; }else{ /** * Restore focus to last page element which had focus before modal opening */ last_elem_before_modal && last_elem_before_modal.focus(); current_modal_focusable = null; } clicked_inside_modal = false; }, 200); _log("CookieConsent [SETTINGS]: hide settings_modal"); } /** * Accept cookieconsent function API * @param {string[]|string} _categories - Categories to accept * @param {string[]} [_exclusions] - Excluded categories [optional] */ _cookieconsent.accept = function(_categories, _exclusions){ var categories = _categories || undefined; var exclusions = _exclusions || []; var to_accept = []; /** * Get all accepted categories * @returns {string[]} */ var _getCurrentPreferences = function(){ var toggles = document.querySelectorAll('.c-tgl') || []; var states = []; for(var i=0; i= 1){ for(i=0; i 0){ for(var i=0; i<_cookies.length; i++){ this.validCookie(_cookies[i]) && cookies.push(_cookies[i]); } }else{ this.validCookie(_cookies) && cookies.push(_cookies); } _eraseCookies(cookies, _path, domains); } /** * Set cookie, by specifying name and value * @param {string} name * @param {string} value */ var _setCookie = function(name, value) { var cookie_expiration = _config.cookie_expiration; if(typeof _config.cookie_necessary_only_expiration === 'number' && accept_type === 'necessary') cookie_expiration = _config.cookie_necessary_only_expiration; value = _config.use_rfc_cookie ? encodeURIComponent(value) : value; var date = new Date(); date.setTime(date.getTime() + (1000 * (cookie_expiration * 24 * 60 * 60))); var expires = "; expires=" + date.toUTCString(); var cookieStr = name + "=" + (value || "") + expires + "; Path=" + _config.cookie_path + ";"; cookieStr += " SameSite=" + _config.cookie_same_site + ";"; // assures cookie works with localhost (=> don't specify domain if on localhost) if(window.location.hostname.indexOf(".") > -1){ cookieStr += " Domain=" + _config.cookie_domain + ";"; } if(window.location.protocol === "https:") { cookieStr += " Secure;"; } document.cookie = cookieStr; _log("CookieConsent [SET_COOKIE]: '" + name + "' expires after " + cookie_expiration + " day(s)"); } /** * Get cookie value by name, * returns the cookie value if found (or an array * of cookies if filter provided), otherwise empty string: "" * @param {string} name * @param {string} filter 'one' or 'all' * @param {boolean} [get_value] set to true to obtain its value * @returns {string|string[]} */ var _getCookie = function(name, filter, get_value) { var found; if(filter === 'one'){ found = document.cookie.match("(^|;)\\s*" + name + "\\s*=\\s*([^;]+)"); found = found ? (get_value ? found.pop() : name) : ""; if(found && name === _config.cookie_name){ try{ found = JSON.parse(found) }catch(e){ try { found = JSON.parse(decodeURIComponent(found)) } catch (e) { // if I got here => cookie value is not a valid json string found = {}; } } found = JSON.stringify(found); } }else if(filter === 'all'){ // array of names of all existing cookies var cookies = document.cookie.split(/;\s*/); found = []; for(var i=0; i