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.