/*!
* CookieConsent v2.8.9
* https://www.github.com/orestbida/cookieconsent
* Author Orest Bida
* Released under the MIT License
*/
(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;
_addDataButtonListeners(consent_modal_inner);
}
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, curr_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