PageSourceSearch

https://www.elah.org.il/wp-content/plugins/complianz-gdpr/cookiebanner/js/complianz.js?ver=1789057505

js elah.org.il collected 2026-09-25 06:21:16 UTC 91,674 bytes, 2,749 lines download raw bytes

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.