PageSourceSearch

https://www.amstetten.at/wp2021/wp-content/plugins/complianz-gdpr/…/js/complianz.js?ts=6ab58ff48af322026

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

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.