PageSourceSearch

https://www.selbstverstaendlich-vielfalt.de/wp-content/plugins/con…/assets/js/conversionbridge.js?ver=1.11.1

js selbstverstaendlich-vielfalt.de collected 2026-10-02 18:32:42 UTC 35,293 bytes, 1,000 lines download raw bytes

1/**
2 * Centralized logging function that checks if console logging is enabled
3 * 
4 * @param {*} message - The message to log
5 * @param {string} type - The type of console message (log, error, warn, etc.)
6 */
7function conversionbridge_log( message, args = '', type = 'log' ) {
8	if ( conversionbridge_vars.console ) {
9		console[type]( '[Conversion Bridge] ' + message, args );
10	}
11}
12
13const ConversionBridge = (function () {
14
15	var pageview_sent = false;
16
17	function send_event( event_type, event_id = '', event_name = '', event_value = '', event_params = '' ) {
18		// Prevent duplicate pageviews.
19		if ( event_type == 'pageview' && pageview_sent ) {
20			return;
21		}
22
23		// Check consent based on has_consent array structure.
24		var has_consent = false;
25		if ( Array.isArray( conversionbridge_vars.has_consent ) ) {
26			// If has_consent is an array, check if analytics consent is granted.
27			if ( conversionbridge_vars.has_consent.includes( 'analytics' ) ) {
28				has_consent = true;
29			}
30		} else if ( conversionbridge_vars.has_consent ) {
31			// Backward compatibility: if has_consent is boolean and true.
32			has_consent = true;
33		}
34
35		// Prepare event parameters
36		var event_params_object = {};
37		if ( typeof event_params === 'object' && event_params !== null ) {
38			event_params_object = event_params;
39		}
40
41		var url_params = get_url_params();
42		if ( url_params && Object.keys(url_params).length > 0 ) {
43			event_params_object = { ...event_params_object, ...url_params };
44		}
45
46		// Build event data
47		var data = {
48			event_id: event_id,
49			event_name: event_name,
50			event_type: event_type,
51			event_value: event_value,
52			event_title: document.title,
53			event_params: event_params_object,
54			event_referrer: document.referrer,
55			event_url: window.location.href,
56			event_time: Date.now(),
57			session: get_session_id(),
58			pageview_id: conversionbridge_vars.pageview_id,
59			host: window.location.hostname,
60			security: conversionbridge_vars.security,
61		};
62
63		// If no consent, queue the event instead of sending (only if event queue is enabled)
64		if ( ! has_consent ) {
65			if ( conversionbridge_vars.event_queue ) {
66				conversionbridge_log( 'No analytics consent, queueing event: ' + event_type );
67				queue_event( data );
68			} else {
69				conversionbridge_log( 'No analytics consent, skipping event: ' + event_type + ' (event queue disabled)' );
70			}
71			return;
72		}
73	
74		// Convert data to string for sendBeacon
75		var dataString = JSON.stringify( data );
76		var blob = new Blob([dataString], { type: 'application/json' } );
77		var beacon_result = navigator.sendBeacon( conversionbridge_vars.rest_url, blob );
78
79		// Send data using sendBeacon first.
80		if ( ! beacon_result ) {
81
82			conversionbridge_log( 'Beacon failed, sending data using fetch API' );
83
84			// Send data using fetch API if beacon failed.
85			fetch( conversionbridge_vars.rest_url, {
86				method: 'POST',
87				credentials: 'include', // Include cookies in the request
88				headers: {
89					'Content-Type': 'application/json',
90				},
91				body: JSON.stringify( data )
92			})
93			.then( function( response ) {
94				if ( ! response.ok ) {
95					console.log( response );
96					throw new Error('Network response was not ok: ' + response.statusText);
97				}
98				return response.json();
99			})
100			.then( function( response ) {
101				handle_response( response, event_type );
102			})
103			.catch( function( error ) {
104				conversionbridge_log( "CB event error: " + error, 'error' );
105			});
106		} else {
107			conversionbridge_log( 'Beacon sent successfully' );
108			handle_response( { status: 'success' }, event_type );
109		}
110	}
111
112	function handle_response( response, event_type ) {
113		if ( response.status === 'success' ) {
114			if ( event_type == 'pageview' ) {
115				pageview_sent = true;
116			}
117			conversionbridge_log( "Event success: " + event_type );
118		} else if ( response.status === 'rejected' ) {
119			conversionbridge_log( "Event rejected by " + response.source + ": " + event_type + " / " + response.session + " / " + response.method );
120		} else {
121			conversionbridge_log( "Event failure: " + response.reason + " (" + event_type + ")" );
122		}
123	}
124	
125
126	function set_cookie( name, value, days ) {
127        let expires = "";
128        if ( days ) {
129            const date = new Date();
130            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
131            expires = "; expires=" + date.toUTCString();
132        }
133        
134        // Use Secure flag when on HTTPS, and add SameSite=Lax for better security
135        const secure = window.location.protocol === 'https:' ? '; Secure' : '';
136        document.cookie = name + "=" + (value || "") + expires + "; path=/; SameSite=Lax" + secure;
137		
138    }
139
140    function get_cookie( name ) {
141        const nameEQ = name + "=";
142        const ca = document.cookie.split(';');
143        for ( let i = 0; i < ca.length; i++ ) {
144            let c = ca[i];
145            while (c.charAt(0) === ' ') c = c.substring(1, c.length);
146            if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
147        }
148        return null;
149    }
150
151	function generate_session_id() {
152        return Math.random().toString(36).substring(2, 11) + new Date().getTime();
153    }
154
155	function get_session_id( skip_consent_check = false ) {
156        let session_id;
157
158		if ( ! conversionbridge_vars.journeys ) {
159			conversionbridge_log( "Journeys are disabled" );
160			return false;
161		}
162		
163		if ( conversionbridge_vars.tracking_method === 'fingerprint' ) {
164			conversionbridge_log( "Fingerprint tracking is enabled" );
165			return false;
166		}
167
168		// Check consent before generating or setting session ID cookie.
169		// Only skip this check when explicitly told to (e.g., when flushing queue after consent granted).
170		if ( ! skip_consent_check ) {
171			var has_consent = false;
172			if ( Array.isArray( conversionbridge_vars.has_consent ) ) {
173				has_consent = conversionbridge_vars.has_consent.includes( 'analytics' );
174			} else if ( conversionbridge_vars.has_consent ) {
175				has_consent = true;
176			}
177
178			if ( ! has_consent ) {
179				conversionbridge_log( "No analytics consent, not generating session ID" );
180				return false;
181			}
182		}
183
184        if ( ! session_id ) {
185            session_id = get_cookie( 'cb_session_id' );
186			conversionbridge_log( "Session ID from cookie: " + session_id );
187        }
188        if ( ! session_id ) {
189            session_id = generate_session_id();
190			conversionbridge_log( "Generating new session ID: " + session_id );
191        }
192
193        // Only set cookie if journey tracking is enabled.
194        set_cookie( 'cb_session_id', session_id, 365 ); // Setting the cookie to expire in 365 days.
195
196		return session_id;
197
198    }
199
200	function get_url_params() {
201        const params = {};
202        new URLSearchParams(window.location.search).forEach((value, key) => {
203            params[key] = value;
204        });
205		if ( Object.keys(params).length > 0 ) {
206			var data = {};
207			// If any of the query params match the conversionbridge_vars.params, add them to the data object.
208			for ( const param in conversionbridge_vars.params ) {
209				if ( params[param] ) {
210					data[param] = params[param];
211				}
212			}
213		}
214		return data;
215    }
216
217	/**
218	 * Queue event in localStorage when consent is not granted.
219	 *
220	 * @param {object} event_data - The event data to queue.
221	 */
222	function queue_event( event_data ) {
223		if ( ! window.localStorage ) {
224			conversionbridge_log( 'localStorage not available, cannot queue event' );
225			return;
226		}
227
228		try {
229			let queue = JSON.parse( localStorage.getItem( 'cb_event_queue' ) || '[]' );
230			
231			// Add timestamp for expiration
232			// Maintain backward compatibility with previous timestamp key.
233			if ( event_data.event_timestamp && ! event_data.event_time ) {
234				event_data.event_time = event_data.event_timestamp;
235				delete event_data.event_timestamp;
236			}
237
238			event_data.event_time = event_data.event_time || Date.now();
239			event_data.queued_at = Date.now();
240			
241			queue.push( event_data );
242			
243			// Limit queue size (keep last 100 events)
244			if ( queue.length > 100 ) {
245				queue = queue.slice( -100 );
246			}
247			
248			localStorage.setItem( 'cb_event_queue', JSON.stringify( queue ) );
249			conversionbridge_log( 'Event queued: ' + event_data.event_type + ' (queue size: ' + queue.length + ')' );
250		} catch ( error ) {
251			conversionbridge_log( 'Error queueing event: ' + error, 'error' );
252		}
253	}
254
255	/**
256	 * Flush queued events from localStorage and send them.
257	 */
258	function flush_event_queue() {
259		if ( ! window.localStorage ) {
260			return;
261		}
262
263		try {
264			let queue = JSON.parse( localStorage.getItem( 'cb_event_queue' ) || '[]' );
265			
266			if ( queue.length === 0 ) {
267				conversionbridge_log( 'No events in queue to flush' );
268				return;
269			}
270
271			// Remove expired events (older than 48 hours)
272			const expiration_time = 48 * 60 * 60 * 1000; // 48 hours in milliseconds
273			const now = Date.now();
274			queue = queue.filter( function( event ) {
275				return ( now - event.queued_at ) < expiration_time;
276			} );
277
278			if ( queue.length === 0 ) {
279				localStorage.removeItem( 'cb_event_queue' );
280				conversionbridge_log( 'All queued events expired' );
281				return;
282			}
283
284			conversionbridge_log( 'Flushing ' + queue.length + ' queued events' );
285
286			// Ensure session ID is generated before flushing.
287			// Skip consent check since we're flushing after consent was granted.
288			var session_id = get_session_id( true );
289			
290			// Process each queued event in chronological order
291			queue.forEach( function( event_data ) {
292				// Update session ID for all queued events (they may not have had a session ID when queued)
293				if ( session_id ) {
294					event_data.session = session_id;
295				}
296
297				// Maintain backward compatibility with previous timestamp key.
298				if ( event_data.event_timestamp && ! event_data.event_time ) {
299					event_data.event_time = event_data.event_timestamp;
300					delete event_data.event_timestamp;
301				}
302
303				// Ensure consent state reflects current allowances before sending
304				if ( Array.isArray( conversionbridge_vars.has_consent ) ) {
305					event_data.consent = [ ...conversionbridge_vars.has_consent ];
306				} else {
307					event_data.consent = conversionbridge_vars.has_consent;
308				}
309
310				// Ensure event time is set.
311				event_data.event_time = event_data.event_time || Date.now();
312
313				// Remove queued_at timestamp before sending
314				delete event_data.queued_at;
315
316				// Send the event directly (bypass consent check since we have consent now)
317				send_event_without_consent_check( 
318					event_data.event_type,
319					event_data.event_id,
320					event_data.event_name,
321					event_data.event_value,
322					event_data.event_params,
323					event_data
324				);
325			} );
326
327			// Clear queue after sending
328			localStorage.removeItem( 'cb_event_queue' );
329			conversionbridge_log( 'Event queue flushed and cleared' );
330		} catch ( error ) {
331			conversionbridge_log( 'Error flushing event queue: ' + error, 'error' );
332		}
333	}
334
335	/**
336	 * Clear queued events from localStorage.
337	 */
338	function clear_event_queue() {
339		if ( ! window.localStorage ) {
340			return;
341		}
342
343		try {
344			localStorage.removeItem( 'cb_event_queue' );
345			conversionbridge_log( 'Event queue cleared' );
346		} catch ( error ) {
347			conversionbridge_log( 'Error clearing event queue: ' + error, 'error' );
348		}
349	}
350
351	/**
352	 * Clean up expired events from the queue (events older than 48 hours).
353	 */
354	function cleanup_expired_events() {
355		if ( ! window.localStorage ) {
356			return;
357		}
358
359		try {
360			let queue = JSON.parse( localStorage.getItem( 'cb_event_queue' ) || '[]' );
361			
362			if ( queue.length === 0 ) {
363				return;
364			}
365
366			// Remove expired events (older than 48 hours)
367			const expiration_time = 48 * 60 * 60 * 1000; // 48 hours in milliseconds
368			const now = Date.now();
369			const original_length = queue.length;
370			
371			queue = queue.filter( function( event ) {
372				return ( now - event.queued_at ) < expiration_time;
373			} );
374
375			// If events were removed, update localStorage
376			if ( queue.length < original_length ) {
377				if ( queue.length === 0 ) {
378					localStorage.removeItem( 'cb_event_queue' );
379					conversionbridge_log( 'All queued events expired and removed' );
380				} else {
381					localStorage.setItem( 'cb_event_queue', JSON.stringify( queue ) );
382					conversionbridge_log( 'Removed ' + ( original_length - queue.length ) + ' expired events from queue' );
383				}
384			}
385		} catch ( error ) {
386			conversionbridge_log( 'Error cleaning up expired events: ' + error, 'error' );
387		}
388	}
389
390	/**
391	 * Send event without consent check (used when flushing queued events after consent granted).
392	 *
393	 * @param {string} event_type - The type of event.
394	 * @param {string} event_id - The event ID.
395	 * @param {string} event_name - The event name.
396	 * @param {string} event_value - The event value.
397	 * @param {object} event_params - The event parameters.
398	 * @param {object} prebuilt_data - Pre-built event data (optional, for queued events).
399	 */
400	function send_event_without_consent_check( event_type, event_id = '', event_name = '', event_value = '', event_params = '', prebuilt_data = null ) {
401		// For queued events, don't check pageview_sent flag - we want to send all historical events
402		var is_queued_event = prebuilt_data !== null;
403		
404		// Prevent duplicate pageviews only for non-queued events
405		if ( event_type == 'pageview' && pageview_sent && ! is_queued_event ) {
406			return;
407		}
408
409		var data;
410		
411		// Use prebuilt data if provided (for queued events), otherwise build it
412		if ( prebuilt_data ) {
413			// For queued events, preserve all original data including referrer and URL
414			data = Object.assign( {}, prebuilt_data );
415			// Update session ID if needed (queued events might not have session ID yet).
416			// Skip consent check since this function is called after consent was granted.
417			if ( ! data.session ) {
418				data.session = get_session_id( true );
419			}
420			// Don't update event_title or event_url for queued events - preserve original values.
421			// Preserve original referrer, URL, title, and all other data.
422		} else {
423			// Prepare event parameters
424			var event_params_object = {};
425			if ( typeof event_params === 'object' && event_params !== null ) {
426				event_params_object = event_params;
427			}
428
429			var url_params = get_url_params();
430			if ( url_params && Object.keys(url_params).length > 0 ) {
431				event_params_object = { ...event_params_object, ...url_params };
432			}
433
434			data = {
435				event_id: event_id,
436				event_name: event_name,
437				event_type: event_type,
438				event_value: event_value,
439				event_title: document.title,
440				event_params: event_params_object,
441				event_referrer: document.referrer,
442				event_url: window.location.href,
443				session: get_session_id( true ), // Skip consent check since this function is called after consent granted.
444				pageview_id: conversionbridge_vars.pageview_id,
445				host: window.location.hostname,
446				security: conversionbridge_vars.security,
447				consent: Array.isArray( conversionbridge_vars.has_consent ) ? [ ...conversionbridge_vars.has_consent ] : conversionbridge_vars.has_consent,
448				event_time: Date.now(),
449			};
450		}
451		
452		// Convert data to string for sendBeacon
453		var dataString = JSON.stringify( data );
454		var blob = new Blob([dataString], { type: 'application/json' } );
455		var beacon_result = navigator.sendBeacon( conversionbridge_vars.rest_url, blob );
456
457		// Send data using sendBeacon first.
458		if ( ! beacon_result ) {
459			conversionbridge_log( 'Beacon failed, sending data using fetch API' );
460
461			// Send data using fetch API if beacon failed.
462			fetch( conversionbridge_vars.rest_url, {
463				method: 'POST',
464				credentials: 'include', // Include cookies in the request
465				headers: {
466					'Content-Type': 'application/json',
467				},
468				body: JSON.stringify( data )
469			})
470			.then( function( response ) {
471				if ( ! response.ok ) {
472					console.log( response );
473					throw new Error('Network response was not ok: ' + response.statusText);
474				}
475				return response.json();
476			})
477			.then( function( response ) {
478				handle_response( response, event_type );
479			})
480			.catch( function( error ) {
481				conversionbridge_log( "CB event error: " + error, 'error' );
482			});
483		} else {
484			conversionbridge_log( 'Beacon sent successfully' );
485			handle_response( { status: 'success' }, event_type );
486		}
487	}
488
489    // Public methods and properties
490    return {
491        init: function () {
492			document.addEventListener( "DOMContentLoaded", function() {
493				if ( conversionbridge_vars.restart ) {
494					localStorage.setItem( 'cb_session_id', '' );
495					set_cookie( 'cb_session_id', '', 365 );
496					// Clear event queue on restart
497					clear_event_queue();
498					const currentUrl = new URL( window.location.href );
499					currentUrl.searchParams.delete( 'cb_restart' );
500					window.location.href = currentUrl.toString();
501				} else {
502					// Clean up expired events from queue
503					cleanup_expired_events();
504					send_event( 'pageview' );
505				}
506            });
507        },
508        event: function ( event_type, event_id, event_name, event_value, event_params ) {
509            // ensure that event_type is a non-empty string
510            if ( typeof event_name !== 'string' || ! event_name.trim()) {
511                return;
512            }
513
514			if ( typeof event_type !== 'string' || ! event_type.trim()) {
515				return;
516			}
517
518            // ensure that event_properties is an object
519            if ( typeof event_params !== 'object' ) {
520                event_params = {};
521            }
522
523            send_event( event_type, event_id, event_name, event_value, event_params );
524        },
525		pageview: function() {
526			send_event( 'pageview' );
527		},
528		cookie_consent: function( category, allowed = false ) {
529			// Initialize has_consent as array if not already.
530			if ( ! Array.isArray( conversionbridge_vars.has_consent ) ) {
531				conversionbridge_vars.has_consent = [];
532			}
533			
534			// Track previous consent state for analytics
535			var had_analytics_consent = conversionbridge_vars.has_consent.includes( 'analytics' );
536			
537			// Add or remove category from consent array.
538			if ( allowed ) {
539				if ( ! conversionbridge_vars.has_consent.includes( category ) ) {
540					conversionbridge_vars.has_consent.push( category );
541				}
542			} else {
543				const index = conversionbridge_vars.has_consent.indexOf( category );
544				if ( index > -1 ) {
545					conversionbridge_vars.has_consent.splice( index, 1 );
546				}
547			}
548			
549			// Handle analytics consent changes
550			var has_analytics_consent = conversionbridge_vars.has_consent.includes( 'analytics' );
551			
552			// If analytics consent was just granted, flush queued events (only if event queue is enabled)
553			if ( ! had_analytics_consent && has_analytics_consent && category === 'analytics' ) {
554				if ( conversionbridge_vars.event_queue ) {
555					conversionbridge_log( 'Analytics consent granted, flushing queued events' );
556					// Ensure session ID is generated before flushing.
557					// Skip consent check since consent was just granted.
558					get_session_id( true );
559					// Flush queued events.
560					flush_event_queue();
561				}
562			}
563			
564			// If analytics consent was just denied, clear queued events (only if event queue is enabled)
565			if ( had_analytics_consent && ! has_analytics_consent && category === 'analytics' ) {
566				if ( conversionbridge_vars.event_queue ) {
567					conversionbridge_log( 'Analytics consent denied, clearing queued events' );
568					clear_event_queue();
569				}
570			}
571			
572			window.dispatchEvent(
573				new CustomEvent( 'cb_consent', {
574					detail: {
575						category: category,
576						allowed: allowed,
577						granted: conversionbridge_vars.has_consent
578					}
579				})
580			);
581			conversionbridge_log( 'Cookie consent updated: ' + category + ' - ' + allowed + ' (granted: ' + conversionbridge_vars.has_consent.join(', ') + ')' );
582		}
583    };
584})();
585
586ConversionBridge.init();
587
588// ============================================
589// WP Consent API Integration (UNIVERSAL)
590// ============================================
591// This automatically works with ANY plugin that implements WP Consent API
592// including GDPR Cookie Consent, Complianz, and others
593(function() {
594	var consent_category_map = {
595		'statistics': 'analytics',
596		'statistics-anonymous': 'analytics',
597		'marketing': 'marketing',
598		'preferences': 'preferences'
599	};
600	
601	document.addEventListener( "wp_listen_for_consent_change", function (e) {
602		var changedConsentCategory = e.detail;
603		conversionbridge_log( 'WP Consent API consent changed: ' + JSON.stringify( changedConsentCategory ) );
604		var analytics_granted = false;
605		
606		for ( var wp_category in changedConsentCategory ) {
607			if ( changedConsentCategory.hasOwnProperty( wp_category ) ) {
608				var cb_category = consent_category_map[wp_category];
609				if ( cb_category ) {
610					var is_allowed = changedConsentCategory[wp_category] === 'allow';
611					ConversionBridge.cookie_consent( cb_category, is_allowed );
612					conversionbridge_log( 'WP Consent API: ' + wp_category + ' -> ' + cb_category + ' = ' + ( is_allowed ? 'granted' : 'denied' ) );
613					if ( cb_category === 'analytics' && is_allowed ) {
614						analytics_granted = true;
615					}
616				}
617			}
618		}
619		if ( analytics_granted ) {
620			ConversionBridge.pageview();
621		}
622	});
623	
624	document.addEventListener( 'DOMContentLoaded', function() {
625		setTimeout( function() {
626			if ( typeof wp_has_consent === 'function' ) {
627				conversionbridge_log( 'Checking initial WP Consent API state' );
628				for ( var wp_category in consent_category_map ) {
629					var cb_category = consent_category_map[wp_category];
630					var has_consent = wp_has_consent( wp_category );
631					if ( has_consent ) {
632						ConversionBridge.cookie_consent( cb_category, true );
633						conversionbridge_log( 'WP Consent API initial: ' + wp_category + ' (' + cb_category + ') granted' );
634					}
635				}
636			}
637		}, 500 );
638	});
639})();
640
641// ============================================
642// Plugin-Specific Integrations
643// ============================================
644
645if ( conversionbridge_vars.cookie_plugin == 'cookiebot' ) {
646	window.addEventListener( 'CookiebotOnAccept', function (e) {
647		conversionbridge_log( 'Cookiebot consent categories: ' + JSON.stringify( Cookiebot.consent ) );
648		if ( Cookiebot.consent.statistics ) {
649			conversionbridge_log( 'Cookiebot analytics allowed' );
650			ConversionBridge.cookie_consent( 'analytics', true );
651			ConversionBridge.pageview();
652		} else {
653			conversionbridge_log( 'Cookiebot analytics denied' );
654			ConversionBridge.cookie_consent( 'analytics', false );
655		} 
656		if ( Cookiebot.consent.marketing ) {
657			conversionbridge_log( 'Cookiebot marketing allowed' );
658			ConversionBridge.cookie_consent( 'marketing', true );
659		} else {
660			conversionbridge_log( 'Cookiebot marketing denied' );
661			ConversionBridge.cookie_consent( 'marketing', false );
662		}
663	}, false );
664}
665
666if ( conversionbridge_vars.cookie_plugin == 'cookieyes' ) {
667	document.addEventListener( "cookieyes_consent_update", function( eventData ) {
668		const data = eventData.detail;
669		conversionbridge_log( 'CookieYes consent categories: ' + data.accepted );
670		if ( data.accepted.includes( "analytics" ) ) {
671			conversionbridge_log( 'CookieYes analytics consent' );
672			ConversionBridge.cookie_consent( 'analytics', true );
673			ConversionBridge.pageview();
674	    } else {
675			conversionbridge_log( 'CookieYes analytics consent denied' );
676			ConversionBridge.cookie_consent( 'analytics', false );
677		}
678		if ( data.accepted.includes( "advertisement" ) ) {
679			conversionbridge_log( 'CookieYes marketing consent' );
680			ConversionBridge.cookie_consent( 'marketing', true );
681		} else {
682			conversionbridge_log( 'CookieYes marketing consent denied' );
683			ConversionBridge.cookie_consent( 'marketing', false );
684		}
685	});
686}
687
688if ( conversionbridge_vars.cookie_plugin == 'complianz' ) {
689	document.addEventListener( "cmplz_enable_category", function( consentData ) {
690		conversionbridge_log( 'Complianz consent categories: ' + consentData.detail.categories );
691		// If statistics is in the consentData.detail.categories array, then the user has consented to statistics.
692	    if ( consentData.detail.categories.includes( 'statistics' ) ) {
693			conversionbridge_log( 'Complianz analytics consent' );
694			ConversionBridge.cookie_consent( 'analytics', true );
695			ConversionBridge.pageview();
696	    } else {
697			conversionbridge_log( 'Complianz analytics consent denied' );
698			ConversionBridge.cookie_consent( 'analytics', false );
699		}
700		if ( consentData.detail.categories.includes( 'marketing' ) ) {
701			conversionbridge_log( 'Complianz marketing consent' );
702			ConversionBridge.cookie_consent( 'marketing', true );
703		} else {
704			conversionbridge_log( 'Complianz marketing consent denied' );
705			ConversionBridge.cookie_consent( 'marketing', false );
706		}
707	} );
708}
709
710if ( conversionbridge_vars.cookie_plugin == 'gdpr-cookie-compliance' ) {
711	document.addEventListener( "wp_listen_for_consent_change", function (e) {
712		var changedConsentCategory = e.detail;
713		conversionbridge_log( 'GDPR cookie compliance consent categories: ' + changedConsentCategory );
714		for ( var key in changedConsentCategory ) {
715			if ( changedConsentCategory.hasOwnProperty( key ) ) {
716				if ( key === 'statistics' && changedConsentCategory[key] === 'allow' ) {
717					conversionbridge_log( 'GDPR cookie compliance analytics consent' );
718					ConversionBridge.cookie_consent( 'analytics', true );
719					ConversionBridge.pageview();
720				} else if ( key === 'statistics' && changedConsentCategory[key] === 'deny' ) {
721					conversionbridge_log( 'GDPR cookie compliance analytics consent denied' );
722					ConversionBridge.cookie_consent( 'analytics', false );
723				} else if ( key === 'marketing' && changedConsentCategory[key] === 'allow' ) {
724					conversionbridge_log( 'GDPR cookie compliance marketing consent' );
725					ConversionBridge.cookie_consent( 'marketing', true );
726				}
727			}
728		}
729	});
730}
731
732if ( conversionbridge_vars.cookie_plugin == 'must-have-cookie' ) {
733	document.addEventListener( "mhcookie/save_consent", function() {	
734		var mhcookie = JSON.parse( localStorage.getItem( 'mhcookie' ) );
735		if ( mhcookie ) {
736			if ( mhcookie.groups.includes( 'all' ) || mhcookie.groups.includes( 'analytics' ) ) {
737				conversionbridge_log( 'Must Have Cookie analytics consent' );
738				ConversionBridge.cookie_consent( 'analytics', true );
739				ConversionBridge.pageview();
740			} else {
741				conversionbridge_log( 'Must Have Cookie analytics consent denied' );
742				ConversionBridge.cookie_consent( 'analytics', false );
743			}
744			if ( mhcookie.groups.includes( 'all' ) || mhcookie.groups.includes( 'marketing' ) ) {
745				conversionbridge_log( 'Must Have Cookie marketing consent' );
746				ConversionBridge.cookie_consent( 'marketing', true );
747			} else {
748				conversionbridge_log( 'Must Have Cookie marketing consent denied' );
749				ConversionBridge.cookie_consent( 'marketing', false );
750			}
751		}
752		// Reload the page
753		window.location.reload();
754	});
755}
756
757if ( conversionbridge_vars.cookie_plugin == 'wplegalpages' ) {
758	window.addEventListener( 'GdprCookieConsentOnAccept', function(e) {
759		var preferences = e.detail.wpl_user_preference;
760		conversionbridge_log( 'GDPR Cookie Consent preferences: ' + JSON.stringify( preferences ) );
761		if ( preferences.analytics === 'yes' ) {
762			ConversionBridge.cookie_consent( 'analytics', true );
763			ConversionBridge.pageview();
764		} else {
765			ConversionBridge.cookie_consent( 'analytics', false );
766		}
767		if ( preferences.marketing === 'yes' ) {
768			ConversionBridge.cookie_consent( 'marketing', true );
769		} else {
770			ConversionBridge.cookie_consent( 'marketing', false );
771		}
772	});
773	
774	window.addEventListener( 'GdprCookieConsentOnAcceptAll', function(e) {
775		conversionbridge_log( 'GDPR Cookie Consent: Accept All' );
776		ConversionBridge.cookie_consent( 'analytics', true );
777		ConversionBridge.cookie_consent( 'marketing', true );
778		ConversionBridge.pageview();
779	});
780	
781	window.addEventListener( 'GdprCookieConsentOnReject', function(e) {
782		conversionbridge_log( 'GDPR Cookie Consent: Reject' );
783		ConversionBridge.cookie_consent( 'analytics', false );
784		ConversionBridge.cookie_consent( 'marketing', false );
785	});
786}
787
788if ( conversionbridge_vars.cookie_plugin == 'termly' ) {
789	// Helper function to set a cookie
790	function conversionbridge_set_termly_cookie( name, value, days ) {
791		var expires = '';
792		if ( days ) {
793			var date = new Date();
794			date.setTime( date.getTime() + ( days * 24 * 60 * 60 * 1000 ) );
795			expires = '; expires=' + date.toUTCString();
796		}
797		var secure = window.location.protocol === 'https:' ? '; Secure' : '';
798		document.cookie = name + '=' + ( value || '' ) + expires + '; path=/; SameSite=Lax' + secure;
799	}
800	
801	// Helper function to process Termly consent state
802	function conversionbridge_process_termly_consent( consentState ) {
803		if ( ! consentState ) {
804			conversionbridge_log( 'Termly: No consent state provided' );
805			return;
806		}
807		conversionbridge_log( 'Termly consent state: ' + JSON.stringify( consentState ) );
808		var analytics_granted = false;
809		
810		// Check analytics consent (can be 'analytics' or 'performance')
811		if ( consentState.analytics === true || consentState.performance === true ) {
812			ConversionBridge.cookie_consent( 'analytics', true );
813			analytics_granted = true;
814			conversionbridge_set_termly_cookie( 'cb_termly_analytics', 'true', 365 );
815		} else {
816			ConversionBridge.cookie_consent( 'analytics', false );
817			conversionbridge_set_termly_cookie( 'cb_termly_analytics', 'false', 365 );
818		}
819		
820		// Check marketing consent (can be 'advertising' or 'marketing')
821		if ( consentState.advertising === true || consentState.marketing === true ) {
822			ConversionBridge.cookie_consent( 'marketing', true );
823			conversionbridge_set_termly_cookie( 'cb_termly_marketing', 'true', 365 );
824		} else {
825			ConversionBridge.cookie_consent( 'marketing', false );
826			conversionbridge_set_termly_cookie( 'cb_termly_marketing', 'false', 365 );
827		}
828		
829		// Send pageview if analytics was granted
830		if ( analytics_granted ) {
831			ConversionBridge.pageview();
832		}
833	}
834	
835	// Listen for consent given event
836	document.addEventListener( 'termly_consent_given', function() {
837		conversionbridge_log( 'Termly: Consent given' );
838		if ( typeof window.Termly !== 'undefined' && typeof window.Termly.getConsentState === 'function' ) {
839			var consentState = window.Termly.getConsentState();
840			conversionbridge_process_termly_consent( consentState );
841		}
842	});
843	
844	// Listen for consent denied event
845	document.addEventListener( 'termly_consent_denied', function() {
846		conversionbridge_log( 'Termly: Consent denied' );
847		ConversionBridge.cookie_consent( 'analytics', false );
848		ConversionBridge.cookie_consent( 'marketing', false );
849	});
850	
851	// Listen for general consent change event
852	document.addEventListener( 'termlyConsent', function() {
853		conversionbridge_log( 'Termly: Consent changed' );
854		if ( typeof window.Termly !== 'undefined' && typeof window.Termly.getConsentState === 'function' ) {
855			var consentState = window.Termly.getConsentState();
856			conversionbridge_process_termly_consent( consentState );
857		}
858	});
859	
860	// Check initial consent state on page load
861	document.addEventListener( 'DOMContentLoaded', function() {
862		setTimeout( function() {
863			if ( typeof window.Termly !== 'undefined' && typeof window.Termly.getConsentState === 'function' ) {
864				conversionbridge_log( 'Termly: Checking initial consent state' );
865				var consentState = window.Termly.getConsentState();
866				if ( consentState ) {
867					conversionbridge_process_termly_consent( consentState );
868				}
869			}
870		}, 1000 ); // Wait 1 second for Termly to fully initialize
871	});
872}
873
874async function cb_hash( value ) {
875
876	if ( ! value ) {
877		return '';
878	}
879
880	if ( typeof value !== 'string' ) {
881		conversionbridge_log( 'Cannot hash this value: ' + value );
882		return value;
883	}
884
885	// Normalize the value by trimming and converting to lowercase
886	const normalized = value.trim().toLowerCase();
887
888	// Encode the normalized email as a Uint8Array
889	const encoder = new TextEncoder();
890	const data = encoder.encode( normalized );
891
892	// Hash the data using SHA-256
893	const hashBuffer = await crypto.subtle.digest( 'SHA-256', data );
894
895	// Convert the hash to a hexadecimal string
896	const hashArray = Array.from(new Uint8Array(hashBuffer));
897	const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
898
899	return hashHex;
900}
901
902async function cb_get_hash( value ) {
903    try {
904        const hashValue = await cb_hash( value );
905        return hashValue;
906    } catch (error) {
907        conversionbridge_log( 'Error getting hash value: ' + error, 'error' );
908        return '';
909    }
910}
911
912/**
913 * Get the current value of a form element using a CSS selector.
914 * If the selector points to a container, it will find the first form element within that container.
915 *
916 * @param {string} selector - The CSS selector of the form element or container.
917 * @return {string|null} The value of the form element or null if no form element found.
918 */
919function cb_get_form_element_value( selector ) {
920	const element = document.querySelector( selector );
921	if ( ! element ) {
922		conversionbridge_log( 'Element not found for selector: ' + selector );
923		return null;
924	}
925
926	// Check if the element is a form element itself
927	const isFormElement = element.tagName === 'INPUT' || 
928						  element.tagName === 'SELECT' || 
929						  element.tagName === 'TEXTAREA';
930	
931	// If it's a single form element
932	if ( isFormElement ) {
933		// Handle different types of form elements
934		if ( element.type === 'checkbox' || element.type === 'radio' ) {
935			return element.checked ? element.value : '';
936		} else if ( element.tagName === 'SELECT' && element.multiple ) {
937			// Handle multi-select elements
938			const selectedValues = Array.from(element.selectedOptions).map(option => option.value);
939			return selectedValues.join(', ');
940		} else {
941			if ( element.type === 'email' ) {
942				return element.value.trim().toLowerCase();
943			} else {
944				// If element is text but has an @, return the value as email.
945				if ( element.value.includes( '@' ) ) {
946					return element.value.trim().toLowerCase();
947				} else {
948					return element.value;
949				}
950			}
951		}
952	} else {
953		conversionbridge_log( 'Element not of right type: ' + selector );
954	}
955
956	// If it's a container, check for multiple checkboxes first
957	const checkboxes = element.querySelectorAll('input[type="checkbox"]:checked');
958	if ( checkboxes.length > 0 ) {
959		conversionbridge_log( checkboxes + ' checkboxes' );
960		// Return all checked checkbox values as comma-separated string
961		const values = Array.from(checkboxes).map(checkbox => checkbox.value);
962		return values.join(', ');
963	}
964	
965	// Check for radio buttons
966	const radios = element.querySelectorAll('input[type="radio"]:checked');
967	if ( radios.length > 0 ) {
968		conversionbridge_log( radios + ' radios' );
969		// Return the value of the checked radio button
970		return radios[0].value;
971	}
972	
973	// Check for select elements
974	const select = element.querySelector('select');
975	if ( select ) {
976		conversionbridge_log( select + ' select' );
977		if ( select.multiple ) {
978			// Handle multi-select elements
979			const selectedValues = Array.from(select.selectedOptions).map(option => option.value);
980			return selectedValues.join(', ');
981		} else {
982			return select.value;
983		}
984	}
985	
986	// Check for other input types or textarea
987	const input = element.querySelector('input:not([type="checkbox"]):not([type="radio"]), textarea');
988	if ( input ) {
989		conversionbridge_log( input + ' input' );
990		if ( input.type === 'email' ) {
991			return input.value.trim().toLowerCase();
992		} else {
993			return input.value;
994		}
995	}
996
997	// If no form element found within the container
998	conversionbridge_log( 'No form element found within container: ' + selector );
999	return null;
1000}

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.