1'use strict'; 2/* 3 * Opt in (e.g. EU): 4 * default all scripts disabled. 5 * cookie banner 6 * 7 * Opt out (e.g. US): 8 * default all scripts enabled 9 * opt out cookie banner 10 * 11 * Other regions: 12 * default all scripts enabled 13 * no banner 14 * 15 * 16 * For examples to edit the behaviour of the banner, please see https://github.com/really-Simple-Plugins/complianz-integrations 17 * */ 18 19// Cookie cleanup data - declared at top level to avoid TDZ issues 20let cmplz_cookie_data = []; 21let cmplzCleanCookieInterval; 22 23/* 24 * Debug logger. Only emits when SCRIPT_DEBUG is enabled (complianz.debug), 25 * so diagnostics stay available for developers without spamming visitors. 26 * @param args 27 */ 28function cmplz_log( ...args ) { 29 if ( typeof complianz === 'undefined' || ! complianz.debug ) { 30 return; 31 } 32 // eslint-disable-next-line no-console 33 console.warn( ...args ); 34} 35 36/* 37 * Create an element 38 * @param el 39 * @param content 40 * @returns {*} 41 */ 42function cmplz_create_element( el, content ) { 43 const obj = document.createElement( el ); 44 obj.innerHtml = content; 45 return obj; 46} 47 48/* 49 * Add an event 50 * @param event 51 * @param selector 52 * @param callback 53 * @param context 54 */ 55function cmplz_add_event( event, selector, callback ) { 56 document.addEventListener( event, ( e ) => { 57 if ( e.target.closest( selector ) ) { 58 callback( e ); 59 } 60 } ); 61} 62 63/* 64 * Check if the element is hidden 65 * @param el 66 * @returns {boolean} 67 */ 68function cmplz_is_hidden( el ) { 69 return ( el.offsetParent === null ); 70} 71 72function cmplz_html_decode( input ) { 73 const doc = new DOMParser().parseFromString( input, 'text/html' ); 74 return doc.documentElement.textContent; 75} 76 77/* 78 * Sanitize placeholder HTML before injecting it into DOM. 79 * Keeps expected markup but removes scripts and risky attributes. 80 */ 81function cmplz_sanitize_placeholder_html( input ) { 82 const template = document.createElement( 'template' ); 83 template.innerHTML = String( input || '' ); 84 85 // Remove active content elements entirely. 86 template.content.querySelectorAll( 'script, iframe, object, embed, link, style, base, meta' ).forEach( ( el ) => { 87 el.remove(); 88 } ); 89 90 template.content.querySelectorAll( '*' ).forEach( ( el ) => { 91 Array.from( el.attributes ).forEach( ( attr ) => { 92 const attrName = attr.name.toLowerCase(); 93 94 // Remove inline event handlers like onclick. 95 if ( attrName.indexOf( 'on' ) === 0 ) { 96 el.removeAttribute( attr.name ); 97 return; 98 } 99 100 // Block javascript: and data: URLs on URL-based attributes. 101 const attrValue = attr.value.trim(); 102 if ( ( attrName === 'href' || attrName === 'src' || attrName === 'xlink:href' ) && 103 /^\s*(javascript:|data:)/i.test( attrValue ) ) { 104 el.removeAttribute( attr.name ); 105 } 106 } ); 107 } ); 108 109 return template.innerHTML; 110} 111 112/* 113 * Validate dynamic script URLs before assigning to script.src. 114 * Only allow http(s) URLs (including relative URLs resolved by URL()). 115 */ 116function cmplz_is_safe_script_src( scriptUrl ) { 117 if ( typeof scriptUrl !== 'string' ) { 118 return false; 119 } 120 121 const trimmed = scriptUrl.trim(); 122 if ( ! trimmed ) { 123 return false; 124 } 125 126 try { 127 const parsedUrl = new URL( trimmed, window.location.href ); 128 return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'; 129 } catch ( e ) { 130 return false; 131 } 132} 133 134/* 135 * Focus trap functions for cookie banner modal 136 */ 137 138function cmplz_trap_focus() { 139 if ( ! cmplz_banner ) { 140 return; 141 } 142 143 // Store current active element 144 cmplz_original_active_element = cmplz_banner.ownerDocument.activeElement; 145 146 // Add class to body for CSS targeting 147 if ( complianz.soft_cookiewall ) { 148 document.body.classList.add( 'cmplz-banner-active' ); 149 } 150 151 // Add keydown listener at document level so it fires regardless of propagation 152 document.addEventListener( 'keydown', cmplz_handle_tab_navigation ); 153 154 // Focus first focusable element in banner 155 const focusableElements = cmplz_banner.querySelectorAll( 156 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 157 ); 158 if ( focusableElements.length > 0 ) { 159 focusableElements[ 0 ].focus(); 160 } 161} 162 163function cmplz_handle_tab_navigation( e ) { 164 if ( e.key !== 'Tab' || ! cmplz_banner ) { 165 return; 166 } 167 168 const focusableElements = Array.from( cmplz_banner.querySelectorAll(
169 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])' 170 ) ).filter( function( el ) { 171 return el.offsetParent !== null; 172 } ); 173 174 if ( focusableElements.length === 0 ) { 175 e.preventDefault(); 176 return; 177 } 178 179 const firstElement = focusableElements[ 0 ]; 180 const lastElement = focusableElements[ focusableElements.length - 1 ]; 181 const activeElement = cmplz_banner.ownerDocument.activeElement; 182 const focusIsInBanner = cmplz_banner.contains( activeElement ); 183 184 if ( e.shiftKey ) { 185 if ( ! focusIsInBanner || activeElement === firstElement ) { 186 e.preventDefault(); 187 lastElement.focus(); 188 } 189 } else if ( ! focusIsInBanner || activeElement === lastElement ) { 190 e.preventDefault(); 191 firstElement.focus(); 192 } 193} 194 195function cmplz_release_focus_trap() { 196 document.removeEventListener( 'keydown', cmplz_handle_tab_navigation ); 197 198 // Remove class from body 199 document.body.classList.remove( 'cmplz-banner-active' ); 200 201 const triggerSource = cmplz_banner_trigger_source; 202 cmplz_banner_trigger_source = 'page-load'; 203 204 if ( triggerSource === 'manage-consent' ) { 205 // Return focus to the button that opened the dialog 206 if ( cmplz_original_active_element && cmplz_original_active_element.focus ) { 207 try { 208 cmplz_original_active_element.focus(); 209 } catch ( e ) { 210 document.body.focus(); 211 } 212 } 213 } else if ( cmplz_last_input_was_keyboard ) { 214 // Page-load + keyboard: move focus to the skip-navigation link so keyboard 215 // users can proceed. Skipped for mouse/touch to avoid a visible skip-link flash. 216 const skipLink = document.querySelector( 217 'a[href="#content"], a[href="#main"], a[href="#main-content"], .skip-link' 218 ); 219 if ( skipLink ) { 220 skipLink.focus(); 221 } else { 222 document.body.focus(); 223 } 224 } 225} 226/** 227 * Consent Area management for shortcode and block editor 228 */ 229document.querySelectorAll( '.cmplz-consent-area.cmplz-placeholder' ).forEach( ( cmplzConsentArea ) => { 230 cmplzConsentArea.addEventListener( 'click', ( e ) => { 231 let container = e.target; 232 if ( ! container.classList.contains( 'cmplz-consent-area' ) ) { 233 container = e.target.closest( '.cmplz-consent-area.cmplz-placeholder' ); 234 } 235 if ( container ) { 236 const consentedService = container.getAttribute( 'data-service' ); 237 cmplz_set_service_consent( consentedService, true ); 238 cmplzLoadConsentAreaContent( false, consentedService ); 239 cmplz_enable_category( null, consentedService ); 240 cmplz_set_banner_status( 'dismissed' ); 241 } 242 } ); 243 244 document.addEventListener( 'cmplz_enable_category', function( consentData ) { 245 const consentedCategory = consentData.detail.category; 246 const consentedService = consentData.detail.service; 247 cmplzLoadConsentAreaContent( consentedCategory, consentedService ); 248 } ); 249} ); 250 251function cmplzLoadConsentAreaContent( consentedCategory, consentedService ) { 252 document.querySelectorAll( '.cmplz-consent-area.cmplz-placeholder' ).forEach( ( obj ) => { 253 const category = obj.getAttribute( 'data-category' ); 254 const service = obj.getAttribute( 'data-service' ); 255 const postId = obj.getAttribute( 'data-post_id' ); 256 const blockId = obj.getAttribute( 'data-block_id' ); 257 if ( consentedCategory === category || consentedService === service ) { 258 //if not stored yet, load. As features in the user object can be changed on updates, we also check for the version 259 const request = new XMLHttpRequest(); 260 request.open( 'GET', complianz.url + 'consent-area/' + postId + '/' + blockId, true ); 261 request.setRequestHeader( 'Content-type', 'application/json' ); 262 request.send(); 263 obj.classList.remove( 'cmplz-placeholder' ); 264 request.onload = function() { 265 obj.innerHTML = JSON.parse( request.response ); 266 //search for script elements and execute them 267 obj.querySelectorAll( 'script' ).forEach( ( scriptEl ) => { 268 cmplz_run_script( scriptEl.innerHTML, category, service, 'inline', scriptEl ); 269 } ); 270 }; 271 } 272 } ); 273} 274 275/* 276 * If an anchor is passed for an element which may load only after an ajax call, make sure it will scroll into view. 277 */ 278document.addEventListener( 'cmplz_manage_consent_container_loaded', function() { 279 const url = window.location.href; 280 if ( url.indexOf( '#' ) !== -1 ) { 281 const end_pos = url.lastIndexOf( '?' ) !== -1 ? url.lastIndexOf( '?' ) : undefined; 282 const anchor = url.substring( url.indexOf( '#' ) + 1, end_pos ); 283 const element = document.getElementById( anchor ); 284 if ( element ) { 285 const y = element.getBoundingClientRect().top + window.pageYOffset - 200; 286 window.scrollTo( { top: y, behavior: 'smooth' } ); 287 } 288 } 289} ); 290 291/* 292 * prevent caching of the WP Rest API by varnish or other caching tools 293 */
294complianz.locale = complianz.locale + '&token=' + Math.random().toString( 36 ).replace( /[^a-z]+/g, '' ).substr( 0, 5 ); 295 296/* 297 * CustomEvent() polyfill 298 * https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill 299 */ 300( function() { 301 if ( typeof window.CustomEvent === 'function' ) { 302 return false; 303 } 304 function CustomEvent( event, params ) { 305 params = params || { bubbles: false, cancelable: false, detail: undefined }; 306 const evt = document.createEvent( 'CustomEvent' ); 307 evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); 308 return evt; 309 } 310 CustomEvent.prototype = window.Event.prototype; 311 window.CustomEvent = CustomEvent; 312}() ); 313 314let cmplz_banner;//look this one up when the cookiebanner loads. 315const cmplz_banner_container = document.getElementById( 'cmplz-cookiebanner-container' ); 316let cmplz_manage_consent_button; 317const cmplz_waiting_inline_scripts = []; 318const cmplz_waiting_scripts = []; 319const cmplz_fired_scripts = []; 320let cmplz_placeholder_class_index = 0; 321let cmplz_all_scripts_hook_fired = false; 322let cmplz_consent_stored_once = false; 323const cmplz_fired_category_events = [ 'functional' ]; 324const cmplz_fired_service_events = []; 325const cmplz_categories = [ 326 'functional', 327 'preferences', 328 'statistics', 329 'marketing', 330]; 331 332// Focus trap variables 333let cmplz_original_active_element = null; 334let cmplz_banner_trigger_source = 'page-load'; // 'page-load' | 'manage-consent' 335 336// Track the last input modality so we only move focus to the skip link when 337// the banner was dismissed via keyboard. Mouse/touch users keep their focus 338// where the click left it, avoiding a visible skip-link flash. 339let cmplz_last_input_was_keyboard = false; 340document.addEventListener( 'keydown', function() { 341 cmplz_last_input_was_keyboard = true; 342}, true ); 343document.addEventListener( 'pointerdown', function() { 344 cmplz_last_input_was_keyboard = false; 345}, true ); 346 347/* 348 * Get a cookie by name 349 * @param name 350 * @returns {string} 351 */ 352 353window.cmplz_get_cookie = function( name ) { 354 if ( typeof document === 'undefined' ) { 355 return ''; 356 } 357 name = complianz.prefix + name; 358 const cookies = document.cookie.split( ';' ); 359 for ( let i = 0; i < cookies.length; i++ ) { 360 const cookie = cookies[ i ].trim(); 361 if ( cookie.startsWith( name + '=' ) ) { 362 return cookie.substring( name.length + 1 ); 363 } 364 } 365 return ''; 366}; 367 368/* 369 * set a cookie 370 * @param name 371 * @param value 372 * @param use_prefix 373 */ 374 375window.cmplz_set_cookie = function( name, value, use_prefix ) { 376 if ( typeof document === 'undefined' ) { 377 return; 378 } 379 380 use_prefix = typeof use_prefix !== 'undefined' ? use_prefix : true; 381 const secure = window.location.protocol === 'https:' ? ';secure' : ''; 382 const date = new Date(); 383 date.setTime( date.getTime() + ( complianz.cookie_expiry * 24 * 60 * 60 * 1000 ) ); 384 const expires = ';expires=' + date.toGMTString(); 385 386 const domain = cmplz_get_cookie_domain(); 387 const domainString = domain.length > 0 ? `;domain=${ domain }` : ''; 388 389 const prefix = use_prefix ? complianz.prefix : ''; 390 const cookiePath = cmplz_get_cookie_path(); 391 392 document.cookie = `${ prefix }${ name }=${ value };SameSite=Lax${ secure }${ expires }${ domainString };path=${ cookiePath }`; 393}; 394 395/* 396 * Check if needle occurs in the haystack 397 * @param needle 398 * @param haystack 399 * @returns {boolean} 400 */ 401window.cmplz_in_array = function( needle, haystack ) { 402 return haystack.includes( needle ); 403}; 404 405/* 406 * Retrieve the highest level of consent that has been given 407 * 408 * */ 409 410window.cmplz_highest_accepted_category = function() { 411 const consentedCategories = cmplz_accepted_categories(); 412 const priorityCategories = [ 'marketing', 'statistics', 'preferences' ]; 413 414 for ( let i = 0; i < priorityCategories.length; i++ ) { 415 if ( cmplz_in_array( priorityCategories[ i ], consentedCategories ) ) { 416 return priorityCategories[ i ]; 417 } 418 } 419 return 'functional'; 420}; 421 422/* 423 * Sets all accepted categories as class in body 424 */ 425 426const cmplz_set_category_as_body_class = () => { 427 const classList = document.body.classList; 428 for ( let i = classList.length - 1; i >= 0; i-- ) { 429 if ( classList[ i ].startsWith( 'cmplz-' ) && classList[ i ] !== 'cmplz-document' ) { 430 classList.remove( classList[ i ] ); 431 } 432 } 433 const cats = cmplz_accepted_categories(); 434 Object.values( cats ).forEach( ( category ) => { 435 if ( typeof category === 'string' ) { 436 classList.add( 'cmplz-' + category ); 437 } 438 } ); 439 const services = cmplz_get_all_service_consents(); 440 Object.entries( services ).forEach( ( [ service, consent ] ) => { 441 if ( consent ) { 442 classList.add( 'cmplz-' + service ); 443 } 444 } ); 445 446 classList.add( 'cmplz-' + complianz.region, 'cmplz-' + complianz.consenttype ); 447 const event = new CustomEvent( 'cmplz_set_category_as_bodyclass' ); 448 document.dispatchEvent( event ); 449}; 450 451const cmplz_append_css = ( css ) => { 452 const head = document.head || document.getElementsByTagName( 'head' )[ 0 ]; 453 const style = document.createElement( 'style' ); 454 style.setAttribute( 'type', 'text/css' ); 455 style.appendChild( document.createTextNode( css ) ); 456 head.appendChild( style ); 457}; 458 459const cmplz_load_css = ( path ) => { 460 const head = document.head || document.getElementsByTagName( 'head' )[ 0 ]; 461 const targetObj = document.createElement( 'link' ); 462 targetObj.rel = 'stylesheet'; 463 targetObj.type = 'text/css'; 464 targetObj.href = path; 465 head.appendChild( targetObj ); 466}; 467 468/* 469 * Run script, src or inline 470 * @param script //src or inline script 471 * @param category 472 * @param type 473 */ 474 475function cmplz_run_script( script, category, service, type, sourceObj ) { 476 const targetObj = document.createElement( 'script' );
477 if ( type !== 'inline' && cmplz_is_safe_script_src( script ) ) { 478 targetObj.src = script; 479 } else { 480 if ( typeof script !== 'string' ) { 481 script = script.innerHTML; 482 } 483 targetObj.textContent = [ script, 'cmplzScriptLoaded();' ].join( '\n' ); 484 } 485 486 //check if already fired 487 if ( cmplz_in_array( script, cmplz_fired_scripts ) ) { 488 return; 489 } 490 491 cmplzCopyAttributes( sourceObj, targetObj ); 492 try { 493 if ( type !== 'inline' ) { 494 targetObj.onload = function() { 495 cmplz_run_after_all_scripts( category, service ); 496 cmplz_maybe_run_waiting_scripts( script, category, service, sourceObj ); 497 }; 498 } else { 499 window.cmplzScriptLoaded = function() { 500 cmplz_run_after_all_scripts( category, service ); 501 cmplz_maybe_run_waiting_scripts( script, category, service, sourceObj ); 502 }; 503 } 504 document.head.appendChild( targetObj ); 505 } catch ( exception ) { 506 //only runs in case of error 507 cmplz_run_after_all_scripts( category, service ); 508 throw 'Something went wrong ' + exception + ' while loading ' + script; 509 } 510} 511 512/* 513 * Check if there are waiting scripts, and if so, run them. 514 * @param script //src or inline script 515 * @param category 516 */ 517 518function cmplz_maybe_run_waiting_scripts( script, category, service, sourceObj ) { 519 const waitingScript = cmplz_get_waiting_script( cmplz_waiting_scripts, script ); 520 if ( waitingScript ) { 521 cmplz_run_script( waitingScript, category, service, 'src', sourceObj ); 522 } 523 524 const waiting_inline_script = cmplz_get_waiting_script( cmplz_waiting_inline_scripts, script ); 525 if ( waiting_inline_script ) { 526 cmplz_run_script( waiting_inline_script, category, service, 'inline', sourceObj ); 527 } 528} 529 530const cmplzLazyLoader = () => { 531 // Get all elements with the "lazy-load" class 532 const cmplzLazyLoadElements = document.querySelectorAll( '.cmplz-blocked-content-container' ); 533 534 // Create an Intersection Observer instance 535 const cmplzObserver = new IntersectionObserver( ( entries, observer ) => { 536 entries.forEach( ( entry ) => { 537 if ( entry.isIntersecting ) { 538 // When the element is in view, load the background image 539 const element = entry.target; 540 const src = element.getAttribute( 'data-placeholder-image' ); 541 if ( src ) { 542 const index = element.getAttribute( 'data-placeholder_class_index' ); 543 cmplz_append_css( '.cmplz-placeholder-' + index + ' {background-image: url(' + src + ') !important;}' ); 544 cmplz_set_blocked_content_container_aspect_ratio( element, src, index ); 545 } 546 547 // Stop observing the element 548 observer.unobserve( element ); 549 } 550 } ); 551 } ); 552 553 // Start observing each lazy-load element 554 cmplzLazyLoadElements.forEach( ( element ) => { 555 cmplzObserver.observe( element ); 556 } ); 557}; 558 559/* 560 * Set placeholder image as background on the parent div, set notice, and handle height. 561 * 562 * */ 563 564function cmplz_set_blocked_content_container() { 565 //to prevent this function to twice run on an element, we add an attribute to each element that has been processed. 566 //then skip elements with that element. 567 document.querySelectorAll( '.cmplz-image' ).forEach( ( obj ) => { 568 if ( obj.classList.contains( 'cmplz-processed' ) ) { 569 return; 570 } 571 obj.classList.add( 'cmplz-processed' ); 572 const service = obj.getAttribute( 'data-service' ); 573 const category = obj.getAttribute( 'data-category' ); 574 const blocked_image_container = obj.parentElement; 575 blocked_image_container.classList.add( 'cmplz-blocked-content-container' ); 576 const curIndex = blocked_image_container.getAttribute( 'data-placeholder_class_index' ); 577 //handle browser native lazy load feature 578 if ( obj.getAttribute( 'loading' ) === 'lazy' ) { 579 obj.removeAttribute( 'loading' ); 580 obj.setAttribute( 'data-deferlazy', 1 ); 581 } 582 583 if ( curIndex === null ) { 584 cmplz_placeholder_class_index++; 585 blocked_image_container.classList.add( 'cmplz-placeholder-' + cmplz_placeholder_class_index, 'cmplz-blocked-content-container' ); 586 blocked_image_container.setAttribute( 'data-placeholder_class_index', cmplz_placeholder_class_index ); 587 cmplz_insert_placeholder_text( blocked_image_container, category, service ); 588 } 589 } ); 590 591 document.querySelectorAll( '.cmplz-placeholder-element' ).forEach( ( obj ) => { 592 if ( obj.classList.contains( 'cmplz-processed' ) ) { 593 return; 594 } 595 obj.classList.add( 'cmplz-processed' ); 596 const service = obj.getAttribute( 'data-service' ); 597 const category = obj.getAttribute( 'data-category' ); 598 599 //we set this element as container with placeholder image 600 let blocked_content_container; 601 if ( obj.classList.contains( 'cmplz-iframe' ) ) { 602 //handle browser native lazy load feature 603 if ( obj.getAttribute( 'loading' ) === 'lazy' ) { 604 obj.removeAttribute( 'loading' ); 605 obj.setAttribute( 'data-deferlazy', 1 ); 606 } 607 blocked_content_container = obj.parentElement; 608 } else { 609 blocked_content_container = obj; 610 } 611 const curIndex = blocked_content_container.getAttribute( 'data-
611placeholder_class_index' ); 612 //if the blocked content container class is already added, don't add it again 613 if ( curIndex === null ) { 614 cmplz_placeholder_class_index++; 615 blocked_content_container.classList.add( 'cmplz-placeholder-' + cmplz_placeholder_class_index, 'cmplz-blocked-content-container' ); 616 blocked_content_container.setAttribute( 'data-placeholder_class_index', cmplz_placeholder_class_index ); 617 cmplz_insert_placeholder_text( blocked_content_container, category, service ); 618 //handle image size for video 619 const src = obj.getAttribute( 'data-placeholder-image' ); 620 if ( src && typeof src !== 'undefined' && src.length ) { 621 //move src to parent, if needed. 622 blocked_content_container.setAttribute( 'data-placeholder-image', src ); 623 } 624 } 625 } ); 626 627 cmplzLazyLoader(); 628 629 /* 630 * In some cases, like ajax loaded content, the placeholders are initialized again. In that case, the scripts may need to be fired again as well. 631 * In case of an opt-out region and Do Not Track, a consent check will return 'allow', because it doesn't take DNT into account 632 */ 633 634 if ( cmplz_has_consent( 'statistics' ) ) { 635 cmplz_enable_category( 'statistics' ); 636 } 637 638 if ( cmplz_has_consent( 'marketing' ) ) { 639 cmplz_enable_category( 'marketing' ); 640 } 641} 642 643function cmplz_insert_placeholder_text( container, category, service ) { 644 if ( ! container.querySelector( '.cmplz-blocked-content-notice' ) ) { 645 let placeholder_text = complianz.placeholdertext; 646 category = category || 'marketing'; 647 let body; 648 if ( typeof placeholder_text !== 'undefined' ) { 649 if ( Number( complianz.clean_cookies ) === 1 ) { 650 //make service human readable 651 let service_nicename = service ? service.replace( '-', ' ' ) : ''; 652 service_nicename = service_nicename.charAt( 0 ).toUpperCase() + service_nicename.slice( 1 ); 653 placeholder_text = placeholder_text.replace( '{service}', service_nicename ); 654 body = cmplz_create_element( 'div', placeholder_text ); 655 body.innerHTML = cmplz_sanitize_placeholder_html( placeholder_text ); 656 body.classList.add( 'cmplz-blocked-content-notice' ); 657 const btn = body.querySelector( 'button' ); 658 btn.setAttribute( 'data-service', service ); 659 btn.setAttribute( 'data-category', category ); 660 btn.setAttribute( 'aria-label', complianz.aria_label.replace( '{service}', service_nicename ) ); 661 const pageLinks = complianz.page_links[ complianz.region ]; 662 const link = body.querySelector( '.cmplz-links a' ); 663 if ( pageLinks && pageLinks.hasOwnProperty( 'cookie-statement' ) ) { 664 link.setAttribute( 'href', pageLinks[ 'cookie-statement' ].url ); 665 if ( link.innerText === '{title}' ) { 666 link.innerText = pageLinks[ 'cookie-statement' ].title; 667 } 668 } else if ( link ) { 669 link.parentElement.style.display = 'none'; 670 } 671 } else { 672 const btn = cmplz_create_element( 'button', '' ); 673 const category_nicename = complianz.categories.hasOwnProperty( category ) ? complianz.categories[ category ] : 'marketing'; 674 btn.innerText = placeholder_text.replace( '{category}', category_nicename ); 675 btn.classList.add( 'cmplz-blocked-content-notice', 'cmplz-accept-category', 'cmplz-accept-' + category ); //'cmplz-accept-'+category is deprecated 676 677 btn.setAttribute( 'data-service', service ); 678 btn.setAttribute( 'data-category', category ); 679 btn.setAttribute( 'aria-label', complianz.aria_label.replace( '{category}', category ) ); 680 body = btn; 681 } 682 683 if ( container.tagName !== 'VIDEO' ) { 684 container.appendChild( body ); 685 } else { 686 container.parentElement.appendChild( body ); 687 } 688 } 689 } 690} 691 692/* 693 * Set the height of an image relative to the width, depending on the image widht/height aspect ratio. 694 * 695 * 696 * */ 697 698function cmplz_set_blocked_content_container_aspect_ratio( container, src, placeholder_class_index ) { 699 if ( container == null ) { 700 return; 701 } 702 703 // Handle image size for video 704 const img = new Image(); 705 img.addEventListener( 'load', function() { 706 const imgWidth = this.naturalWidth || 1; 707 const imgHeight = this.naturalHeight; 708 709 const w = container.clientWidth; 710 const h = imgHeight * ( w / imgWidth ); 711 712 const heightCSS = src.indexOf( 'placeholder.jpg' ) === -1 ? 'height:' + h + 'px;' : ''; 713 cmplz_append_css( '.cmplz-placeholder-' + placeholder_class_index + ' {' + height
713CSS + '}' ); 714 } ); 715 img.src = src; 716} 717 718/* 719 * Keep window aspect ratio in sync when window resizes 720 * To lower the number of times this code is executed, it is done with a timeout. 721 * 722 * */ 723 724let cmplzResizeTimer; 725window.addEventListener( 'resize', function() { 726 clearTimeout( cmplzResizeTimer ); 727 cmplzResizeTimer = setTimeout( cmplz_set_blocked_content_container, 500 ); 728}, true ); 729 730/* 731 * we run this function also on an interval, because with ajax loaded content, the placeholders would otherwise not be handled. 732 */ 733if ( Number( complianz.block_ajax_content ) === 1 ) { 734 setInterval( function() { 735 cmplz_set_blocked_content_container(); 736 }, 2000 ); 737} 738 739/* 740 * Enable scripts that were blocked 741 * 742 * */ 743 744function cmplz_enable_category( category, service ) { 745 if ( Number( complianz.tm_categories ) === 1 && category !== '' ) { 746 cmplz_run_tm_event( category ); 747 } 748 749 const details = {}; 750 details.category = category; 751 details.categories = cmplz_accepted_categories(); 752 details.region = complianz.region; 753 const event = new CustomEvent( 'cmplz_before_category', { detail: details } ); 754 document.dispatchEvent( event ); 755 756 service = typeof service !== 'undefined' ? service : 'do_not_match'; 757 if ( category === '' ) { 758 category = 'do_not_match'; 759 } 760 761 if ( category === 'functional' ) { 762 return; 763 } 764 //enable cookies for integrations 765 if ( category === 'marketing' ) { 766 cmplz_set_integrations_cookies(); 767 } 768 769 //remove accept cookie notice overlay 770 let selector; 771 if ( service !== 'do_not_match' ) { 772 selector = '.cmplz-blocked-content-notice [data-service="' + service + '"]'; 773 } else { 774 selector = Number( complianz.clean_cookies ) !== 1 ? '.cmplz-blocked-content-notice.cmplz-accept-' + category : '.cmplz-blocked-content-notice [data-category="' + category + '"]'; 775 } 776 document.querySelectorAll( selector ).forEach( ( obj ) => { 777 const blockedElementService = obj.getAttribute( 'data-service' ); 778 if ( obj.parentNode.classList.contains( 'cmplz-blocked-content-notice' ) ) { 779 obj = obj.parentNode; 780 } 781 //only remove if the service is not explicitly denied 782 if ( ! cmplz_is_service_denied( blockedElementService ) ) { 783 obj.parentNode.removeChild( obj ); 784 } 785 } ); 786 787 document.querySelectorAll( '[data-category="' + category + '"], [data-service="' + service + '"]' ).forEach( ( obj ) => { 788 //if a category is activated, but this specific service is denied, skip. 789 const elementService = obj.getAttribute( 'data-service' ); 790 if ( cmplz_is_service_denied( elementService ) ) { 791 return; 792 } 793 794 //if native class is included, it isn't blocked, so will have run already 795 if ( obj.getAttribute( 'data-category' ) === 'functional' ) { 796 return; 797 } 798 799 if ( obj.classList.contains( 'cmplz-activated' ) ) { 800 return; 801 } 802 803 const tagName = obj.tagName; 804 if ( tagName === 'LINK' ) { 805 obj.classList.add( 'cmplz-activated' ); 806 const src = obj.getAttribute( 'data-href' ); 807 cmplz_load_css( src, category ); 808 } else if ( tagName === 'IMG' ) { 809 obj.classList.add( 'cmplz-activated' ); 810 const src = obj.getAttribute( 'data-src-cmplz' ); 811 obj.setAttribute( 'src', src ); 812 //handle browser native lazy load feature 813 if ( obj.getAttribute( 'data-deferlazy' ) ) { 814 obj.setAttribute( 'loading', 'lazy' ); 815 } 816 cmplz_remove_placeholder( obj ); 817 } else if ( tagName === 'IFRAME' ) { 818 obj.classList.add( 'cmplz-activated' ); 819 let src = obj.getAttribute( 'data-src-cmplz' ); 820 const srcAttribute = obj.getAttribute( 'data-cmplz-target' ) ? obj.getAttribute( 'data-cmplz-target' ) : 'src'; 821 822 //check if there's an autoplay value we need to pass on, if it's added later on by javascript 823 const autoplay = cmplz_get_url_parameter( obj.getAttribute( srcAttribute ), 'autoplay' ); 824 if ( autoplay === '1' ) { 825 src = src + '&autoplay=1'; 826 } 827 //handle browser native lazy load feature 828 if ( obj.getAttribute( 'data-deferlazy' ) ) { 829 obj.setAttribute( 'loading', 'lazy' ); 830 } 831 832 obj.addEventListener( 'load', () => { 833 cmplz_remove_placeholder( obj ); 834 } ); 835 obj.setAttribute( srcAttribute, src ); 836 } else if ( obj.classList.contains( 'cmplz-placeholder-element' ) ) { 837 obj.classList.add( 'cmplz-activated' ); 838 //other services, no iframe, with placeholders 839 //remove the added classes
840 const cssIndex = obj.getAttribute( 'data-placeholder_class_index' ); 841 obj.classList.remove( 'cmplz-blocked-content-container', 'cmplz-placeholder-' + cssIndex ); 842 } 843 } ); 844 845 /* 846 * Let's activate the scripts 847 */ 848 849 //create list of waiting scripts 850 const scriptElements = document.querySelectorAll( 'script[data-category="' + category + '"], script[data-service="' + service + '"]' ); 851 scriptElements.forEach( ( obj ) => { 852 const waitfor = obj.getAttribute( 'data-waitfor' ); 853 const src = obj.getAttribute( 'data-cmplz-src' ); 854 if ( waitfor ) { 855 if ( src ) { 856 cmplz_waiting_scripts[ waitfor ] = src; 857 } else if ( obj.innerText.length > 0 ) { 858 cmplz_waiting_inline_scripts[ waitfor ] = obj; 859 } 860 } 861 862 //cleanup after adding it to our waiting or scriptElements list. 863 if ( obj.parentElement ) { 864 obj.parentElement.removeChild( obj ); 865 } 866 } ); 867 868 //scripts: remove text/plain, and move data-src attribute to src attribute 869 scriptElements.forEach( ( obj ) => { 870 //we don't want already activate scripts to fire, but also we don't want scripts that weren't blocked to fire. Hence the check for type 871 const script_mime_type = obj.getAttribute( 'type' ); 872 if ( obj.classList.contains( 'cmplz-activated' ) || ( ! script_mime_type || script_mime_type === 'text/javascript' ) ) { 873 return; 874 } 875 obj.classList.add( 'cmplz-activated' ); 876 const src = obj.getAttribute( 'data-cmplz-src' ); 877 if ( src ) { 878 obj.removeAttribute( 'type' ); 879 //check if this src or txt is in a waiting script. If so, skip. 880 if ( cmplz_is_waiting_script( cmplz_waiting_scripts, src ) ) { 881 return; 882 } 883 884 if ( obj.getAttribute( 'data-post_scribe_id' ) ) { 885 const psID = '#' + obj.getAttribute( 'data-post_scribe_id' ); 886 const postScribeObj = document.querySelector( psID ); 887 if ( postScribeObj ) { 888 postScribeObj.innerHtml( '' ); 889 postscribe( psID, '<script src=' + src + '></script>' ); 890 } 891 } else { 892 cmplz_run_script( src, category, service, 'src', obj ); 893 } 894 } else if ( obj.innerText.length > 0 ) { 895 //check if this src or txt is in a waiting script. If so, skip. 896 if ( cmplz_is_waiting_script( cmplz_waiting_inline_scripts, obj.innerText ) ) { 897 return; 898 } 899 900 cmplz_run_script( obj.innerText, category, service, 'inline', obj ); 901 } 902 } ); 903 904 // if there are no blockable scripts at all, we still want to provide a hook 905 // in most cases, there are blocked scripts, and run_after_all_scripts hook won't be fired yet. In that 906 // case only the category event is fired (if activated), and this function will run 907 // again from the script activation callbacks. 908 cmplz_run_after_all_scripts( category, service ); 909} 910 911/* 912 * remove added classes from the blocked content container 913 * 914 * @param obj 915 */ 916function cmplz_remove_placeholder( obj ) { 917 const blocked_content_container = obj.closest( '.cmplz-blocked-content-container' ); 918 if ( blocked_content_container ) { 919 const cssIndex = blocked_content_container.getAttribute( 'data-placeholder_class_index' ); 920 blocked_content_container.classList.remove( 'cmplz-blocked-content-container', 'cmplz-placeholder-' + cssIndex ); 921 } 922 obj.classList.remove( 'cmplz-iframe-styles', 'cmplz-iframe', 'video-wrap' ); 923} 924 925/* 926 * check if the passed source has a waiting script that should be executed, and return it if so. 927 * @param waiting_scripts 928 * @param src 929 * @returns {*} 930 */ 931 932function cmplz_get_waiting_script( waiting_scripts, src ) { 933 for ( const waitfor in waiting_scripts ) { 934 if ( waiting_scripts.hasOwnProperty( waitfor ) ) { 935 if ( src.indexOf( waitfor ) !== -1 ) { 936 const output = waiting_scripts[ waitfor ]; 937 delete waiting_scripts[ waitfor ]; 938 return output; 939 } 940 } 941 } 942 return false; 943} 944 945/* 946 * Because we need a key=>value array in javascript, the .length check for an empty array doesn't work. 947 * @param arr 948 * @returns {boolean} 949 */ 950 951function cmplz_array_is_empty( arr ) { 952 for ( const key in arr ) { 953 if ( arr.hasOwnProperty( key ) ) { 954 return false; 955 } 956 } 957 return true; 958} 959 960/* 961 * Check if the passed src or script is waiting for another script and should not execute 962 * @param waiting_scripts 963 * @param srcOrScript 964 */ 965
966function cmplz_is_waiting_script( waiting_scripts, srcOrScript ) { 967 for ( const waitfor in waiting_scripts ) { 968 if ( waiting_scripts.hasOwnProperty( waitfor ) ) { 969 let waitingScript = waiting_scripts[ waitfor ]; 970 if ( typeof waitingScript !== 'string' ) { 971 waitingScript = waitingScript.innerText; 972 } 973 974 if ( srcOrScript.indexOf( waitingScript ) !== -1 || waitingScript.indexOf( srcOrScript ) !== -1 ) { 975 return true; 976 } 977 } 978 } 979 return false; 980} 981 982/* 983 * if all scripts have been executed, fire a hook. 984 */ 985 986function cmplz_run_after_all_scripts( category, service ) { 987 //fire an event so custom scripts can hook into this. 988 const enableService = service !== 'do_not_match' && ! cmplz_in_array( service, cmplz_fired_service_events ); 989 const enableCategory = category !== 'do_not_match' && ! cmplz_in_array( category, cmplz_fired_category_events ); 990 if ( enableCategory || enableService ) { 991 if ( enableCategory ) { 992 cmplz_fired_category_events.push( category ); 993 } 994 if ( enableService ) { 995 cmplz_fired_service_events.push( service ); 996 } 997 const details = {}; 998 details.category = category; 999 details.service = service; 1000 details.categories = cmplz_accepted_categories(); 1001 details.services = cmplz_get_all_service_consents(); 1002 details.region = complianz.region; 1003 1004 const event = new CustomEvent( 'cmplz_enable_category', { detail: details } ); 1005 document.dispatchEvent( event ); 1006 } 1007 1008 if ( ! cmplz_all_scripts_hook_fired && cmplz_array_is_empty( cmplz_waiting_inline_scripts ) && cmplz_array_is_empty( cmplz_waiting_scripts ) ) { 1009 const event = new CustomEvent( 'cmplz_run_after_all_scripts', { detail: category, service } ); 1010 document.dispatchEvent( event ); 1011 cmplz_all_scripts_hook_fired = true; 1012 } 1013} 1014 1015/* 1016 * Fire an event in Tag Manager 1017 * 1018 * 1019 * */ 1020 1021const cmplz_fired_events = []; 1022function cmplz_run_tm_event( category ) { 1023 if ( cmplz_fired_events.indexOf( category ) === -1 ) { 1024 cmplz_fired_events.push( category ); 1025 window.dataLayer = window.dataLayer || []; 1026 window.dataLayer.push( { 1027 event: 'cmplz_event_' + category, 1028 } ); 1029 const event = new CustomEvent( 'cmplz_tag_manager_event', { detail: category } ); 1030 document.dispatchEvent( event ); 1031 } 1032} 1033 1034/* 1035 * Accept all categories 1036 */ 1037window.cmplz_accept_all = function() { 1038 cmplz_clear_all_service_consents(); 1039 cmplz_fire_before_categories_consent( cmplz_categories ); 1040 1041 for ( const key in cmplz_categories ) { 1042 if ( cmplz_categories.hasOwnProperty( key ) ) { 1043 cmplz_set_consent( cmplz_categories[ key ], 'allow' ); 1044 } 1045 } 1046 cmplz_sync_category_checkboxes(); 1047}; 1048 1049function cmplz_fire_before_categories_consent( categories ) { 1050 const details = {}; 1051 details.categories = categories; 1052 details.region = complianz.region; 1053 const event = new CustomEvent( 'cmplz_before_categories_consent', { detail: details } ); 1054 document.dispatchEvent( event ); 1055} 1056 1057/* 1058 * Deny all categories, and reload if needed. 1059 */ 1060window.cmplz_deny_all = function() { 1061 for ( const key in cmplz_categories ) { 1062 if ( cmplz_categories.hasOwnProperty( key ) ) { 1063 cmplz_set_consent( cmplz_categories[ key ], 'deny' ); 1064 } 1065 } 1066 const consentLevel = cmplz_highest_accepted_category(); 1067 let reload = false; 1068 1069 if ( consentLevel !== 'functional' || cmplz_exists_service_consent() ) { 1070 reload = true; 1071 } 1072 if ( cmplz_clear_cookies( 'cmplz_service' ) ) { 1073 reload = true; 1074 } 1075 1076 //has to be after the check if should be reloaded, otherwise that check won't work. 1077 cmplz_clear_all_service_consents(); 1078 cmplz_integrations_revoke(); 1079 cmplz_fire_categories_event(); 1080 cmplz_track_status(); 1081 1082 const event = new CustomEvent( 'cmplz_revoke', { detail: reload } ); 1083 document.dispatchEvent( event ); 1084 1085 //we need to let the iab extension handle the reload, otherwise the consent revoke might not be ready yet. 1086 if ( ! complianz.tcf_active && reload ) { 1087 cmplz_reload_browser_compatible(); 1088 } 1089}; 1090 1091/* 1092 * If current cookie policy has changed, reset cookie consent 1093 * 1094 * */ 1095 1096function cmplz_check_cookie_policy_id() { 1097 const user_policy_id = cmplz_get_cookie( 'policy_id' ); 1098 if ( user_policy_id && ( parseInt( complianz.current_policy_id ) !== parseInt( user_policy_id ) ) ) { 1099 cmplz_deny_all(); 1100 cmplz_set_banner_status( 'show' ); 1101 cmplz_clear_cookies( 'cmplz' ); 1102 } 1103} 1104 1105window.conditionally_show_banner = function() { 1106 //merge userdata with complianz data, in case a b testing is used with user specific cookie banner data 1107 //objects are merged so user_data will override data in complianz object 1108 complianz = cmplz_merge_object( complianz, cmplz_user_data ); 1109 //check if we need to redirect to another legal document, for a specific region 1110 cmplz_maybe_auto_redirect(); 1111 cmplz_set_blocked_content_container(); 1112 1113 /* 1114 * Integration with WordPress, tell what kind of consent type we're using, then fire an event 1115 */ 1116 1117 window.wp_consent_type = complianz.consenttype; 1118 let event = new CustomEvent( 'wp_consent_type_defined' ); 1119 document.dispatchEvent( event ); 1120 event = new CustomEvent( 'cmplz_before_cookiebanner' ); 1121 document.dispatchEvent( event ); 1122 if ( Number( complianz.forceEnableStats ) === 1 && complianz.consenttype === 'optin' ) { 1123 cmplz_set_consent( 'statistics', 'allow' ); 1124 } 1125 const rev_cats = cmplz_categories.reverse(); 1126 const consented_categories = []; 1127 for ( const key in rev_cats ) { 1128 if ( rev_cats.hasOwnProperty( key ) ) { 1129 const category = cmplz_categories[ key ]; 1130 if ( cmplz_has_consent( category ) ) { 1131 consented_categories.push( category ); 1132 } 1133 } 1134 } 1135 cmplz_fire_before_categories_consent( consented_categories ); 1136 for ( const key in consented_categories ) { 1137 if ( rev_cats.hasOwnProperty( key ) ) { 1138 const category = consented_categories[ key ]; 1139 cmplz_enable_category( category ); 1140 } 1141 } 1142 1143 if ( cmplz_exists_service_consent() ) { 1144 //if any service is enabled, allow the general services also, because some services are partially 'general' 1145 cmplz_enable_category( '', 'general' ); 1146 const services = cmplz_get_services_on_page(); 1147 for ( const key in services ) { 1148 if ( services.hasOwnProperty( key ) ) { 1149 const service = services[ key ].service; 1150 const category = services[ key ].category; 1151 if ( cmplz_has_service_consent( service, category ) ) { 1152 document.querySelectorAll( '.cmplz-accept-service[data-service="' + service + '"]' ).forEach( ( obj ) => { 1153 obj.checked = true; 1154 } ); 1155 cmplz_enable_category( '', service ); 1156 } 1157 } 1158 } 1159 } 1160 1161 cmplz_sync_category_checkboxes(); 1162 cmplz_integrations_init(); 1163 cmplz_check_cookie_policy_id(); 1164 cmplz_set_up_auto_dismiss(); 1165 1166 //if we're on the cookie policy page, we dynamically load the applicable revoke checkbox 1167 cmplz_load_manage_consent_container(); 1168 1169 event = new CustomEvent( 'cmplz_cookie_banner_data', { detail: complianz } ); 1170 document.dispatchEvent( event ); 1171 1172 //if no categories were saved before, we do it now 1173 if ( cmplz_get_cookie( 'saved_categories' ) === '' ) { 1174 //for Non optin/optout visitors, and DNT users, we just track the no-warning option 1175 if ( complianz.consenttype !== 'optin' && complianz.consenttype !== 'optout' ) { 1176 cmplz_track_status(); 1177 } else if ( cmplz_do_not_track() ) { 1178 cmplz_track_status( 'do_not_track' ); 1179 } 1180 } 1181 1182 cmplz_set_category_as_body_class(); 1183 //fire cats event, but do not fire a track, as we do this on exit. 1184 cmplz_fire_categories_event(); 1185 if ( ! cmplz_do_not_track() ) { 1186 if ( complianz.consenttype === 'optin' ) { 1187 if ( complianz.forceEnableStats ) { 1188 cmplz_enable_category( 'statistics' ); 1189 } 1190 cmplz_log( 'opt-in' ); 1191 show_cookie_banner(); 1192 } else if ( complianz.consenttype === 'optout' ) { 1193 cmplz_log( 'opt-out' ); 1194 show_cookie_banner(); 1195 } else {
1196 cmplz_log( 'other consent type, no cookie warning' ); 1197 //on other consent types, all scripts are enabled by default. 1198 cmplz_accept_all(); 1199 } 1200 } else { 1201 cmplz_log( 'global privacy control or do not track detected: no banner.' ); 1202 cmplz_track_status( 'do_not_track' ); 1203 } 1204}; 1205 1206/** 1207 * Check if User has set either GPC or DNT in the browser. 1208 * @return {boolean} True if do not track is enabled and GPC or DNT is set. 1209 */ 1210function cmplz_do_not_track() { 1211 const dnt = 'doNotTrack' in navigator && navigator.doNotTrack === '1'; 1212 const gpc = 'globalPrivacyControl' in navigator && navigator.globalPrivacyControl; 1213 return !! ( complianz.do_not_track_enabled && ( gpc || dnt ) ); 1214} 1215 1216/* 1217 * Get list of services active on the page 1218 * @returns {*[]} 1219 */ 1220function cmplz_get_services_on_page() { 1221 const services = []; 1222 document.querySelectorAll( '[data-service]' ).forEach( ( obj ) => { 1223 const service = obj.getAttribute( 'data-service' ); 1224 const category = obj.getAttribute( 'data-category' ); 1225 if ( services.indexOf( service ) === -1 ) { 1226 services.push( { 1227 category, 1228 service, 1229 } ); 1230 } 1231 } ); 1232 return services; 1233} 1234 1235/* 1236 * Run the actual cookie warning 1237 * 1238 * */ 1239 1240window.show_cookie_banner = function() { 1241 let disableCookiebanner = complianz.disable_cookiebanner || cmplz_is_speedbot(); 1242 //do not show banner when manage consent area on cookie policy is visible 1243 //when users use only the shortcode, the manage consent container is not active, but the dropdown cookie policy class is. 1244 //when the complianz shortcode is used, the dropdown cookie policy class is loaded late because it's loaded with javascript. 1245 let tmpDismissCookiebanner = false; 1246 if ( document.querySelector( '#cmplz-manage-consent-container' ) || document.querySelector( '.cmplz-dropdown-cookiepolicy' ) ) { 1247 tmpDismissCookiebanner = true; 1248 } 1249 1250 const container = document.getElementById( 'cmplz-cookiebanner-container' ); 1251 if ( container ) { 1252 document.body.prepend( container ); 1253 } 1254 1255 const pageLinks = complianz.page_links[ complianz.region ]; 1256 //get correct banner, based on banner_id 1257 cmplz_banner = document.querySelector( '.cmplz-cookiebanner.banner-' + complianz.user_banner_id + '.' + complianz.consenttype ); 1258 if ( ! cmplz_banner ) { 1259 disableCookiebanner = true; 1260 } 1261 cmplz_manage_consent_button = document.querySelector( '#cmplz-manage-consent .cmplz-manage-consent.manage-consent-' + complianz.user_banner_id ); 1262 1263 // Reveal the banner once its stylesheet is applied. Idempotent: removing an 1264 // already-removed class is a no-op, so calling this more than once is safe. 1265 const revealBanner = function() { 1266 if ( disableCookiebanner ) { 1267 return; 1268 } 1269 cmplz_banner.classList.remove( 'cmplz-hidden' ); 1270 cmplz_manage_consent_button.classList.remove( 'cmplz-hidden' ); 1271 1272 // Activate focus trap only if banner is currently shown (not already dismissed) 1273 if ( cmplz_get_banner_status() === 'show' ) { 1274 cmplz_trap_focus(); 1275 1276 // Set focus to the close button when banner is shown 1277 const closeButton = cmplz_banner.querySelector( '.cmplz-close' ); 1278 if ( closeButton ) { 1279 closeButton.focus(); 1280 } 1281 } 1282 }; 1283 1284 // Guarantee the banner stylesheet is present and enabled, then reveal. 1285 // In the deterministic case the sheet was enqueued server-side and is 1286 // already applied, so we reveal immediately (no FOUC). Otherwise the sheet 1287 // is injected here and we reveal on load, with a timeout safety net for the 1288 // race where the sheet finished loading before the listener attached. 1289 const bannerLink = cmplzEnsureBannerCss(); 1290 if ( ! disableCookiebanner ) { 1291 if ( bannerLink && bannerLink.sheet ) { 1292 revealBanner(); 1293 } else if ( bannerLink ) { 1294 bannerLink.addEventListener( 'load', revealBanner ); 1295 setTimeout( revealBanner, 500 ); 1296 } else { 1297 revealBanner(); 1298 } 1299 } 1300 if ( cmplz_banner && ! disableCookiebanner ) { 1301 cmplz_banner.querySelectorAll( '.cmplz-links a:not(.cmplz-external), .cmplz-buttons a:not(.cmplz-external)' ).forEach( ( obj ) => { 1302 const docElement = obj; 1303 docElement.classList.add( 'cmplz-hidden' ); 1304 for ( const pageType in pageLinks ) { 1305 if ( pageLinks.hasOwnProperty( pageType ) && docElement.classList.contains( pageType ) ) { 1306 docElement.setAttribute( 'href', pageLinks[ pageType ].url + docElement.getAttribute( 'data-relative_url' ) ); 1307 if ( docElement.innerText === '{title}' ) { 1308 docElement.innerText = cmplz_html_decode( pageLinks[ pageType ].title ); 1309 } 1310 docElement.classList.remove( 'cmplz-hidden' ); 1311 } 1312 } 1313 } ); 1314 1315 cmplz_set_banner_status(); 1316 //we don't use the setBannerStatus function here, as we don't want to save it in a cookie now. 1317 if ( tmpDismissCookiebanner ) { 1318 cmplz_banner.classList.remove( 'cmplz-show' ); 1319 cmplz_banner.classList.add( 'cmplz-dismissed' ); 1320 cmplz_manage_consent_button.classList.remove( 'cmplz-dismissed' ); 1321 cmplz_manage_consent_button.classList.add( 'cmplz-show' ); 1322 } 1323 } 1324 1325 const event = new CustomEvent( 'cmplz_cookie_warning_loaded', { detail: complianz.region } ); 1326 document.dispatchEvent( event ); 1327}; 1328 1329/* 1330 * Guarantee the banner stylesheet for the current variant is present in the DOM 1331 * and enabled. Idempotent â safe to call on first load and after every 1332 * client-side (Interactivity API) navigation. 1333 * 1334 * From WP 6.9 the iAPI router disables any stylesheet absent from the fetched 1335 * server HTML by setting CSSStyleSheet.disabled = true (it never removes the 1336 * element). When the sheet was enqueued server-side (deterministic single 1337 * variant) it survives by design; if the router disabled it we re-enable it 1338 * synchronously â zero network, zero flash. When the sheet was injected by JS 1339 * (A/B or multiple consent types) we re-enable it, or inject it if missing. 1340 * 1341 * @returns {HTMLLinkElement|null} the banner <link>, or null if none resolved. 1342 */ 1343function cmplzEnsureBannerCss() { 1344 //server-enqueued sheet carries the WordPress style handle id. 1345 //Invariant: enqueue_banner_css() only enqueues server-side in the deterministic 1346 //case (no A/B testing), where user_banner_id resolves to the default banner id 1347 //it enqueued under â so this id matches. Under A/B testing the variant differs 1348 //per user, nothing is enqueued server-side, and we fall through to the injected/ 1349 //inject paths below instead. 1350 const handleId = 'cmplz-banner-' + complianz.user_banner_id + '-' + complianz.consenttype + '-css'; 1351 const enqueued = document.getElementById( handleId ); 1352 if ( enqueued && enqueued.rel === 'stylesheet' ) { 1353 if ( enqueued.sheet && enqueued.sheet.disabled ) { 1354 enqueued.sheet.disabled = false;
1355 } 1356 return enqueued; 1357 } 1358 1359 const css_file_url = complianz.css_file.replace( '{type}', complianz.consenttype ).replace( '{banner_id}', complianz.user_banner_id ); 1360 if ( complianz.css_file.indexOf( 'cookiebanner/css/defaults/banner' ) !== -1 ) { 1361 cmplz_log( 'Fallback default css file used. Please re-save banner settings, or check file writing permissions in uploads directory' ); 1362 } 1363 1364 //match an already-injected <link> by filename, ignoring cache-busting query strings 1365 const filename = css_file_url.split( '?' )[ 0 ].split( '/' ).pop(); 1366 let injected = null; 1367 document.querySelectorAll( 'link[rel="stylesheet"]' ).forEach( function( el ) { 1368 if ( el.href.split( '?' )[ 0 ].split( '/' ).pop() === filename ) { 1369 injected = el; 1370 } 1371 } ); 1372 if ( injected ) { 1373 if ( injected.sheet && injected.sheet.disabled ) { 1374 injected.sheet.disabled = false; 1375 } 1376 return injected; 1377 } 1378 1379 //nothing present yet â inject it (multi-variant first load, or default-CSS fallback) 1380 const link = document.createElement( 'link' ); 1381 link.href = css_file_url; 1382 link.type = 'text/css'; 1383 link.rel = 'stylesheet'; 1384 document.getElementsByTagName( 'head' )[ 0 ].appendChild( link ); 1385 return link; 1386} 1387 1388/* 1389 * Re-sync consent state and de-duplicate banner instances after a client-side 1390 * navigation. The banner is rendered once in the footer (outside any router 1391 * region) so it normally persists untouched; this is a safety pass in case a 1392 * theme/plugin wraps it in a router region, which would duplicate it with the 1393 * cache-default markup on swap. 1394 */ 1395function cmplzSyncConsentState() { 1396 const banners = document.querySelectorAll( '.cmplz-cookiebanner' ); 1397 //drop copies brought in inside a router region; the footer original persists 1398 if ( banners.length > 1 ) { 1399 banners.forEach( function( banner ) { 1400 if ( banner.closest( '[data-wp-router-region]' ) ) { 1401 banner.remove(); 1402 } 1403 } ); 1404 } 1405 1406 //re-apply visibility from the stored consent status 1407 const dismissed = cmplz_get_banner_status() === 'dismissed'; 1408 document.querySelectorAll( '.cmplz-cookiebanner' ).forEach( function( banner ) { 1409 banner.classList.toggle( 'cmplz-show', ! dismissed ); 1410 banner.classList.toggle( 'cmplz-dismissed', dismissed ); 1411 } ); 1412 1413 //remove a manage-consent button duplicated inside a region, but only when an 1414 //out-of-region original survives. Mirrors the banner guard above: if a theme 1415 //wraps the whole page (footer included) in a single router region, the only 1416 //copy lives inside it â removing it unconditionally would delete the manage- 1417 //consent button with no replacement. 1418 const manageBtns = document.querySelectorAll( '#cmplz-manage-consent' ); 1419 const hasOriginal = Array.prototype.some.call( manageBtns, function( btn ) { 1420 return ! btn.closest( '[data-wp-router-region]' ); 1421 } ); 1422 if ( hasOriginal ) { 1423 manageBtns.forEach( function( btn ) { 1424 if ( btn.closest( '[data-wp-router-region]' ) ) { 1425 btn.remove(); 1426 } 1427 } ); 1428 } 1429} 1430 1431/* 1432 * Arm the client-side navigation hook. On WP 7.0+ the companion script module 1433 * (complianz-router.js) drives navigation via the official watch()+state.url 1434 * API and notifies us with a DOM event. On WP 6.9 we fall back to a History API 1435 * patch, scoped to pages that actually have an iAPI router region. Handlers are 1436 * idempotent, so duplicate/extra ticks are harmless. 1437 */ 1438function cmplzInitNavigationHook() { 1439 if ( ! document.querySelector( '[data-wp-router-region]' ) ) { 1440 return; //no iAPI navigation on this page 1441 } 1442 1443 const onNavigate = function() { 1444 cmplzEnsureBannerCss(); 1445 cmplzSyncConsentState(); 1446 }; 1447 1448 if ( complianz.iapi_nav === 'module' ) { 1449 //WP 7.0+: the companion module drives navigation via watch() 1450 document.addEventListener( 'cmplz_router_navigated', onNavigate ); 1451 return; 1452 } 1453 1454 //WP 6.9 fallback: patch the History API + popstate. 1455 //Defer onNavigate so it never runs synchronously inside the iAPI router's 1456 //pushState call â a synchronous DOM mutation there triggers the iAPI 1457 //MutationObserver, which re-evaluates directives that access private stores 1458 //and throws "Cannot unlock a private store with an invalid lock code". 1459 const onNavigateDeferred = function() { 1460 requestAnimationFrame( function() { 1461 onNavigate(); 1462 setTimeout( onNavigate, 150 ); 1463 } ); 1464 }; 1465 [ 'pushState', 'replaceState' ].forEach( function( method ) { 1466 const original = history[ method ]; 1467 history[ method ] = function() { 1468 const result = original.apply( this, arguments ); 1469 onNavigateDeferred(); 1470 return result; 1471 }; 1472 } ); 1473 window.addEventListener( 'popstate', onNavigateDeferred ); 1474} 1475 1476/* 1477 * Get the status of the banner: dismissed | show 1478 * @returns {string} 1479 */ 1480window.cmplz_get_banner_status = function() { 1481 return cmplz_get_cookie( 'banner-status' ); 1482}; 1483 1484/* 1485 * Set the banner status so it will be either shown or dismissed, and store it in a cookie. 1486 * @param status (optional) 1487 */ 1488 1489window.cmplz_set_banner_status = function( status ) { 1490 let prevStatus = cmplz_get_cookie( 'banner-status' ); 1491 status = typeof status !== 'undefined' ? status : prevStatus; 1492 if ( status !== prevStatus ) { 1493 cmplz_set_cookie( 'banner-status', status ); 1494 } 1495 if ( status.length === 0 ) { 1496 status = 'show'; 1497 } 1498 1499 if ( status === 'show' ) { 1500 prevStatus = 'dismissed'; 1501 } else { 1502 prevStatus = 'show'; 1503 } 1504 1505 if ( cmplz_banner && status.length > 0 ) { 1506 cmplz_banner.classList.remove( 'cmplz-' + prevStatus );
1507 cmplz_banner.classList.add( 'cmplz-' + status ); 1508 if ( cmplz_manage_consent_button ) { 1509 cmplz_manage_consent_button.classList.add( 'cmplz-' + prevStatus ); 1510 cmplz_manage_consent_button.classList.remove( 'cmplz-' + status ); 1511 } 1512 } 1513 1514 if ( cmplz_banner_container && complianz.soft_cookiewall ) { 1515 cmplz_banner_container.classList.remove( 'cmplz-' + prevStatus ); 1516 cmplz_banner_container.classList.add( 'cmplz-' + status, 'cmplz-soft-cookiewall' ); 1517 } 1518 const event = new CustomEvent( 'cmplz_banner_status', { detail: status } ); 1519 document.dispatchEvent( event ); 1520 cmplz_start_clean(); 1521 1522 if ( status === 'show' ) { 1523 // Activate focus trap only if soft cookie wall is enabled 1524 cmplz_trap_focus(); 1525 1526 // Set focus to the close button when banner is shown 1527 const closeButton = cmplz_banner.querySelector( '.cmplz-close' ); 1528 if ( closeButton ) { 1529 closeButton.focus(); 1530 } 1531 } else { 1532 // Release focus trap when banner is dismissed 1533 cmplz_release_focus_trap(); 1534 } 1535}; 1536 1537/* 1538 * Check if current visitor is a bot 1539 * 1540 * @returns {boolean} 1541 */ 1542function cmplz_is_bot() { 1543 const botPattern = '(googlebot\/|Googlebot-Mobile|Google-InspectionTool|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)'; 1544 const reBot = new RegExp( botPattern, 'i' ); 1545 const userAgent = navigator.userAgent; 1546 return reBot.test( userAgent ); 1547} 1548/* 1549 * Check if current visitor is a speedbot 1550 * 1551 * @returns {boolean} 1552 */ 1553function cmplz_is_speedbot() { 1554 const userAgent = navigator.userAgent; 1555 const speedBotPattern = '(GTmetrix|pingdom|pingbot|Lighthouse)'; 1556 const speedBot = new RegExp( speedBotPattern, 'i' ); 1557 return speedBot.test( userAgent ); 1558} 1559 1560/* 1561 * Check if there is consent for a category or service 1562 * @param category 1563 * @returns {boolean} 1564 */ 1565 1566window.cmplz_has_consent = function( category ) { 1567 if ( cmplz_is_bot() ) { 1568 return true; 1569 } 1570 1571 if ( category === 'functional' ) { 1572 return true; 1573 } 1574 let has_consent, value; 1575 1576 //if DNT is detected, we should only return the actual cookie value, and not look at the consenttype 1577 if ( cmplz_do_not_track() ) { 1578 value = cmplz_get_cookie( category ); 1579 has_consent = ( value === 'allow' ); 1580 return has_consent; 1581 } 1582 1583 /* 1584 * categories 1585 */ 1586 value = cmplz_get_cookie( category ); 1587 if ( ( complianz.consenttype === 'optout' || complianz.consenttype === 'other' ) && value === '' ) { 1588 //if it's opt out and no cookie is set we should return true 1589 has_consent = true; 1590 } else { 1591 //all other situations, return only true if value is allow 1592 has_consent = ( value === 'allow' ); 1593 } 1594 1595 return has_consent; 1596}; 1597 1598/* 1599 * Check if a service has consent 1600 * @param service 1601 * @returns {boolean|*} 1602 */
1603window.cmplz_is_service_denied = function( service ) { 1604 //Check if it's in the consented services cookie 1605 const consented_services_json = cmplz_get_cookie( 'consented_services' ); 1606 let consented_services; 1607 try { 1608 consented_services = JSON.parse( consented_services_json ); 1609 } catch ( e ) { 1610 consented_services = {}; 1611 } 1612 1613 if ( ! consented_services.hasOwnProperty( service ) ) { 1614 return false; 1615 } 1616 return ! consented_services[ service ]; 1617}; 1618 1619/* 1620 * Check if a service has consent 1621 * @param service 1622 * @param category 1623 * @returns {boolean|*} 1624 */ 1625window.cmplz_has_service_consent = function( service, category ) { 1626 //Check if it's in the consented services cookie 1627 const consented_services_json = cmplz_get_cookie( 'consented_services' ); 1628 let consented_services; 1629 try { 1630 consented_services = JSON.parse( consented_services_json ); 1631 } catch ( e ) { 1632 consented_services = {}; 1633 } 1634 1635 if ( ! consented_services.hasOwnProperty( service ) ) { 1636 //default to the category 1637 return cmplz_has_consent( category ); 1638 } 1639 return consented_services[ service ]; 1640}; 1641 1642/* 1643 * check if there's at least one service with consent 1644 * @returns {boolean} 1645 */ 1646function cmplz_exists_service_consent() { 1647 const consented_services_json = cmplz_get_cookie( 'consented_services' ); 1648 let consented_services; 1649 try { 1650 consented_services = JSON.parse( consented_services_json ); 1651 for ( const key in consented_services ) { 1652 if ( consented_services.hasOwnProperty( key ) ) { 1653 if ( consented_services[ key ] === true ) { 1654 return true; 1655 } 1656 } 1657 } 1658 } catch ( e ) { 1659 return false; 1660 } 1661 return false; 1662} 1663 1664/* 1665 * Set consent for a service 1666 * @param service 1667 * @param consented 1668 */ 1669function cmplz_set_service_consent( service, consented ) { 1670 const consented_services_json = cmplz_get_cookie( 'consented_services' ); 1671 let consented_services; 1672 try { 1673 consented_services = JSON.parse( consented_services_json ); 1674 } catch ( e ) { 1675 consented_services = {}; 1676 } 1677 consented_services[ service ] = consented; 1678 cmplz_set_cookie( 'consented_services', JSON.stringify( consented_services ) ); 1679 const details = {}; 1680 details.service = service; 1681 details.value = consented; 1682 details.region = complianz.region; 1683 //when the status is changed, we may need to fire scripts again, so we reset it. 1684 cmplz_all_scripts_hook_fired = false; 1685 const event = new CustomEvent( 'cmplz_status_change_service', { detail: details } ); 1686 document.dispatchEvent( event ); 1687} 1688 1689/* 1690 * Remove all service consents 1691 */ 1692function cmplz_clear_all_service_consents() { 1693 cmplz_set_cookie( 'consented_services', '' ); 1694} 1695 1696/* 1697 * Get all consented or denied services 1698 */ 1699 1700function cmplz_get_all_service_consents() { 1701 const consented_services_json = cmplz_get_cookie( 'consented_services' ); 1702 let consented_services; 1703 try { 1704 consented_services = JSON.parse( consented_services_json ); 1705 } catch ( e ) { 1706 consented_services = {}; 1707 } 1708 return consented_services; 1709} 1710/* 1711 * Get cookie path 1712 * @returns {*} 1713 */ 1714function cmplz_get_cookie_path() { 1715 return typeof complianz.cookie_path !== 'undefined' && complianz.cookie_path !== '' ? complianz.cookie_path : '/'; 1716} 1717 1718/* 1719 * retrieve domain to set the cookies on 1720 * @returns {string} 1721 */ 1722function cmplz_get_cookie_domain() { 1723 if ( Number( complianz.set_cookies_on_root ) === 1 && complianz.cookie_domain.length > 3 && ! complianz.cookie_domain.includes( 'localhost' ) ) { 1724 return complianz.cookie_domain; 1725 } 1726 return ''; 1727} 1728 1729/* 1730 * Set consent for a category 1731 * @param category 1732 * @param value 1733 */ 1734window.cmplz_set_consent = function( category, value ) { 1735 cmplz_set_accepted_cookie_policy_id(); 1736 1737 //functional is always allow 1738 value = category === 'functional' ? 'allow' : value; 1739 const previous_value = cmplz_get_cookie( category ); 1740 1741 //keep checkboxes in banner and on cookie policy in sync 1742 //do this before the change check to ensure sync: https://github.com/Really-Simple-Plugins/complianz-gdpr/issues/324 1743 const checked = value === 'allow'; 1744 document.querySelectorAll( 'input.cmplz-' + category ).forEach( ( obj ) => { 1745 obj.checked = checked; 1746 } ); 1747 1748 //do not trigger a change event if nothing has changed. 1749 if ( previous_value === value ) { 1750 return; 1751 } 1752 1753 cmplz_set_cookie( category, value ); 1754 if ( value === 'allow' ) { 1755 cmplz_enable_category( category ); 1756 } 1757 1758 cmplz_wp_set_consent( category, value ); 1759 if ( category === 'statistics' ) { 1760 cmplz_wp_set_consent( 'statistics-anonymous', 'allow' ); 1761 } 1762 1763 const details = new Object(); 1764 details.category = category; 1765 details.value = value; 1766 details.region = complianz.region; 1767 details.categories = cmplz_accepted_categories();
1768 //when the status is changed, we may need to fire scripts again, so we reset it. 1769 cmplz_all_scripts_hook_fired = false; 1770 const event = new CustomEvent( 'cmplz_status_change', { detail: details } ); 1771 document.dispatchEvent( event ); 1772 if ( category === 'marketing' && value === 'deny' && previous_value === 'allow' ) { 1773 cmplz_integrations_revoke(); 1774 //give the code some time to finish, so our track status code can send a signal to the backend. 1775 setTimeout( function() { 1776 cmplz_reload_browser_compatible(); 1777 }, 500 ); 1778 } 1779}; 1780 1781/* 1782* In some browsers, like firefox, the reload does not force reload, but keeps cached data, causing e.g. Google Maps to load anyway. 1783*/ 1784function cmplz_reload_browser_compatible() { 1785 if ( navigator.userAgent.toLowerCase().indexOf( 'firefox' ) > -1 ) { 1786 const url = new URL( window.location.href ); 1787 url.searchParams.set( 'cmplz-force-reload', Date.now().toString() ); 1788 window.location.href = url.toString(); 1789 } else { 1790 window.location.reload(); 1791 } 1792} 1793 1794/* 1795 * We use ajax to check the consenttype based on region, otherwise caching could prevent the user specific warning 1796 * 1797 * */ 1798 1799let cmplz_user_data = []; 1800//check if it's already stored 1801if ( typeof ( Storage ) !== 'undefined' && sessionStorage.cmplz_user_data ) { 1802 cmplz_user_data = JSON.parse( sessionStorage.cmplz_user_data ); 1803} 1804 1805//if not stored yet, load. As features in the user object can be changed on updates, we also check for the version 1806if ( Number( complianz.geoip ) === 1 && ( cmplz_user_data.length === 0 || ( cmplz_user_data.version !== complianz.version ) || ( cmplz_user_data.banner_version !== complianz.banner_version ) ) ) { 1807 const request = new XMLHttpRequest(); 1808 let cmplzUserRegion = cmplz_get_url_parameter( window.location.href, 'cmplz_user_region' ); 1809 cmplzUserRegion = cmplzUserRegion ? '&cmplz_user_region=' + cmplzUserRegion : ''; 1810 request.open( 'GET', complianz.url + 'banner?' + complianz.locale + cmplzUserRegion, true ); 1811 request.setRequestHeader( 'Content-type', 'application/json' ); 1812 request.send(); 1813 request.onload = function() { 1814 cmplz_user_data = JSON.parse( request.response ); 1815 sessionStorage.cmplz_user_data = JSON.stringify( cmplz_user_data ); 1816 conditionally_show_banner(); 1817 }; 1818} else { 1819 conditionally_show_banner(); 1820} 1821 1822//keep the banner styled + consistent across Interactivity API client-side navigation 1823cmplzInitNavigationHook(); 1824 1825/* 1826 * when ab testing, or using records of consent, we want to keep track of the unique user id 1827 */ 1828 1829if ( Number( complianz.store_consent ) === 1 ) { 1830 const cmplz_id_cookie = cmplz_get_cookie( 'id' ); 1831 let cmplz_id_session = ''; 1832 let cmplz_id = ''; 1833 if ( typeof ( Storage ) !== 'undefined' && sessionStorage.cmplz_id ) { 1834 cmplz_id_session = JSON.parse( sessionStorage.cmplz_id ); 1835 } 1836 1837 if ( cmplz_id_cookie.length === 0 && cmplz_id_session.length > 0 ) { 1838 cmplz_id = cmplz_id_session; 1839 cmplz_set_cookie( 'id', cmplz_id ); 1840 } 1841 1842 if ( cmplz_id_cookie.length > 0 && cmplz_id_session.length === 0 ) { 1843 cmplz_id = cmplz_id_cookie; 1844 } 1845 1846 if ( typeof ( Storage ) !== 'undefined' ) { 1847 sessionStorage.cmplz_id = JSON.stringify( cmplz_id ); 1848 } 1849} 1850 1851// visibilitychange and pagehide work in more browsers hence we check if they are supported and try to use them 1852document.addEventListener( 'visibilitychange', function() { 1853 if ( document.visibilityState === 'hidden' ) { 1854 cmplz_track_status_end(); 1855 } 1856} ); 1857window.addEventListener( 'pagehide', cmplz_track_status_end, false ); 1858window.addEventListener( 'beforeunload', cmplz_track_status_end, false ); 1859 1860function cmplz_track_status_end() { 1861 if ( ! cmplz_consent_stored_once ) { 1862 cmplz_track_status(); 1863 } 1864} 1865 1866/* 1867 * This creates an API which devs can use to trigger actions in complianz. 1868 */ 1869document.addEventListener( 'cmplz_consent_action', function( e ) { 1870 cmplz_set_consent( e.detail.category, 'allow' ); 1871 cmplz_fire_categories_event(); 1872 cmplz_track_status(); 1873} ); 1874 1875/* 1876 * For both opt-in and opt-out, clicking cmplz-accept should result in accepting all categories 1877 */ 1878cmplz_add_event( 'click', '.cmplz-accept', function( e ) { 1879 e.preventDefault(); 1880 setTimeout( () => { 1881 cmplz_accept_all(); 1882 cmplz_set_banner_status( 'dismissed' ); 1883 cmplz_fire_categories_event(); 1884 cmplz_track_status(); 1885 }, 0 ); 1886} ); 1887 1888/* 1889 * Accept marketing cookies by clicking any other link cookie acceptance from a custom link 1890 */ 1891 1892//cmplz-accept-marketing is deprecated 1893cmplz_add_event( 'click', '.cmplz-accept-category, .cmplz-accept-marketing', function( e ) { 1894 e.preventDefault(); 1895 const obj = e.target; 1896 const service = obj.getAttribute( 'data-service' ); 1897 let category = obj.getAttribute( 'data-category' ); 1898 category = category ? category : 'marketing'; 1899 if ( Number( complianz.clean_cookies ) === 1 && typeof service !== 'undefined' && service ) { 1900 cmplz_set_service_consent( service, true ); 1901 cmplz_enable_category( '', 'general' ); 1902 cmplz_enable_category( '', service ); 1903 } else { 1904 //we're activating a category, so do not need service activation here. 1905 // cmplz_enable_category('', 'general'); 1906 cmplz_set_consent( category, 'allow' ); 1907 } 1908 cmplz_set_banner_status( 'dismissed' ); 1909 cmplz_fire_categories_event(); 1910 cmplz_track_status(); 1911} ); 1912 1913/* 1914 * Accept a specific service 1915 */ 1916cmplz_add_event( 'click', '.cmplz-accept-service', function( e ) { 1917 const obj = e.target; 1918 //INPUT should be handled by change event, these are checkboxes on the cookie policy 1919 const tagName = obj.tagName; 1920 if ( tagName === 'INPUT' ) { 1921 return; 1922 } 1923 const service = obj.getAttribute( 'data-service' ); 1924 if ( typeof service !== 'undefined' ) { 1925 cmplz_set_service_consent( service, true ); 1926 cmplz_enable_category( '', 'general' ); 1927 cmplz_enable_category( '', service ); 1928 } 1929 cmplz_fire_categories_event(); 1930 cmplz_track_status(); 1931} ); 1932/* 1933 * Accept a specific service 1934 */ 1935cmplz_add_event( 'change', '.cmplz-accept-service', function( e ) { 1936 const obj = e.target; 1937 const tagName = obj.tagName; 1938 const service = obj.getAttribute( 'data-service' ); 1939 if ( typeof service !== 'undefined' ) { 1940 //inputs are from the consent per service option on the cookie policy. 1941 if ( tagName === 'INPUT' ) { 1942 cmplz_set_banner_status( 'dismissed' ); 1943 if ( obj.checked ) { 1944 cmplz_set_service_consent( service, true ); 1945 cmplz_enable_category( '', service ); 1946 } else { 1947 cmplz_set_service_consent( service, false ); 1948 //give our track status time to finish 1949 setTimeout( function() { 1950 cmplz_reload_browser_compatible(); 1951 }, 500 ); 1952 } 1953 //if not input, it's a placeholder 1954 } else { 1955 e.preventDefault(); 1956 cmplz_set_service_consent( service, true ); 1957 cmplz_enable_category( '', 'general' ); 1958 cmplz_enable_category( '', service ); 1959 1960 //give our track status time to finish 1961 setTimeout( function() { 1962 cmplz_reload_browser_compatible(); 1963 }, 500 ); 1964 } 1965 } 1966 cmplz_fire_categories_event(); 1967 cmplz_track_status(); 1968} ); 1969 1970/* 1971 * On the banner, clicking a category should fire the category only after the save button is clicked. 1972 * 1973 */ 1974cmplz_add_event( 'click', '.cmplz-save-preferences', function( e ) { 1975 const obj = e.target; 1976 cmplz_banner = obj.closest( '.cmplz-cookiebanner' ); 1977 const consented_categories = []; 1978 1979 for ( const key in cmplz_categories ) { 1980 if ( cmplz_categories.hasOwnProperty( key ) ) { 1981 const category = cmplz_categories[ key ]; 1982 const categoryElement = cmplz_banner.querySelector( 'input.cmplz-' + category ); 1983 if ( categoryElement && categoryElement.checked ) { 1984 consented_categories.push( category ); 1985 } 1986 } 1987 } 1988 cmplz_fire_before_categories_consent( consented_categories ); 1989 for ( const key in cmplz_categories ) { 1990 if ( cmplz_categories.hasOwnProperty( key ) ) { 1991 const category = cmplz_categories[ key ]; 1992 if ( consented_categories.includes( category ) ) { 1993 cmplz_set_consent( category, 'allow' ); 1994 } else { 1995 cmplz_set_consent( category, 'deny' ); 1996 } 1997 } 1998 } 1999 cmplz_set_banner_status( 'dismissed' ); 2000 cmplz_fire_categories_event(); 2001 cmplz_track_status(); 2002} ); 2003 2004cmplz_add_event( 'click', '.cmplz-close', function() { 2005 cmplz_set_banner_status( 'dismissed' ); 2006} ); 2007 2008cmplz_add_event( 'click', '.cmplz-view-preferences', function( e ) { 2009 const obj = e.target; 2010 cmplz_banner = obj.closest( '.cmplz-cookiebanner' ); 2011 if ( cmplz_banner.querySelector( '.cmplz-categories' ).classList.contains( 'cmplz-fade-in' ) ) {
2012 cmplz_banner.classList.remove( 'cmplz-categories-visible' ); 2013 cmplz_banner.querySelector( '.cmplz-categories' ).classList.remove( 'cmplz-fade-in' ); 2014 cmplz_banner.querySelector( '.cmplz-view-preferences' ).style.display = 'block'; 2015 cmplz_banner.querySelector( '.cmplz-save-preferences' ).style.display = 'none'; 2016 } else { 2017 cmplz_banner.classList.add( 'cmplz-categories-visible' ); 2018 cmplz_banner.querySelector( '.cmplz-categories' ).classList.add( 'cmplz-fade-in' ); 2019 cmplz_banner.querySelector( '.cmplz-view-preferences' ).style.display = 'none'; 2020 cmplz_banner.querySelector( '.cmplz-save-preferences' ).style.display = 'block'; 2021 2022 // Set focus to the first category toggle when preferences are shown 2023 const firstCategory = cmplz_banner.querySelector( '.cmplz-categories .cmplz-category' ); 2024 if ( firstCategory ) { 2025 const toggleBtn = firstCategory.querySelector( '.cmplz-category-toggle' ); 2026 if ( toggleBtn ) { 2027 toggleBtn.focus(); 2028 } 2029 } 2030 } 2031} ); 2032/* 2033 * Toggle category description visibility via the expand/collapse button 2034 */ 2035cmplz_add_event( 'click', '.cmplz-category-toggle', function( e ) { 2036 const btn = e.target.closest( '.cmplz-category-toggle' ); 2037 if ( ! btn ) { 2038 return; 2039 } 2040 const isExpanded = btn.getAttribute( 'aria-expanded' ) === 'true'; 2041 btn.setAttribute( 'aria-expanded', String( ! isExpanded ) ); 2042 const category = btn.closest( '.cmplz-category' ); 2043 if ( category ) { 2044 const desc = category.querySelector( '.cmplz-description' ); 2045 if ( desc ) { 2046 desc.hidden = isExpanded; 2047 } 2048 } 2049} ); 2050/* 2051 * On the cookie policy, clicking a category should fire the category immediately 2052 * 2053 */ 2054cmplz_add_event( 'change', '.cmplz-manage-consent-container .cmplz-category', function() { 2055 for ( const key in cmplz_categories ) { 2056 if ( cmplz_categories.hasOwnProperty( key ) ) { 2057 const category = cmplz_categories[ key ]; 2058 const categoryElement = document.querySelector( '.cmplz-manage-consent-container input.cmplz-' + category ); 2059 if ( categoryElement ) { 2060 if ( categoryElement.checked ) { 2061 cmplz_set_consent( category, 'allow' ); 2062 } else { 2063 cmplz_set_consent( category, 'deny' ); 2064 } 2065 cmplz_set_banner_status( 'dismissed' ); 2066 cmplz_fire_categories_event(); 2067 cmplz_track_status(); 2068 } 2069 } 2070 } 2071} ); 2072 2073cmplz_add_event( 'click', '.cmplz-deny', function( e ) { 2074 e.preventDefault(); 2075 cmplz_set_banner_status( 'dismissed' ); 2076 cmplz_deny_all(); 2077} ); 2078 2079cmplz_add_event( 'click', 'button.cmplz-manage-settings', function( e ) { 2080 e.preventDefault(); 2081 const catsContainer = document.querySelector( '.cmplz-cookiebanner .cmplz-categories' ); 2082 const saveSettings = document.querySelector( '.cmplz-save-settings' ); 2083 const manageSettings = document.querySelector( 'button.cmplz-manage-settings' ); 2084 if ( ! cmplz_is_hidden( catsContainer ) ) { 2085 saveSettings.style.display = 'none'; 2086 manageSettings.style.display = 'block'; 2087 catsContainer.style.display = 'none'; 2088 } else { 2089 saveSettings.style.display = 'block'; 2090 manageSettings.style.display = 'none'; 2091 catsContainer.style.display = 'block'; 2092 } 2093} ); 2094 2095cmplz_add_event( 'click', 'button.cmplz-manage-consent', function( e ) { 2096 e.preventDefault(); 2097 cmplz_banner_trigger_source = 'manage-consent'; 2098 cmplz_set_banner_status( 'show' ); 2099} ); 2100 2101/* 2102 * Handle dismiss on scroll and dismiss on timeout 2103 */ 2104function cmplz_set_up_auto_dismiss() { 2105 // If the banner was already dismissed on a previous visit, skip entirely. 2106 // Without this guard, the timeout/scroll handlers would call cmplz_deny_all() 2107 // on every page load, revoking consent and triggering an infinite reload loop. 2108 if ( cmplz_get_banner_status() === 'dismissed' ) { 2109 return; 2110 } 2111 2112 // Handle dismiss on scroll â opt-out only 2113 if ( complianz.consenttype === 'optout' && Number( complianz.dismiss_on_scroll ) === 1 ) { 2114 const onWindowScroll = function() { 2115 if ( window.pageYOffset > Math.floor( 400 ) ) { 2116 window.removeEventListener( 'scroll', onWindowScroll ); 2117 this.onWindowScroll = null; 2118 // User may have explicitly accepted/denied before scrolling enough. 2119 if ( cmplz_get_banner_status() === 'dismissed' ) { 2120 return; 2121 } 2122 cmplz_set_banner_status( 'dismissed' ); 2123 cmplz_fire_categories_event(); 2124 cmplz_track_status(); 2125 } 2126 }; 2127 window.addEventListener( 'scroll', onWindowScroll ); 2128 } 2129 2130 // Handle dismiss on timeout
2131 const delay = parseInt( complianz.dismiss_timeout ); 2132 if ( complianz.consenttype === 'optout' && delay > 0 ) { 2133 window.setTimeout( function() { 2134 // User may have explicitly accepted/denied before the timeout fired. 2135 if ( cmplz_get_banner_status() === 'dismissed' ) { 2136 return; 2137 } 2138 cmplz_set_banner_status( 'dismissed' ); 2139 cmplz_fire_categories_event(); 2140 cmplz_track_status(); 2141 }, Math.floor( delay ) ); 2142 } 2143} 2144 2145/* 2146 * Fire a event wich passes all consented categories 2147 * Separated from the actual category consent because we want to bundle it in one event 2148 */ 2149function cmplz_fire_categories_event() { 2150 const details = new Object(); 2151 details.category = cmplz_highest_accepted_category(); 2152 details.categories = cmplz_accepted_categories(); 2153 details.region = complianz.region; 2154 const event = new CustomEvent( 'cmplz_fire_categories', { detail: details } ); 2155 document.dispatchEvent( event ); 2156} 2157/* 2158 * Track the status of current consent 2159 * @param status 2160 */ 2161 2162function cmplz_track_status( status ) { 2163 let cats = []; 2164 status = typeof status !== 'undefined' ? status : false; 2165 2166 const event = new CustomEvent( 'cmplz_track_status', { detail: status } ); 2167 document.dispatchEvent( event ); 2168 2169 if ( ! status ) { 2170 cats = cmplz_accepted_categories(); 2171 } else { 2172 cats = [ status ]; 2173 } 2174 2175 cmplz_set_category_as_body_class(); 2176 let saved_cats, saved_services; 2177 try { 2178 saved_cats = JSON.parse( cmplz_get_cookie( 'saved_categories' ) ); 2179 } catch ( e ) { 2180 saved_cats = {}; 2181 } 2182 try { 2183 saved_services = JSON.parse( cmplz_get_cookie( 'saved_services' ) ); 2184 } catch ( e ) { 2185 saved_services = {}; 2186 } 2187 const consented_services = cmplz_get_all_service_consents(); 2188 2189 //compare current cats with saved cats. When there are no changes, do nothing. 2190 if ( cmplz_equals( saved_cats, cats ) && cmplz_equals( saved_services, consented_services ) ) { 2191 return; 2192 } 2193 2194 if ( Number( complianz.store_consent ) !== 1 || cmplz_is_bot() || cmplz_is_speedbot() ) { 2195 return; 2196 } 2197 2198 //keep track of the fact that the status was saved at least once 2199 cmplz_set_cookie( 'saved_categories', JSON.stringify( cats ) ); 2200 cmplz_set_cookie( 'saved_services', JSON.stringify( consented_services ) ); 2201 cmplz_consent_stored_once = true; 2202 2203 const data = { 2204 consented_categories: cats, 2205 consented_services, 2206 consenttype: window.wp_consent_type, //store the source consenttype, as our complianz.consenttype will not include optinstats. 2207 }; 2208 2209 const request = new XMLHttpRequest(); 2210 request.open( 'POST', complianz.url + 'track', true ); 2211 request.setRequestHeader( 'Content-type', 'application/json' ); 2212 request.send( JSON.stringify( data ) ); 2213} 2214 2215/* 2216 * Get accepted categories 2217 * 2218 * @returns {string} 2219 */ 2220function cmplz_accepted_categories() { 2221 let consentedCategories = cmplz_categories; 2222 const consentedTemp = []; 2223 2224 //because array filter changes keys, we make a temp arr 2225 for ( const key in consentedCategories ) { 2226 if ( consentedCategories.hasOwnProperty( key ) ) { 2227 const category = consentedCategories[ key ]; 2228 if ( cmplz_has_consent( category ) ) { 2229 consentedTemp.push( category ); 2230 } 2231 } 2232 } 2233 2234 consentedCategories = consentedCategories.filter( function( value ) { 2235 return cmplz_in_array( value, consentedTemp ); 2236 } ); 2237 return consentedCategories; 2238} 2239 2240/* 2241 * Enable the checkbox for each category which was enabled 2242 * 2243 * */ 2244 2245function cmplz_sync_category_checkboxes() { 2246 for ( const key in cmplz_categories ) { 2247 if ( cmplz_categories.hasOwnProperty( key ) ) { 2248 const category = cmplz_categories[ key ]; 2249 if ( cmplz_has_consent( category ) || category === 'functional' ) { 2250 document.querySelectorAll( 'input.cmplz-' + category ).forEach( ( obj ) => { 2251 obj.checked = true; 2252 } ); 2253 } else { 2254 document.querySelectorAll( 'input.cmplz-' + category ).forEach( ( obj ) => { 2255 obj.checked = false; 2256 } ); 2257 } 2258 } 2259 2260 document.querySelectorAll( '.cmplz-accept-service' ).forEach( ( obj ) => { 2261 const service = obj.getAttribute( 'data-service' ); 2262 const category = obj.getAttribute( 'data-category' ); 2263 if ( cmplz_has_service_consent( service, category ) ) { 2264 obj.checked = true; 2265 } else if ( cmplz_is_service_denied( service ) ) { 2266 obj.checked = false;
2267 } else { 2268 //no consent on service level, check if it's category has consent. 2269 obj.checked = !! cmplz_has_consent( category ); 2270 } 2271 } ); 2272 } 2273} 2274 2275/* 2276 * Merge two objects 2277 * 2278 * */ 2279 2280function cmplz_merge_object( userdata, ajax_data ) { 2281 const output = {}; 2282 //first, we fill the important data. 2283 for ( const key in ajax_data ) { 2284 if ( ajax_data.hasOwnProperty( key ) ) { 2285 output[ key ] = ajax_data[ key ]; 2286 } 2287 } 2288 2289 //conditionally add static data 2290 for ( const key in userdata ) { 2291 //only add if not in ajax_data 2292 if ( ! ajax_data.hasOwnProperty( key ) || typeof ajax_data[ key ] === 'undefined' ) { 2293 if ( userdata.hasOwnProperty( key ) ) { 2294 output[ key ] = userdata[ key ]; 2295 } 2296 } 2297 } 2298 2299 return output; 2300} 2301 2302/* 2303 * Clear all our own cookies, to make sure path issues are resolved. 2304 * 2305 * 2306 */ 2307function cmplz_clear_cookies( cookie_part ) { 2308 if ( typeof document === 'undefined' ) { 2309 return false; 2310 } 2311 2312 let foundCookie = false; 2313 const secure = window.location.protocol === 'https:' ? ';secure' : ''; 2314 const expires = 'expires=' + new Date().toGMTString(); 2315 const pathParts = location.pathname.replace( /^\/|\/$/g, '' ).split( '/' ); 2316 2317 // Loop through all cookies 2318 document.cookie.split( '; ' ).forEach( function( cookie ) { 2319 const cookieName = cookie.split( ';' )[ 0 ].split( '=' )[ 0 ]; 2320 2321 // If the cookie contains cookie_part, try to delete it 2322 if ( cookieName.indexOf( cookie_part ) !== -1 ) { 2323 foundCookie = true; 2324 const domainParts = window.location.hostname.split( '.' ); 2325 const skipLast = domainParts.length > 1; 2326 2327 // Clear cookies on root path and subpaths 2328 pathParts.forEach( function( pathPart ) { 2329 const path = '/' + pathPart; 2330 document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=.' + domainParts.join( '.' ) + ';path=' + path; 2331 document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=.' + domainParts.join( '.' ) + ';path=' + path + '/'; 2332 } ); 2333 2334 // Clear cookies on parent domains 2335 while ( domainParts.length > 0 ) { 2336 const domain = '.' + domainParts.join( '.' ); 2337 domainParts.shift(); 2338 if ( skipLast && domainParts.length === 1 ) { 2339 domainParts.shift(); 2340 } 2341 2342 pathParts.forEach( function( pathPart ) { 2343 const path = '/' + pathPart; 2344 document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=' + domain + ';path=' + path; 2345 document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=' + domain + ';path=' + path + '/'; 2346 } ); 2347 } 2348 } 2349 } ); 2350 2351 // To prevent a double reload, we preserve the cookie policy id 2352 cmplz_set_accepted_cookie_policy_id(); 2353 2354 return foundCookie; 2355} 2356 2357/* 2358 * 2359 * If a policy is accepted, save this in the user policy id 2360 * 2361 * */ 2362 2363function cmplz_set_accepted_cookie_policy_id() { 2364 cmplz_set_cookie( 'policy_id', complianz.current_policy_id ); 2365} 2366 2367/* 2368 * For supported integrations, initialize the not consented state 2369 * 2370 * */ 2371 2372function cmplz_integrations_init() { 2373 const cookiesToSet = complianz.set_cookies; 2374 //check if we have scripts that need to be set to true on init. 2375 for ( const key in cookiesToSet ) { 2376 if ( cookiesToSet.hasOwnProperty( key ) && cookiesToSet[ key ][ 1 ] === '1' ) { 2377 cmplz_set_cookie( key, cookiesToSet[ key ][ 1 ], false ); 2378 } 2379 } 2380} 2381 2382/* 2383 * For supported integrations, revoke consent 2384 * 2385 * */ 2386function cmplz_integrations_revoke() { 2387 const cookiesToSet = complianz.set_cookies; 2388 for ( const key in cookiesToSet ) { 2389 if ( cookiesToSet.hasOwnProperty( key ) ) { 2390 const revokeValue = cookiesToSet[ key ][ 1 ]; 2391 cmplz_set_cookie( key, revokeValue, false ); 2392 // A zero/empty revoke value (e.g. integer 0 from some integrations) 2393 // means "clear this cookie" â preserves the original == false behaviour. 2394 // A meaningful value such as '1' (e.g. dont_track_me) is kept, not cleared. 2395 if ( Number( revokeValue ) === 0 ) { 2396 cmplz_clear_cookies( key ); 2397 } 2398 } 2399 } 2400} 2401 2402/* 2403 * For supported integrations, set consent 2404 * 2405 * */ 2406 2407function cmplz_set_integrations_cookies() { 2408 const cookiesToSet = complianz.set_cookies; 2409 for ( const key in cookiesToSet ) { 2410 if ( cookiesToSet.hasOwnProperty( key ) ) {
2411 cmplz_set_cookie( key, cookiesToSet[ key ][ 0 ], false ); 2412 } 2413 } 2414} 2415 2416function cmplz_get_url_parameter( sPageURL, sParam ) { 2417 if ( ! sPageURL || typeof sPageURL === 'undefined' || sPageURL.indexOf( '?' ) === -1 ) { 2418 return false; 2419 } 2420 2421 const queryString = sPageURL.split( '?' )[ 1 ]; 2422 if ( ! queryString ) { 2423 return false; 2424 } 2425 2426 const sURLVariables = queryString.split( '&' ); 2427 for ( let i = 0; i < sURLVariables.length; i++ ) { 2428 const sParameterName = sURLVariables[ i ].split( '=' ); 2429 if ( sParameterName[ 0 ] === sParam ) { 2430 return sParameterName[ 1 ] === undefined ? true : decodeURIComponent( sParameterName[ 1 ] ); 2431 } 2432 } 2433 2434 return false; 2435} 2436 2437/* 2438 * If the parameter cmplz_region_redirect =true is passed, find the user's region, and redirect. 2439 */ 2440function cmplz_maybe_auto_redirect() { 2441 const redirect = cmplz_get_url_parameter( window.location.href, 'cmplz_region_redirect' ); 2442 const region = cmplz_get_url_parameter( window.location.href, 'cmplz-region' ); 2443 if ( redirect && ! region ) { 2444 const newUrl = window.location.href.split( '#' )[ 0 ] + '&cmplz-region=' + complianz.region; 2445 const hash = window.location.hash; 2446 window.location.href = newUrl + hash; 2447 } 2448} 2449 2450/* 2451 * wrapper to set consent for wp consent API. If consent API is not active, do nothing 2452 * @param type 2453 * @param value 2454 */ 2455function cmplz_wp_set_consent( type, value ) { 2456 //wp consent api integration 2457 if ( typeof wp_set_consent === 'function' ) { 2458 wp_set_consent( type, value ); 2459 } 2460} 2461 2462function cmplz_start_clean() { 2463 if ( Number( complianz.clean_cookies ) === 1 ) { 2464 //check if it's already stored 2465 if ( typeof ( Storage ) !== 'undefined' ) { 2466 cmplz_cookie_data = JSON.parse( sessionStorage.getItem( 'cmplz_cookie_data' ) ); 2467 } 2468 //if not stored yet, load. As features in the user object can be changed on updates, we also check for the version 2469 if ( ! cmplz_cookie_data || cmplz_cookie_data.length === 0 ) { 2470 const request = new XMLHttpRequest(); 2471 request.open( 'GET', complianz.url + 'cookie_data', true ); 2472 request.setRequestHeader( 'Content-type', 'application/json' ); 2473 request.send(); 2474 request.onload = function() { 2475 cmplz_cookie_data = JSON.parse( request.response ); 2476 sessionStorage.setItem( 'cmplz_cookie_data', JSON.stringify( cmplz_cookie_data ) ); 2477 cmplz_setup_clean_interval(); 2478 }; 2479 } else { 2480 cmplz_setup_clean_interval(); 2481 } 2482 } 2483} 2484 2485/* 2486* Execute the cleanup of cookies 2487*/ 2488function cmplz_do_cleanup() { 2489 const consent_categories = [ 'preferences', 'statistics', 'marketing' ]; 2490 for ( const category of consent_categories ) { 2491 if ( ! cmplz_has_consent( category ) && cmplz_cookie_data.hasOwnProperty( category ) ) { 2492 const services = cmplz_cookie_data[ category ]; 2493 2494 for ( const service in services ) { 2495 if ( ! cmplz_has_service_consent( service, category ) ) { 2496 const cookies = services[ service ]; 2497 2498 for ( const item of cookies ) { 2499 cmplz_clear_cookies( item ); 2500 cmplz_clear_storage( item ); 2501 } 2502 } 2503 } 2504 } 2505 } 2506} 2507 2508function cmplz_setup_clean_interval() { 2509 // if the cookie data array is empty, return, nothing to do. 2510 if ( ! cmplz_cookie_data ) { 2511 return; 2512 } 2513 //check if it's already activated 2514 if ( cmplzCleanCookieInterval ) { 2515 return; 2516 } 2517 //one straight away, then every second 2518 cmplz_do_cleanup(); 2519 cmplzCleanCookieInterval = setInterval( cmplz_do_cleanup, 1000 ); 2520} 2521 2522/* 2523 * Clear an item from either session or localstorage 2524 * @param item 2525 */ 2526function cmplz_clear_storage( item ) { 2527 if ( typeof ( Storage ) !== 'undefined' ) { 2528 if ( localStorage.getItem( item ) ) { 2529 localStorage.removeItem( item ); 2530 } 2531 if ( sessionStorage.getItem( item ) ) { 2532 sessionStorage.removeItem( item ); 2533 } 2534 } 2535} 2536/* 2537 * Load revoke options 2538 */ 2539 2540function cmplz_load_manage_consent_container() { 2541 //don't load manage html code in the block editor 2542 const manage_consent_container = document.querySelector( '.cmplz-manage-consent-container' ); 2543 const is_block_editor = false;//document.querySelector('.wp-admin .cmplz-unlinked-mode'); 2544 if ( manage_consent_container && ! is_block_editor ) { 2545 const request = new XMLHttpRequest(); 2546 request.open( 'GET', complianz.url + 'manage_consent_html?' + complianz.locale, true ); 2547 request.setRequestHeader( 'Content-type', 'application/json' ); 2548 request.send(); 2549 request.onload = function() { 2550 const html = JSON.parse( request.response ); 2551 manage_consent_container.insertAdjacentHTML( 'beforeend', html ); 2552 cmplz_sync_category_checkboxes(); 2553 const nojavascript = document.querySelector( '#cmplz-manage-consent-container-nojavascript' ); 2554 nojavascript.style.display = 'none'; 2555 manage_consent_container.style.display = 'block'; 2556 const event = new CustomEvent( 'cmplz_manage_consent_container_loaded' ); 2557 document.dispatchEvent( event ); 2558 }; 2559 } 2560} 2561 2562/* 2563 * Make slider radio's tabable 2564 */ 2565 2566cmplz_add_event( 'keypress', '.cmplz-banner-slider label', function( e ) { 2567 const keycode = ( e.keyCode ? e.keyCode : e.which ); 2568 if ( keycode === 32 ) { 2569 e.target.click(); 2570 } 2571} ); 2572 2573/* 2574 * Make close button closable with enter 2575 */ 2576cmplz_add_event( 'keypress', '.cmplz-cookiebanner .cmplz-header .cmplz-close', function( e ) { 2577 const keycode = ( e.keyCode ? e.keyCode : e.which ); 2578 if ( keycode === 13 ) { 2579 e.target.click(); 2580 } 2581} ); 2582 2583/* 2584 * Compare two arrays 2585 * @param array 2586 * @returns {boolean} 2587 */ 2588function cmplz_equals( array_1, array_2 ) { 2589 // If both are objects (not arrays), compare their values properly 2590 if ( typeof array_1 === 'object' && array_1 !== null && ! Array.isArray( array_1 ) && 2591 typeof array_2 === 'object' && array_2 !== null && ! Array.isArray( array_2 ) ) { 2592 // Get all unique keys from both objects 2593 const allKeys = [ ...new Set( [ ...Object.keys( array_1 ), ...Object.keys( array_2 ) ] ) ]; 2594 2595 // Compare each key's value 2596 for ( const key of allKeys ) { 2597 if ( array_1[ key ] !== array_2[ key ] ) { 2598 return false;
2599 } 2600 } 2601 return true; 2602 } 2603 2604 // Original array comparison logic 2605 if ( ! Array.isArray( array_1 ) ) { 2606 array_1 = Object.keys( array_1 ); 2607 array_2 = Object.keys( array_2 ); 2608 } 2609 // if the other array is a falsy value, return 2610 if ( ! array_1 || ! array_2 ) { 2611 return false; 2612 } 2613 2614 // compare lengths - can save a lot of time 2615 if ( array_1.length !== array_2.length ) { 2616 return false; 2617 } 2618 2619 for ( let i = 0, l = array_1.length; i < l; i++ ) { 2620 // Check if we have nested arrays 2621 if ( array_1[ i ] instanceof Array && array_2[ i ] instanceof Array ) { 2622 // recurse into the nested arrays 2623 if ( ! cmplz_equals( array_1[ i ], array_2[ i ] ) ) { 2624 return false; 2625 } 2626 } else if ( array_1[ i ] !== array_2[ i ] ) { 2627 return false; 2628 } 2629 } 2630 return true; 2631} 2632 2633/* 2634* Copy all element atributes to the new element 2635*/ 2636function cmplzCopyAttributes( source, target ) { 2637 const excludes = [ 'type', 'data-service', 'data-category', 'async' ]; 2638 Array.from( source.attributes ).forEach( ( attribute ) => { 2639 if ( attribute.nodeName === 'data-script-type' && attribute.nodeValue === 'module' ) { 2640 target.setAttribute( 'type', 'module' ); 2641 target.removeAttribute( 'data-script-type' ); 2642 } else if ( ! excludes.includes( attribute.nodeName ) ) { 2643 target.setAttribute( attribute.nodeName, attribute.nodeValue ); 2644 } 2645 } ); 2646} 2647 2648/* 2649 * Hooked into jquery 2650 */ 2651const cmplz_has_wp_video = document.querySelector( '.cmplz-wp-video-shortcode' ); 2652let cmplz_times_checked = 0; 2653if ( 'undefined' !== typeof window.jQuery ) { 2654 jQuery( document ).ready( function( $ ) { 2655 if ( cmplz_has_wp_video ) { 2656 document.addEventListener( 'cmplz_enable_category', function() { 2657 cmplz_activate_wp_video(); 2658 } ); 2659 2660 const cmplzInterval = setInterval( function() { 2661 cmplz_times_checked += 1; 2662 if ( document.querySelector( '.cmplz-wp-video-shortcode' ) && cmplz_times_checked < 100 ) { 2663 cmplz_activate_wp_video(); 2664 } else { 2665 clearInterval( cmplzInterval ); 2666 } 2667 }, 500 ); 2668 } 2669 2670 /* 2671 * WordPress legacy shortcode 2672 */ 2673 function cmplz_activate_wp_video() { 2674 if ( ! document.querySelector( '.cmplz-wp-video-shortcode' ) ) { 2675 return; 2676 } 2677 const categories = cmplz_accepted_categories(); 2678 const services = cmplz_get_all_service_consents(); 2679 let selectorVideo = ''; 2680 const selectorVideos = []; 2681 for ( const c_key in categories ) { 2682 if ( categories.hasOwnProperty( c_key ) ) { 2683 const category = categories[ c_key ]; 2684 if ( category === 'functional' ) { 2685 break; 2686 } 2687 selectorVideos.push( '.cmplz-wp-video-shortcode[data-category="' + category + '"]' ); 2688 } 2689 } 2690 for ( const s_key in services ) { 2691 if ( services.hasOwnProperty( s_key ) ) { 2692 selectorVideos.push( '.cmplz-wp-video-shortcode[data-service="' + s_key + '"]' ); 2693 } 2694 } 2695 selectorVideo = selectorVideos.join( ',' ); 2696 let should_initialize_video = false; 2697 if ( selectorVideo.length > 0 ) { 2698 document.querySelectorAll( selectorVideo ).forEach( ( obj ) => { 2699 should_initialize_video = true; 2700 obj.setAttribute( 'controls', 'controls' ); 2701 obj.classList.add( 'wp-video-shortcode', 'cmplz-processed' ); 2702 obj.classList.remove( 'cmplz-wp-video-shortcode' ); 2703 obj.closest( '.cmplz-wp-video' ).classList.remove( 'cmplz-wp-video' ); 2704 2705 const blocked_notice = obj.closest( '.wp-video' ).querySelector( '.cmplz-blocked-content-notice' ); 2706 if ( blocked_notice ) { 2707 blocked_notice.parentElement.removeChild( blocked_notice ); 2708 } 2709 obj.classList.remove( 'cmplz-blocked-content-container' ); 2710 } ); 2711 } 2712 2713 if ( should_initialize_video ) { 2714 //in normal setup, this should work. e.g. with primavera theme not, then we init per element. 2715 if ( window.wp.mediaelement ) { 2716 window.wp.mediaelement.initialize(); 2717 } else { 2718 const settings = {}; 2719 settings.videoWidth = '100%'; 2720 settings.videoHeight = '100%'; 2721 settings.enableAutosize = true; 2722 $( '.wp-video-shortcode' ).mediaelementplayer( settings ); 2723 } 2724 } 2725 } 2726 2727 /* 2728 * Activate fitvids on the parent element if active 2729 * a.o. Beaverbuilder 2730 */ 2731 document.querySelectorAll( '.cmplz-video.cmplz-activated' ).forEach( ( obj ) => { 2732 cmplz_activate_fitvids( obj ); 2733 } ); 2734 2735 document.addEventListener( 'cmplz_enable_category', function() { 2736 document.querySelectorAll( '.cmplz-video.cmplz-activated' ).forEach( ( obj ) => { 2737 cmplz_activate_fitvids( obj ); 2738 } ); 2739 } ); 2740 2741 function cmplz_activate_fitvids( obj ) { 2742 //turn obj into jquery object
2743 const $obj = $( obj ); 2744 if ( typeof $obj.parent().fitVids === 'function' ) { 2745 $obj.parent().fitVids(); 2746 } 2747 } 2748 } ); 2749}
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.