1window.metrion = { 2 // Configuration object 3 configuration: { 4 init_unix_timestamp: Date.now(), 5 //last_interaction_unix_timestamp_ms: Date.now(), 6 cookie_value_separator: "--", 7 user_cookie_name: metrion_api.user_cookie_name + "_js", 8 user_cookie_lifetime_milliseconds: 9 metrion_api.user_cookie_lifetime_milliseconds, 10 session_cookie_name: metrion_api.session_cookie_name, 11 session_cookie_lifetime_milliseconds: Number( 12 metrion_api.session_cookie_lifetime_milliseconds 13 ), 14 cookie_expiration_milliseconds: Number( 15 metrion_api.user_cookie_lifetime_milliseconds 16 ), 17 cookie_domain: window.location.hostname.split(/\./).slice(-2).join("."), 18 session_start_trigger: false, 19 session_start_reasons: [], 20 event_id_name: metrion_api.event_id_name, 21 browser_support_safe_uuid: true, 22 consent_cookie_name: metrion_api.consent_cookie_name, 23 cmp_selection: metrion_api.cmp_selection, 24 cmp_update_handled: false, 25 cmp_allow_marketing: false, 26 session_start_handled: false, 27 floodgate_name: metrion_api.floodgate_name, 28 onetrust_marketing_code: metrion_api.onetrust_marketing_code, 29 onetrust_analytics_code: metrion_api.onetrust_analytics_code, 30 floodgate_open: false, 31 floodgate_release_pending: false, 32 session_info_storage_name: metrion_api.session_info_storage_name, 33 event_broker_queue: [], 34 purchase_only_tracking: metrion_api.purchase_only_tracking, 35 allow_cookie_placement_before_explicit_consent: 36 metrion_api.allow_cookie_placement_before_explicit_consent, 37 click_ids_cookie_name: metrion_api.click_ids_cookie_name, 38 allow_pings: metrion_api.allow_pings, 39 blocking_detected: false, 40 prices_vat_included: metrion_api.metrion_vat_included, 41 prices_shipping_included: metrion_api.metrion_shipping_costs_included 42 }, 43 44 // The user manager object 45 user_manager: { 46 //Function to create the user cookie if not called by the PHP itself 47 ensure_user_cookie_existance: function (callback) { 48 var cookie_url = metrion_api.api_url.replace(/\/?$/, "/") + "cookies"; 49 var xhr = new XMLHttpRequest(); 50 xhr.open("POST", cookie_url, true); 51 xhr.setRequestHeader("Content-Type", "application/json"); 52 53 xhr.onreadystatechange = function () { 54 if (xhr.readyState === 4) { 55 56 var domain = window.metrion.helpers.get_cookie_domain(window.location.hostname); 57 var base_cookie_name = window.metrion.configuration.user_cookie_name.replace("_js", ""); 58 var header_was_stripped = (window.metrion.helpers.get_cookie_value(base_cookie_name) === null); 59 60 // Use the dynamic separator from config instead of hardcoding "--" 61 var separator = window.metrion.configuration.cookie_value_separator; 62 63 // A fresh user starts at session 0. The session_manager will immediately increment this to 1. 64 var initial_session_number = 0; 65 66 // IF SERVER/CDN RESPONDED NORMALLY (200 OK or 204 No Content) 67 if (xhr.status === 200 || xhr.status === 204) { 68 69 var response_data = null; 70 try { 71 if (xhr.responseText) { 72 response_data = JSON.parse(xhr.responseText); 73 } 74 } catch(e) { 75 window.metrion.helpers.log_debug("Failed to parse JSON cookie response", "warn"); 76 } 77 78 // SCENARIO A: Server returned JSON (CDN stripped header, but left body intact) 79 if (response_data && response_data.cookie_name && response_data.cookie_value) { 80 81 if (header_was_stripped) { 82 window.metrion.helpers.log_debug("Applying JSON fallback to create user cookie.", "log"); 83 var lifetime_ms = (response_data.expiration_days || 365) * 86400 * 1000; 84 85 window.metrion.helpers.set_cookie(response_data.cookie_name, response_data.cookie_value, lifetime_ms, "/", domain); 86 87 var parts = response_data.cookie_value.split(separator); 88 if (parts.length >= 3) { 89 var js_cookie_value = parts[0] + separator + parts[2]; 90 window.metrion.helpers.set_cookie(response_data.cookie_name + "_js", js_cookie_value, lifetime_ms, "/", domain); 91 } 92 93 window.metrion.send_event("status_event", { 94 "message": "Set-Cookie header stripped by CDN/cache, falling back to JSON response.", 95 "id": "fallback_cookie_creation_via_json" 96 }); 97 } 98 99 } 100 // SCENARIO B: CDN forced a 204 No Content AND destroyed the JSON body 101 else if (header_was_stripped) { 102 window.metrion.helpers.log_debug("CDN forced 204/stripped body. Generating user cookie locally.", "log"); 103 104 var local_uuid = window.metrion.helpers.generate_uuid(); 105 var local_lifetime_ms = window.metrion.configuration.user_cookie_lifetime_milliseconds || (365 * 86400 * 1000); 106 var local_timestamp = new Date().getTime() + local_lifetime_ms; 107 108 var local_cookie_value = local_uuid + separator + local_timestamp + separator + initial_session_number; 109 var local_js_value = local_uuid + separator + initial_session_number; 110 111 window.metrion.helpers.set_cookie(base_cookie_name, local_cookie_value, local_lifetime_ms, "/", domain); 112 window.metrion.helpers.set_cookie(base_cookie_name + "_js", local_js_value, local_lifetime_ms, "/", domain); 113 114 window.metrion.send_event("status_event", { 115 "message": "CDN forced empty 204 response. User cookie generated natively via JS fallback.", 116 "id": "fallback_cookie_creation_local_js" 117 }); 118 } 119 } 120 121 // SCENARIO C: Request was blocked or failed (Status 0, 403, 500, etc.) 122 else { 123 if (header_was_stripped) { 124 window.metrion.helpers.log_debug("Request blocked/failed (Status: " + xhr.status + "). Generating user cookie locally.", "log"); 125 126 var error_uuid = window.metrion.helpers.generate_uuid(); 127 var error_lifetime_ms = window.metrion.configuration.user_cookie_lifetime_milliseconds || (365 * 86400 * 1000); 128 var error_timestamp = new Date().getTime() + error_lifetime_ms; 129 130 var error_cookie_value = error_uuid + separator + error_timestamp + separator + initial_session_number; 131 var error_js_value = error_uuid + separator + initial_session_number; 132 133 window.metrion.helpers.set_cookie(base_cookie_name, error_cookie_value, error_lifetime_ms, "/", domain); 134 window.metrion.helpers.set_cookie(base_cookie_name + "_js", error_js_value, error_lifetime_ms, "/", domain); 135 136 window.metrion.send_event("status_event", { 137 "message": "Request blocked or failed (Status: " + xhr.status + "). User cookie generated natively via JS fallback.", 138 "id": "fallback_cookie_creation_network_error" 139 }); 140 } else { 141 window.metrion.helpers.log_debug("Metrion Cookie Error: " + window.metrion.helpers.safe_to_string(xhr.statusText), "log"); 142 } 143 } 144 145 // Continue with standard session creation 146 if (!window.metrion.session_manager.session_cookie_exists()) { 147 window.metrion.session_manager.create_session_cookie(); 148 } 149 callback(); // Call the next function after success 150 } 151 }; 152 xhr.send(); 153 }, 154 get_user_cookie_values: function () { 155 // Return in this order: session id, session expiration time, session start time, session number 156 var user_cookie_value = window.metrion.helpers.get_cookie_value( 157 window.metrion.configuration.user_cookie_name 158 ); 159 if (user_cookie_value !== null) { 160 var user_cookie_value_parts = user_cookie_value.split("--"); 161 return user_cookie_value_parts; 162 } 163 return [undefined, undefined]; 164 }, 165 }, 166 167 // The session manager object 168 session_manager: { 169 session_cookie_exists: function () { 170 var configuration = window.metrion.configuration; 171 172 return ( 173 window.metrion.helpers.get_cookie_value( 174 configuration.session_cookie_name 175 ) !== null 176 ); 177 }, 178 179 is_session_cookie_expired: function () { 180 var configuration = window.metrion.configuration; 181 182 if (!window.metrion.session_manager.session_cookie_exists()) { 183 return true; 184 } 185 186 var session_cookie_value = window.metrion.helpers.get_cookie_value( 187 configuration.session_cookie_name 188 ); 189 var session_expiration = parseInt(session_cookie_value.split("--")[1]); 190 return new Date(session_expiration) < new Date(); 191 }, 192 193 get_session_cookie_values: function () { 194 // Return in this order: session id, session expiration time, session start time, session number 195 var session_cookie_value = window.metrion.helpers.get_cookie_value( 196 window.metrion.configuration.session_cookie_name 197 ); 198 if (session_cookie_value !== null) { 199 var session_cookie_value_parts = session_cookie_value.split( 200 window.metrion.configuration.cookie_value_separator 201 ); 202 return session_cookie_value_parts; 203 } 204 return [undefined, undefined, undefined, undefined]; 205 }, 206 207 create_session_cookie: function () { 208 window.metrion.helpers.log_debug( 209 "Session cookie creation started:", 210 "log" 211 ); 212 var configuration = window.metrion.configuration; 213 var session_start_timestamp_seconds = Math.floor(Date.now() / 1000); 214 // Default value 215 var session_number = 1; 216 // If session number exists in the user cookie 217 var user_cookie_values = 218 window.metrion.user_manager.get_user_cookie_values(); 219 // If only user exists, get it from the user and increment it with 1 (since this function is started to launch a new session) 220 if (user_cookie_values[1] !== undefined) { 221 session_number = 222 window.metrion.helpers.safe_parse_int(user_cookie_values[1]) + 1; 223 } 224 var session_id = window.metrion.helpers.generate_uuid(); 225 var session_expiration = 226 new Date().getTime() + 227 configuration.session_cookie_lifetime_milliseconds; 228 var session_cookie_new_value = 229 session_id + 230 window.metrion.configuration.cookie_value_separator + 231 session_expiration + 232 window.metrion.configuration.cookie_value_separator + 233 session_start_timestamp_seconds; 234 // Set the session cookie with the session start time 235 //{session id}--{session expiration}--{session start time} 236 window.metrion.helpers.set_cookie( 237 configuration.session_cookie_name, 238 session_cookie_new_value, 239 configuration.session_cookie_lifetime_milliseconds, 240 "/", 241 window.metrion.helpers.get_cookie_domain(window.location.hostname) 242 ); 243 // Update the user cookie with the session number variable 244 //{user id}--{session number} 245 var user_cookie_new_value = user_cookie_values[0] + "--" + session_number; 246 window.metrion.helpers.set_cookie( 247 configuration.user_cookie_name, 248 user_cookie_new_value, 249 configuration.user_cookie_lifetime_milliseconds, 250 "/", 251 window.metrion.helpers.get_cookie_domain(window.location.hostname) 252 ); 253 return session_id; 254 }, 255 256 extend_session_cookie_lifetime: function () { 257 var configuration = window.metrion.configuration; 258 259 if (!window.metrion.session_manager.session_cookie_exists()) { 260 return undefined; 261 } 262 263 if (window.metrion.session_manager.is_session_cookie_expired()) { 264 return window.metrion.session_manager.create_session_cookie(); 265 } 266 267 var session_cookie_values = 268 window.metrion.session_manager.get_session_cookie_values(); 269 var new_session_expiration = 270 new Date().getTime() + 271 configuration.session_cookie_lifetime_milliseconds; 272 var updated_session_cookie_value = 273 session_cookie_values[0] + 274 window.metrion.configuration.cookie_value_separator + 275 new_session_expiration + 276 window.metrion.configuration.cookie_value_separator + 277 session_cookie_values[2]; 278 window.metrion.helpers.set_cookie( 279 configuration.session_cookie_name, 280 updated_session_cookie_value, 281 configuration.session_cookie_lifetime_milliseconds, 282 "/", 283 window.metrion.helpers.get_cookie_domain(window.location.hostname) 284 ); 285 286 return session_cookie_values[0]; 287 }, 288 }, 289 290 // Helper functions 291 helpers: { 292 generate_uuid_non_secure: function () { 293 window.metrion.configuration.browser_support_safe_uuid = false;
294 return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace( 295 /[018]/g, 296 function (c) { 297 return ( 298 c ^ 299 (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4))) 300 ).toString(16); 301 } 302 ); 303 }, 304 generate_uuid_secure: function () { 305 window.metrion.configuration.browser_support_safe_uuid = true; 306 return crypto.randomUUID(); 307 }, 308 generate_uuid: function () { 309 try { 310 if (typeof crypto.randomUUID === "function") { 311 return window.metrion.helpers.generate_uuid_secure(); 312 } else if (typeof crypto.getRandomValues === "function") { 313 return window.metrion.helpers.generate_uuid_non_secure(); 314 } 315 } catch (e) { 316 window.metrion.configuration.browser_support_safe_uuid = false; 317 return ( 318 Math.floor(100000000 + Math.random() * 900000000).toString() + 319 "." + 320 new Date().getTime().toString() 321 ); 322 } 323 324 return ( 325 Math.floor(100000000 + Math.random() * 900000000).toString() + 326 "." + 327 new Date().getTime().toString() 328 ); 329 }, 330 get_cookie_value: function (cookie_name) { 331 var value = "; " + document.cookie; 332 var parts = value.split("; " + cookie_name + "="); 333 334 if (parts.length == 2) { 335 return parts.pop().split(";").shift(); 336 } 337 338 return null; 339 }, 340 set_cookie: function ( 341 cookie_name, 342 cookie_value, 343 cookie_lifetime_in_milliseconds, 344 cookie_path, 345 cookie_domain 346 ) { 347 var cookie_secure = ";secure"; 348 var cookie_samesite = "lax"; 349 350 var d = new Date(); 351 d.setTime(d.getTime() + cookie_lifetime_in_milliseconds); 352 353 var domain_string = ""; 354 if (cookie_domain && cookie_domain !== "") { 355 domain_string = ";domain=" + cookie_domain; 356 } 357 358 document.cookie = 359 cookie_name + 360 "=" + 361 cookie_value + 362 ";expires=" + 363 d.toUTCString() + 364 ";path=" + 365 cookie_path + 366 domain_string + 367 cookie_secure + 368 ";SameSite=" + 369 cookie_samesite; 370 }, 371 set_consent_cookie: function (cookie_value) { 372 var hostname = window.location.hostname; 373 var cookie_domain = window.metrion.helpers.get_cookie_domain(hostname); 374 window.metrion.helpers.set_cookie( 375 window.metrion.configuration.consent_cookie_name, 376 cookie_value, 377 window.metrion.configuration.cookie_expiration_milliseconds, 378 "/", 379 cookie_domain 380 ); 381 }, 382 get_cookie_domain: function (hostname) { 383 if (hostname === 'localhost' || /^[0-9\.]+$/.test(hostname)) { 384 return ""; 385 } 386 var parts = hostname.split('.'); 387 if (parts.length <= 2) { 388 return "." + hostname; 389 } 390 var tld = parts[parts.length - 1]; 391 var sld = parts[parts.length - 2]; 392 393 if (tld.length === 2 && sld.length <= 3) { 394 return "." + parts.slice(-3).join('.'); 395 } 396 return "." + parts.slice(-2).join('.'); 397 }, 398 get_url_parameter_value: function (url_parameter_name) { 399 var urlParams = new URLSearchParams(window.location.search); 400 return urlParams.get(url_parameter_name) || null; 401 }, 402 log_debug: function (message, type) { 403 type = type || "log"; // Default parameter handling for ES5 compatibility 404 405 if ( 406 typeof window.metrion_api !== "undefined" && 407 window.metrion_api.debug_mode === "1" 408 ) { 409 switch (type) { 410 case "log": 411 console.log(message); 412 break; 413 case "warn": 414 console.warn(message); 415 break; 416 case "error": 417 console.error(message); 418 break; 419 default: 420 console.log(message); 421 } 422 } 423 }, 424 sha256_hash: function(string_to_hash) { 425 // 1. Check availability and otherwise return undefined 426 // ECMA 2015 friendly 427 if (typeof window === 'undefined' || !window.crypto || !window.crypto.subtle) { 428 window.metrion.helpers.log_debug("Web Crypto API is not available.", "log"); 429 return Promise.resolve(undefined); 430 } 431
432 try { 433 var string_to_hash_uint8 = new TextEncoder().encode(string_to_hash); 434 435 // 2. Return the Promise chain directly 436 return window.crypto.subtle.digest('SHA-256', string_to_hash_uint8) 437 .then(function(hashBuffer) { 438 var hashArray = Array.from(new Uint8Array(hashBuffer)); 439 return hashArray.map(function(b) { 440 // Fallback for padStart if you need to support truly ancient browsers like IE11 441 var hex = b.toString(16); 442 return hex.length === 1 ? '0' + hex : hex; 443 }).join(''); 444 }) 445 .catch(function(error) { 446 window.metrion.helpers.log_debug("SHA-256 hashing failed:", "log"); 447 return undefined; 448 }); 449 450 } catch (error) { 451 window.metrion.helpers.log_debug("SHA-256 hashing setup failed:", "log"); 452 return Promise.resolve(undefined); 453 } 454 }, 455 wait_for_jquery: function (callback) { 456 if (typeof jQuery !== "undefined") { 457 callback(); // jQuery is available, proceed with logic 458 } else { 459 // Use DOMContentLoaded to ensure the document is loaded 460 document.addEventListener("DOMContentLoaded", function () { 461 if (typeof jQuery !== "undefined") { 462 callback(); // jQuery is available, proceed with logic 463 } else { 464 window.metrion.helpers.log_debug("jQuery is not loaded yet", "log"); 465 } 466 }); 467 } 468 }, 469 safe_parse_float: function (value) { 470 try { 471 if (value === null || typeof value === "undefined") { 472 return 0; 473 } 474 var parsed = parseFloat(value); 475 if (isNaN(parsed)) { 476 return 0; 477 } 478 return parsed; 479 } catch (e) { 480 return 0; 481 } 482 }, 483 safe_parse_int: function (value) { 484 try { 485 if (value === null || typeof value === "undefined") { 486 return 0; 487 } 488 var parsed = parseInt(value); 489 if (isNaN(parsed)) { 490 return 0; 491 } 492 return parsed; 493 } catch (e) { 494 return 0; 495 } 496 }, 497 safe_to_string: function (value) { 498 if (value === null || typeof value === "undefined") { 499 return ""; 500 } 501 return String(value); 502 }, 503 504 clean_url: function (s) { 505 return s.replace(/\/$/, '').replace(/^https?:\/\/(www\.)?/, ''); 506 }, 507 post_json: function (url, payload) { 508 var body = JSON.stringify(payload); 509 try { 510 if (typeof fetch === "function") { 511 fetch(url, { 512 method: "POST", 513 headers: { "Content-Type": "application/json" }, 514 body: body, 515 keepalive: true, 516 credentials: "include", 517 mode: "cors", 518 }).catch(function (e) { 519 window.metrion.helpers.log_debug("post_json fetch error: " + e, "log"); 520 }); 521 window.metrion.helpers.log_debug("Event sent using fetch", "log"); 522 return; 523 } 524 } catch (e) { 525 window.metrion.helpers.log_debug("post_json fetch threw: " + e, "log"); 526 } 527 try { 528 var xhr = new XMLHttpRequest(); 529 xhr.open("POST", url, true); 530 xhr.setRequestHeader("Content-Type", "application/json"); 531 xhr.send(body); 532 window.metrion.helpers.log_debug("Event sent using XMLHttpRequest", "log"); 533 } catch (e2) { 534 window.metrion.helpers.log_debug("post_json xhr error: " + e2, "log"); 535 } 536 }, 537 }, 538 539 // All consent management functionality 540 consent_manager: { 541 // Function to load js of destination channels dynamically through an internal API 542 load_destination_js_dynamically: function () { 543 var script_src = 544 metrion_api.api_url.replace(/\/?$/, "/") + "destinations"; 545 var existing_script = document.querySelector( 546 'script[src="' + script_src + '"]' 547 ); 548 if (!existing_script) { 549 var script = document.createElement("script"); 550 script.src = script_src; 551 script.async = true; 552 document.body.appendChild(script); 553 window.metrion.helpers.log_debug( 554 "Loaded script: " + script_src.toString(), 555 "log" 556 ); 557 } else { 558 window.metrion.helpers.log_debug( 559 "Script already exists, skipping: " + script_src.toString(), 560 "log" 561 ); 562 } 563 }, 564 timeout_consent_listener_updates: function () { 565 // Reset flag after 3 seconds 566 setTimeout(function () { 567 window.metrion.configuration.cmp_update_handled = false;
568 }, 1500); 569 }, 570 // Initial consent check sequence 571 initial_consent_enforcement: function () { 572 // Start initial consent enforcement 573 window.metrion.helpers.log_debug( 574 "Initial consent enforcement triggered!", 575 "log" 576 ); 577 578 // First check the initial Metrion consent cookie value (if available) 579 var metrion_consent_cookie = window.metrion.helpers.get_cookie_value( 580 window.metrion.configuration.consent_cookie_name 581 ); 582 583 // Set initial check variables related to consent and system detection 584 var initial_metrion_allow_marketing = false; // Metrion itself assumes marketing is not allowed by default 585 window.metrion.configuration.blocking_detected = undefined; // No blocking detected by default 586 window.metrion.helpers.log_debug( 587 "Initial consent enforcement cookie value: " + 588 (metrion_consent_cookie || "null"), 589 "log" 590 ); 591 592 // If the Metrion consent cookie is NOT emtpy 593 if (metrion_consent_cookie !== null) { 594 window.metrion.helpers.log_debug( 595 "Consent cookie !== to null, so open floodgate.", 596 "log" 597 ); 598 window.metrion.configuration.floodgate_open = true; 599 try { 600 // If the value of the initial Metrion consent cookie is 1, then this indicates that here is no dynamic loading necessary 601 initial_metrion_allow_marketing = 602 JSON.parse(decodeURIComponent(metrion_consent_cookie)) 603 .allow_marketing === "1"; 604 // If the blocking parameter is detected with a high probability (1), change the check variable to 1 else to 0 (because detection is completed) 605 if ( 606 JSON.parse(decodeURIComponent(metrion_consent_cookie)).b === "1" 607 ) { 608 window.metrion.configuration.blocking_detected = "1"; 609 } else if ( 610 JSON.parse(decodeURIComponent(metrion_consent_cookie)).b === "0" 611 ) { 612 window.metrion.configuration.blocking_detected = "0"; 613 } 614 } catch (parse_error) { 615 window.metrion.helpers.log_debug( 616 "Metrion consent cookie parse error:", 617 "log" 618 ); 619 window.metrion.helpers.log_debug(parse_error, "log"); 620 } 621 } 622 623 // Log the selected CMP 624 window.metrion.helpers.log_debug( 625 "Initial consent enforcement for: " + 626 window.metrion.configuration.cmp_selection, 627 "log" 628 ); 629 630 /// CMP logic here 631 if (typeof window.metrion.consent_manager.cmp_init === "function") { 632 window.metrion.consent_manager.cmp_init(); 633 } 634 635 // Evaluate floodgate after to trigger the Metrion events 636 if ( 637 window.metrion.configuration.floodgate_open === true && 638 window.metrion.configuration.cmp_update_handled === true 639 ) { 640 window.metrion.consent_manager.evaluate_consent_floodgate(); 641 } 642 643 // If only initial evaluation is required, also load destinations dynamically when the initial metrion consent cookie is not available 644 if ( 645 window.metrion.configuration.floodgate_open === true && 646 metrion_consent_cookie === null && 647 window.metrion.configuration.cmp_allow_marketing === true 648 ) { 649 window.metrion.consent_manager.load_destination_js_dynamically(); 650 } else if ( 651 window.metrion.configuration.floodgate_open === true && 652 metrion_consent_cookie !== null && 653 initial_metrion_allow_marketing == false && 654 window.metrion.configuration.cmp_allow_marketing === true 655 ) { 656 window.metrion.consent_manager.load_destination_js_dynamically(); 657 } 658 659 return window.metrion.configuration.cmp_allow_marketing; 660 }, 661 // Function to evaluate the floodgate status 662 evaluate_consent_floodgate: function () { 663 window.metrion.helpers.log_debug("Start Floodgate evaluation...", "log"); 664 // First check if the floodgate is open 665 if (!window.metrion.configuration.floodgate_open) { 666 window.metrion.helpers.log_debug( 667 "Floodgate evaluation stopped. Floodgate closed", 668 "log" 669 ); 670 return; 671 } 672 673 // Retrieve floodgate events 674 var floodgate_events = []; 675 if ( 676 typeof localStorage !== "undefined" && 677 typeof window.metrion.floodgate_events === "undefined" 678 ) { 679 floodgate_events = 680 JSON.parse( 681 localStorage.getItem(window.metrion.configuration.floodgate_name) 682 ) || []; 683 } else if (typeof window.metrion.floodgate_events !== "undefined") { 684 floodgate_events = window.metrion.floodgate_events; 685 } 686 window.metrion.helpers.log_debug( 687 "Collected floodgated events: " + floodgate_events.length.toString(), 688 "log" 689 ); 690 691 // Store session info if available 692 if ( 693 typeof window.metrion.session_info !== "undefined" && 694 typeof sessionStorage !== "undefined" 695 ) { 696 sessionStorage.setItem( 697 window.metrion.configuration.session_info_storage_name, 698 window.metrion.session_info 699 ); 700 } 701 702 // Store click-id info if available 703 if (typeof window.metrion.click_ids !== "undefined") { 704 window.metrion.helpers.set_cookie( 705 window.metrion.configuration.click_ids_cookie_name, 706 window.metrion.click_ids, 707 window.metrion.configuration.cookie_expiration_milliseconds, 708 "/", 709 window.metrion.helpers.get_cookie_domain(window.location.hostname) 710 ); 711 } 712 713 // If no floodgate events exist, log and exit 714 if (floodgate_events.length === 0) { 715 window.metrion.helpers.log_debug("No Floodgate events sent", "log"); 716 return; 717 } 718 719 // Function to send floodgated events 720 function send_floodgated_events() { 721 window.metrion.helpers.log_debug("Sending floodgated events", "log"); 722 723 for (var i = 0; i < floodgate_events.length; i++) { 724 window.metrion.send_floodgated_event(floodgate_events[i]); 725 } 726 727 // Empty the floodgate to prevent double triggers 728 localStorage.removeItem(window.metrion.configuration.floodgate_name); 729 window.metrion.floodgate_events = []; 730 } 731 732 window.metrion.helpers.log_debug( 733 "Evaluation finished with floodgate status: " + 734 window.metrion.configuration.floodgate_open.toString(), 735 "log" 736 ); 737 // Ensure user cookie existence before sending events 738 if ( 739 window.metrion.user_manager.get_user_cookie_values()[0] === undefined 740 ) { 741 // A release is already waiting for the user cookie request to finish 742 // (e.g. UC_CONSENT and the dataLayer push both fire for one consent 743 // change). Starting a second one would send every floodgated event twice. 744 if (window.metrion.configuration.floodgate_release_pending === true) { 745 window.metrion.helpers.log_debug( 746 "Floodgate release already pending, skipping duplicate evaluation", 747 "log" 748 ); 749 return; 750 } 751 window.metrion.configuration.floodgate_release_pending = true; 752 try { 753 window.metrion.user_manager.ensure_user_cookie_existance(function () { 754 window.metrion.configuration.floodgate_release_pending = false;
755 window.metrion.helpers.log_debug( 756 "Dynamic user cookie ensuration function called", 757 "log" 758 ); 759 send_floodgated_events(); 760 window.metrion.helpers.log_debug( 761 "Send floodgated events function called with status: " + 762 window.metrion.configuration.floodgate_open.toString(), 763 "log" 764 ); 765 // Load destination js files dynamically 766 window.metrion.consent_manager.load_destination_js_dynamically(); 767 }); 768 } catch (cookie_request_error) { 769 // The request never started, so no callback will clear the flag 770 window.metrion.configuration.floodgate_release_pending = false; 771 throw cookie_request_error; 772 } 773 } else { 774 send_floodgated_events(); 775 } 776 }, 777 // Function to manually overwrite the consent settings when no CMP is selected 778 update_consent: function (consent_object) { 779 var metrion_consent_cookie = window.metrion.helpers.get_cookie_value( 780 window.metrion.configuration.consent_cookie_name 781 ); 782 if (metrion_consent_cookie !== null) { 783 var current_consent_object = JSON.parse( 784 decodeURIComponent(metrion_consent_cookie) 785 ); 786 window.metrion.configuration.blocking_detected = "0"; // No blocking detected by default 787 if (JSON.parse(decodeURIComponent(metrion_consent_cookie)).b === "1") { 788 window.metrion.configuration.blocking_detected = "1"; 789 } 790 var updated_consent_object = consent_object; 791 // Add the blocking parameter 792 updated_consent_object.b = 793 window.metrion.configuration.blocking_detected; 794 795 // Merge new consent values into the current consent object (the current is the one that needs updating) 796 for (var key in updated_consent_object) { 797 if ( 798 updated_consent_object.hasOwnProperty(key) && 799 current_consent_object.hasOwnProperty(key) 800 ) { 801 current_consent_object[key] = 802 updated_consent_object[key].toString(); 803 } 804 } 805 current_consent_object["unix"] = Date.now(); 806 807 window.metrion.helpers.set_consent_cookie(encodeURIComponent(JSON.stringify(current_consent_object))); 808 } 809 }, 810 }, 811 812 // Send floodgated events 813 send_floodgated_event: function (stored_event) { 814 var current_stored_event = stored_event; 815 var consent_cookie_value = window.metrion.helpers.get_cookie_value( 816 window.metrion.configuration.consent_cookie_name 817 ); 818 var metrion_consent_cookie_value = {}; 819 if (consent_cookie_value) { 820 try { 821 metrion_consent_cookie_value = JSON.parse( 822 decodeURIComponent(consent_cookie_value) 823 ); 824 } catch (e) { 825 // handle parse error 826 } 827 } 828 var allow_uid = metrion_consent_cookie_value.allow_uid === "1"; 829 var allow_sid = metrion_consent_cookie_value.allow_sid === "1"; 830 831 // Apply consent settings on floodgated events 832 if (allow_uid) { 833 current_stored_event[metrion_api.user_cookie_name] = 834 window.metrion.user_manager.get_user_cookie_values()[0]; 835 } else { 836 delete current_stored_event[metrion_api.user_cookie_name]; 837 } 838 if (allow_sid) { 839 current_stored_event[metrion_api.session_cookie_name] = 840 window.metrion.session_manager.get_session_cookie_values()[0]; 841 } else { 842 delete current_stored_event[metrion_api.session_cookie_name]; 843 } 844 // Overwrite the consent object with the recent change 845 current_stored_event.consent = metrion_consent_cookie_value; 846 847 // Add additional floodgate context which we don't want stored before consent. 848 current_stored_event["screen_height"] = screen.height || ""; 849 current_stored_event["screen_width"] = screen.width || ""; 850 current_stored_event["user_agent"] = navigator.userAgent || ""; 851 current_stored_event["language"] = navigator.language || ""; 852 current_stored_event["consent"] = metrion_consent_cookie_value; 853 current_stored_event["event_body"]["floodgated"] = true; 854 current_stored_event["event_body"]["floodgate_release_unix_timestamp"] = 855 Date.now(); 856 current_stored_event["event_body"]["session_number"] = 857 window.metrion.user_manager.get_user_cookie_values()[1]; 858 current_stored_event["event_body"]["session_start_time"] = 859 window.metrion.session_manager.get_session_cookie_values()[2]; 860 //current_stored_event["event_body"]["engaged_time_ms"] = 861 // current_stored_event["event_unix_timestamp"] - 862 // window.metrion.configuration.last_interaction_unix_timestamp_ms; 863 //window.metrion.configuration.last_interaction_unix_timestamp_ms = 864 // current_stored_event["event_unix_timestamp"]; 865 866 // Prepare for data sending to API 867 window.metrion.helpers.log_debug("Sending event data to API...", "log"); 868 window.metrion.helpers.post_json(metrion_api.api_url, stored_event); 869 870 // Push the event to the event broker 871 window.metrion.configuration.event_broker_queue.push({ 872 event_name: current_stored_event.event_name, 873 event_data: current_stored_event, 874 }); 875 }, 876 877 // Send event function 878 send_event: function (event_name, event_body) { 879 // Construct the event context 880 var event_data = {};
881 event_data[metrion_api.event_id_name] = 882 window.metrion.helpers.generate_uuid(); 883 event_data["event_name"] = event_name; 884 event_data["event_unix_timestamp"] = Date.now(); 885 event_data["event_timestamp"] = new Date().toISOString(); 886 event_data["location"] = { 887 protocol: window.location.protocol || "", 888 host: window.location.host || "", 889 path: window.location.pathname || "", 890 query: window.location.search || "", 891 hash: window.location.hash || "", 892 referrer: document.referrer || "", 893 title: document.title || "", 894 }; 895 event_data["event_body"] = event_body || {}; 896 event_data["event_body"]["browser_support_safe_uuid"] = 897 window.metrion.configuration.browser_support_safe_uuid; 898 event_data["event_body"]["init_unix_timestamp"] = 899 window.metrion.configuration.init_unix_timestamp; 900 event_data["event_body"]["session_number"] = 901 window.metrion.user_manager.get_user_cookie_values()[1]; 902 event_data["event_body"]["session_start_time"] = 903 window.metrion.session_manager.get_session_cookie_values()[2]; 904 905 // If consent cookie is available and floodgate open, handle based on the metrion consent cookie. Otherwise the event will be floodgated later 906 window.metrion.helpers.log_debug( 907 "Floodgate open: " + 908 window.metrion.configuration.floodgate_open.toString(), 909 "log" 910 ); 911 window.metrion.helpers.log_debug( 912 "Consent cookie value: " + 913 window.metrion.helpers.get_cookie_value( 914 decodeURIComponent(window.metrion.configuration.consent_cookie_name) 915 ), 916 "log" 917 ); 918 window.metrion.helpers.log_debug( 919 "User cookie value: " + 920 window.metrion.user_manager.get_user_cookie_values()[0], 921 "log" 922 ); 923 924 // Only push events to the metrion API if the floodgates are open! 925 if (window.metrion.configuration.floodgate_open) { 926 // First check consent information 927 var consent_cookie_value = window.metrion.helpers.get_cookie_value( 928 window.metrion.configuration.consent_cookie_name 929 ); 930 var metrion_consent_cookie_value = {}; 931 if (consent_cookie_value) { 932 // A corrupted consent cookie must not stop the event from being sent. 933 // Same fallback as send_floodgated_event(): no consent information, so 934 // no identifiers are attached. 935 try { 936 metrion_consent_cookie_value = JSON.parse( 937 decodeURIComponent(consent_cookie_value) 938 ); 939 } catch (e) { 940 window.metrion.helpers.log_debug( 941 "Metrion consent cookie parse error in send_event", 942 "log" 943 ); 944 metrion_consent_cookie_value = {}; 945 } 946 } 947 var allow_uid = metrion_consent_cookie_value.allow_uid === "1"; 948 var allow_sid = metrion_consent_cookie_value.allow_sid === "1"; 949 if (allow_uid) { 950 event_data[metrion_api.user_cookie_name] = 951 window.metrion.user_manager.get_user_cookie_values()[0]; 952 } 953 if (allow_sid) { 954 event_data[metrion_api.session_cookie_name] = 955 window.metrion.session_manager.get_session_cookie_values()[0]; 956 } 957 958 // Add other event-level context 959 event_data["screen_height"] = screen.height || ""; 960 event_data["screen_width"] = screen.width || ""; 961 event_data["user_agent"] = navigator.userAgent || ""; 962 event_data["language"] = navigator.language || ""; 963 event_data["consent"] = metrion_consent_cookie_value; 964 965 // Trigger the main Metrion event 966 window.metrion.helpers.log_debug("Sending event data to API...", "log"); 967 window.metrion.helpers.post_json(metrion_api.api_url, event_data); 968 969 // Push the event to the event broker 970 window.metrion.configuration.event_broker_queue.push({ 971 event_name: event_name, 972 event_data: event_data, 973 }); 974 } 975 // Floodgate closed, store event behind floodgate and send ping 976 else { 977 if (window.metrion.configuration.allow_pings === "1") { 978 //Trigger the ping 979 this.send_ping(event_name, event_data); 980 } 981 982 if ( 983 typeof localStorage !== "undefined" && 984 window.metrion.configuration 985 .allow_cookie_placement_before_explicit_consent === "1" 986 ) { 987 //Append to floodgate array in localStorage which is persisstent across pages. 988 window.metrion.floodgate_events = 989 JSON.parse( 990 localStorage.getItem(window.metrion.configuration.floodgate_name) 991 ) || []; 992 window.metrion.floodgate_events.push(event_data); 993 window.metrion.helpers.log_debug( 994 "Event floodgated using localStorage", 995 "log" 996 ); 997 localStorage.setItem( 998 window.metrion.configuration.floodgate_name, 999 JSON.stringify(window.metrion.floodgate_events) 1000 ); 1001 } else { 1002 //Append to floodgate array in JS variable which is not persisstent across pages! 1003 window.metrion.floodgate_events = window.metrion.floodgate_events || []; 1004 window.metrion.floodgate_events.push(event_data); 1005 window.metrion.helpers.log_debug( 1006 "Event floodgated using JS variable", 1007 "log" 1008 ); 1009 } 1010 } 1011 }, 1012 1013 // Send anonymous ping function 1014 send_ping: function (event_name, event_body) { 1015 var ping_url = metrion_api.api_url.replace(/\/?$/, "/") + "ping/"; 1016 var origin = window.location.href; 1017 var referrer = document.referrer; 1018 1019 window.metrion.helpers.post_json(ping_url, { 1020 origin: origin, 1021 referrer: referrer, 1022 event: event_name,
1023 id: event_body[metrion_api.event_id_name], 1024 }); 1025 }, 1026 1027 // Advertising click ID capture and storage. 1028 // 1029 // Cookie format: {"<param>": ["<click_id>", <unix_milliseconds>]}, e.g. 1030 // {"gclid":["123",1725095000000]}. Milliseconds to match event_unix_timestamp 1031 // and every other event-level timestamp (all Date.now()); one shared cookie 1032 // keeps the value small, and at most max_click_ids touchpoints are kept with 1033 // the oldest evicted first. 1034 click_id_manager: { 1035 // Maximum number of click IDs kept in the cookie 1036 max_click_ids: 6, 1037 // Secondary guard for the 4KB per-domain cookie budget. encodeURIComponent 1038 // output is ASCII, so string length equals the byte length of the value. 1039 max_cookie_value_bytes: 3072, 1040 // Used when no cookie lifetime is configured 1041 default_cookie_lifetime_milliseconds: 365 * 86400 * 1000, 1042 // The canonical list lives in config/advertising_params.json and arrives as 1043 // metrion_api.advertising_params. This copy is only the safety net for when 1044 // that file cannot be read, so keep the two in sync. 1045 fallback_advertising_params: [ 1046 "__hsfp", 1047 "__hssc", 1048 "__hstc", 1049 "__s", 1050 "_hsenc", 1051 "_openstat", 1052 "dclid", 1053 "fbclid", 1054 "gclid", 1055 "gbraid", 1056 "wbraid", 1057 "hsCtaTracking", 1058 "mc_eid", 1059 "mkt_tok", 1060 "ml_subscriber", 1061 "ml_subscriber_hash", 1062 "msclkid", 1063 "oly_anon_id", 1064 "oly_enc_id", 1065 "rb_clickid", 1066 "s_cid", 1067 "vero_conv", 1068 "vero_id", 1069 "wickedid", 1070 "yclid", 1071 "scid", 1072 "ttclid", 1073 "li_fat_id", 1074 "cnvsn_id", 1075 "nclid", 1076 "clickid", 1077 "tid", 1078 "clickId", 1079 "channelId", 1080 "advertiserId", 1081 "_kx", 1082 "twclid", 1083 "epik", 1084 "rdt_cid", 1085 "sccid", 1086 "awc", 1087 "tblci", 1088 "ob_click_id", 1089 "dicid", 1090 "irclickid", 1091 "vmcid" 1092 ], 1093 1094 // Allowlist of URL parameters to capture 1095 get_advertising_params: function () { 1096 var manager = window.metrion.click_id_manager; 1097 var configured = null; 1098 if (typeof metrion_api !== "undefined" && metrion_api) { 1099 configured = metrion_api.advertising_params; 1100 } 1101 1102 var allowlist = []; 1103 if (Object.prototype.toString.apply(configured) === "[object Array]") { 1104 for (var i = 0; i < configured.length; i++) { 1105 if (typeof configured[i] === "string" && configured[i] !== "") { 1106 allowlist.push(configured[i]); 1107 } 1108 } 1109 } 1110 1111 if (allowlist.length > 0) { 1112 return allowlist; 1113 } 1114 1115 window.metrion.helpers.log_debug( 1116 "No central advertising parameter list available, using built-in fallback", 1117 "log" 1118 ); 1119 return manager.fallback_advertising_params; 1120 }, 1121 1122 // Allowlisted parameters present in the current URL, as [{name, value}]. 1123 // Matching is on the exact parameter name: a substring test against the query 1124 // string also matched other parameters' names and values (the query 1125 // "?irclickid=x" contains "clickid"), which stored empty click IDs. 1126 detect_in_url: function () { 1127 var manager = window.metrion.click_id_manager; 1128 var allowlist = manager.get_advertising_params(); 1129 var detected = []; 1130 1131 for (var i = 0; i < allowlist.length; i++) { 1132 var value = window.metrion.helpers.get_url_parameter_value(allowlist[i]); 1133 if (typeof value === "string" && value !== "") { 1134 detected.push({ name: allowlist[i], value: value }); 1135 } 1136 } 1137 return detected; 1138 }, 1139 1140 // Separates a seconds-scale timestamp from a milliseconds one: 1e11 as 1141 // milliseconds is 1973, and as seconds it is the year 5138, so nothing real 1142 // falls on the wrong side. 1143 milliseconds_scale_threshold: 100000000000, 1144 1145 // One stored entry as [click_id, unix_milliseconds], or null when unusable 1146 normalize_entry: function (value) { 1147 var manager = window.metrion.click_id_manager; 1148 1149 // Current format: [click_id, unix_milliseconds] 1150 if (Object.prototype.toString.apply(value) === "[object Array]") { 1151 var click_id = window.metrion.helpers.safe_to_string(value[0]); 1152 if (click_id === "") { 1153 return null; 1154 } 1155 var timestamp = window.metrion.helpers.safe_parse_int(value[1]); 1156 if (timestamp < 0) { 1157 timestamp = 0; 1158 } 1159 // A timestamp written in seconds (an earlier build, or a hand-edited 1160 // cookie) is scaled up, otherwise it would look ancient next to a 1161 // millisecond one and always be the first evicted. 1162 if (timestamp > 0 && timestamp < manager.milliseconds_scale_threshold) { 1163 timestamp = timestamp * 1000; 1164 } 1165 return [click_id, timestamp]; 1166 } 1167 1168 // Legacy flat format {"gclid":"123"}: the click ID is still usable but its 1169 // age is unknown, so it is stamped 0 and is the first to be evicted. 1170 if (typeof value === "string" && value !== "") { 1171 return [value, 0]; 1172 } 1173 1174 return null; 1175 }, 1176 1177 // Stored click IDs as {param: [click_id, unix_milliseconds]}. A corrupted or 1178 // malformed cookie yields an empty object instead of throwing. 1179 read: function () { 1180 var manager = window.metrion.click_id_manager; 1181 var stored = {}; 1182 var raw_cookie_value = window.metrion.helpers.get_cookie_value( 1183 window.metrion.configuration.click_ids_cookie_name 1184 ); 1185 1186 if (raw_cookie_value === null) { 1187 return stored; 1188 } 1189 1190 var parsed;
1191 try { 1192 // decodeURIComponent also throws on a malformed percent sequence 1193 parsed = JSON.parse(decodeURIComponent(raw_cookie_value)); 1194 } catch (e) { 1195 window.metrion.helpers.log_debug( 1196 "Click id cookie could not be parsed, starting with an empty object", 1197 "log" 1198 ); 1199 return stored; 1200 } 1201 1202 if ( 1203 !parsed || 1204 typeof parsed !== "object" || 1205 Object.prototype.toString.apply(parsed) === "[object Array]" 1206 ) { 1207 return stored; 1208 } 1209 1210 for (var key in parsed) { 1211 if (!parsed.hasOwnProperty(key)) { 1212 continue; 1213 } 1214 var entry = manager.normalize_entry(parsed[key]); 1215 if (entry !== null) { 1216 stored[key] = entry; 1217 } 1218 } 1219 return stored; 1220 }, 1221 1222 // Overwrite only the parameters present in this URL, with the current 1223 // timestamp. Other touchpoints (a stored gbraid when a gclid comes in) are 1224 // left untouched. 1225 apply_detected: function (stored, detected) { 1226 // Same unit as event_unix_timestamp and the other event-level timestamps 1227 var now_milliseconds = Date.now(); 1228 for (var i = 0; i < detected.length; i++) { 1229 stored[detected[i].name] = [detected[i].value, now_milliseconds]; 1230 } 1231 return stored; 1232 }, 1233 1234 encode: function (stored) { 1235 return encodeURIComponent(JSON.stringify(stored)); 1236 }, 1237 1238 // FIFO: while over the key cap or the size budget, drop the oldest timestamp. 1239 // The last remaining entry is never dropped on size alone. 1240 enforce_limits: function (stored) { 1241 var manager = window.metrion.click_id_manager; 1242 var keys = []; 1243 for (var key in stored) { 1244 if (stored.hasOwnProperty(key)) { 1245 keys.push(key); 1246 } 1247 } 1248 1249 while (keys.length > 0) { 1250 var over_count = keys.length > manager.max_click_ids; 1251 var over_size = 1252 keys.length > 1 && 1253 manager.encode(stored).length > manager.max_cookie_value_bytes; 1254 if (!over_count && !over_size) { 1255 break; 1256 } 1257 1258 var oldest_key = keys[0]; 1259 for (var i = 1; i < keys.length; i++) { 1260 if (stored[keys[i]][1] < stored[oldest_key][1]) { 1261 oldest_key = keys[i]; 1262 } 1263 } 1264 1265 window.metrion.helpers.log_debug( 1266 "Click id limit reached, evicting oldest click id: " + oldest_key, 1267 "log" 1268 ); 1269 delete stored[oldest_key]; 1270 keys.splice(keys.indexOf(oldest_key), 1); 1271 } 1272 return stored; 1273 }, 1274 1275 // Persist when cookie placement is allowed, otherwise keep the encoded value 1276 // on the window so evaluate_consent_floodgate() can write it once consent 1277 // arrives. Same contract as before: an already encoded string. 1278 persist: function (stored) { 1279 var manager = window.metrion.click_id_manager; 1280 var encoded_value = manager.encode(stored); 1281 var cookie_lifetime = window.metrion.helpers.safe_parse_int( 1282 window.metrion.configuration.cookie_expiration_milliseconds 1283 ); 1284 if (cookie_lifetime <= 0) { 1285 cookie_lifetime = manager.default_cookie_lifetime_milliseconds; 1286 } 1287 1288 if ( 1289 window.metrion.configuration.floodgate_open || 1290 window.metrion.configuration 1291 .allow_cookie_placement_before_explicit_consent === "1" 1292 ) { 1293 window.metrion.helpers.log_debug("Updating the click-id cookie...", "log"); 1294 window.metrion.helpers.set_cookie( 1295 window.metrion.configuration.click_ids_cookie_name, 1296 encoded_value, 1297 cookie_lifetime, 1298 "/", 1299 window.metrion.helpers.get_cookie_domain(window.location.hostname) 1300 ); 1301 } else { 1302 window.metrion.helpers.log_debug( 1303 "No acces to storage/cookies so store click id in JS", 1304 "log" 1305 ); 1306 // Store in a temporary JS variable (non-persistent) 1307 window.metrion.click_ids = encoded_value; 1308 } 1309 return encoded_value; 1310 }, 1311 1312 // The click ID string for one parameter, or undefined when not stored. 1313 // Reads both the current [click_id, unix_milliseconds] format and the legacy flat 1314 // format, so destination scripts never have to know which one is in the 1315 // cookie. Never throws. 1316 get_click_id: function (param_name) {
1317 try { 1318 var entry = window.metrion.click_id_manager.read()[param_name]; 1319 if (!entry) { 1320 return undefined; 1321 } 1322 return entry[0]; 1323 } catch (e) { 1324 return undefined; 1325 } 1326 }, 1327 1328 // Read, merge, cap and store. Never throws: a failure here must not stop the 1329 // session and page_view events. 1330 store: function (detected) { 1331 var manager = window.metrion.click_id_manager; 1332 try { 1333 if (!detected || detected.length === 0) { 1334 return null; 1335 } 1336 var stored = manager.apply_detected(manager.read(), detected); 1337 manager.enforce_limits(stored); 1338 return manager.persist(stored); 1339 } catch (e) { 1340 window.metrion.helpers.log_debug("Click id storage failed: " + e, "log"); 1341 return null; 1342 } 1343 }, 1344 }, 1345 1346 // JS session definition logic 1347 session_event_logic: function () { 1348 window.metrion.helpers.log_debug("Start session logic function", "log"); 1349 1350 // Reload session exclusion 1351 var nav = []; 1352 if ( 1353 typeof performance !== "undefined" && 1354 typeof performance.getEntriesByType === "function" 1355 ) { 1356 nav = performance.getEntriesByType("navigation"); 1357 } 1358 if (nav.length > 0 && nav[0] && nav[0].type === "reload") { 1359 return; 1360 } 1361 1362 // Payment session exclusions 1363 var payment_referrer_exclusions = [ 1364 "anbamro.nl", 1365 "asnbank.nl", 1366 "pay.mollie.nl", 1367 "mollie.com", 1368 "ecommerce-gateway.mollie.com", 1369 "paypal.com", 1370 "klarna.com", 1371 "girogate.be", 1372 "snsbank.nl", 1373 "rabobank.nl", 1374 "knab.nl", 1375 "bunq.com", 1376 ".ing.nl", 1377 "regiobank.nl", 1378 "triodos.nl", 1379 "vanlanschot.nl", 1380 "moneyou.nl", 1381 "multisafepay.com", 1382 "agone.com", 1383 "pay.nl", 1384 "sips-atos.com", 1385 "curopayments.net", 1386 "ideal.nl", 1387 "adyen.com", 1388 "afterpay.nl", 1389 "tikkie.me", 1390 "buckaroo.nl", 1391 "sisow.nl", 1392 "targetpay.com", 1393 "paypro.nl", 1394 "icepay.nl", 1395 "omnikassa.rabobank.nl", 1396 "postnl.nl", 1397 "billink.nl", 1398 "spraypay.nl", 1399 "capayable.nl", 1400 "in3.nl", 1401 "klarna.nl", 1402 "vividmoney.nl", 1403 "revolut.com", 1404 "n26.com", 1405 "wise.com", 1406 "bancontact.com", 1407 "belfius.be", 1408 "cbc.be", 1409 "kbc.be", 1410 ".ing.be", 1411 "bnpparibasfortis.be", 1412 "keytradebank.be", 1413 "argenta.be", 1414 "fintro.be", 1415 "hellobank.be", 1416 "crelan.be", 1417 "axabank.be", 1418 "recordbank.be", 1419 "sofort.com", 1420 "stripe.com", 1421 "worldline.com", 1422 "eps-payment.eu", 1423 "ogone.com", 1424 "viva.com", 1425 "twikey.com", 1426 "hipay.com", 1427 "payconiq.com", 1428 "postfinance.be", 1429 "six-payment-services.com", 1430 ]; 1431 var regex_payment = new RegExp( 1432 payment_referrer_exclusions 1433 .map(function (domain) { 1434 return domain.replace(/\./g, "\\."); 1435 }) 1436 .join("|"), 1437 "i" 1438 ); 1439 1440 if (regex_payment.test(document.referrer)) { 1441 return; 1442 } 1443 1444 // Referrer session evaluation 1445 if (document.referrer !== "") { 1446 var referrerHost = new URL(document.referrer).hostname 1447 .split(".") 1448 .slice(-2) 1449 .join("."); 1450 var currentHost = location.hostname.split(".").slice(-2).join("."); 1451 if (referrerHost !== currentHost) { 1452 window.metrion.configuration.session_start_trigger = true; 1453 window.metrion.configuration.session_start_reasons.push("new referrer"); 1454 } 1455 } else { 1456 window.metrion.configuration.session_start_trigger = true; 1457 window.metrion.configuration.session_start_reasons.push("no referrer"); 1458 } 1459 1460 // UTM session evaluation 1461 if (window.location.search.indexOf("utm_") > -1) { 1462 window.metrion.configuration.session_start_trigger = true; 1463 window.metrion.configuration.session_start_reasons.push("utms detected"); 1464 } 1465 1466 // Advertising click tracker evaluation. The canonical parameter list lives in 1467 // config/advertising_params.json and reaches the front end through 1468 // metrion_api.advertising_params. 1469 var detected_advertising_params = 1470 window.metrion.click_id_manager.detect_in_url(); 1471 var detected_advertising_param_names = []; 1472 for ( 1473 var advertising_param_i = 0; 1474 advertising_param_i < detected_advertising_params.length; 1475 advertising_param_i++ 1476 ) { 1477 detected_advertising_param_names.push( 1478 detected_advertising_params[advertising_param_i].name 1479 ); 1480 } 1481 var known_advertising_param_present = 1482 detected_advertising_params.length > 0; 1483 window.metrion.helpers.log_debug( 1484 "Detected params: " + detected_advertising_param_names.join(","), 1485 "log" 1486 ); 1487 1488 // Add the context and click ids if they are available 1489 if ( 1490 known_advertising_param_present && 1491 window.location.search.indexOf("_gl") === -1 1492 ) { 1493 window.metrion.configuration.session_start_trigger = true; 1494 window.metrion.configuration.session_start_reasons.push( 1495 "advertising param detected" 1496 ); 1497 window.metrion.helpers.log_debug( 1498 "Advertising parameters detected", 1499 "log" 1500 ); 1501 1502 window.metrion.click_id_manager.store(detected_advertising_params); 1503 } 1504 1505 // Check if a duplicate session start is being triggered (when sessionStorage is available) 1506 var duplicate_session_start = false;
1507 if ( 1508 window.metrion.configuration.session_start_trigger && 1509 typeof sessionStorage !== "undefined" && 1510 window.metrion.configuration.floodgate_open 1511 ) { 1512 var current_session_info = JSON.stringify({ 1513 reasons: window.metrion.configuration.session_start_reasons, 1514 referrer: document.referrer, 1515 search: window.location.search, 1516 }); 1517 var previous_session_info = sessionStorage.getItem( 1518 window.metrion.configuration.session_info_storage_name 1519 ); 1520 // Duplicate session_start information detected. 1521 if (current_session_info === previous_session_info) { 1522 duplicate_session_start = true; 1523 } else { 1524 if ( 1525 window.metrion.configuration 1526 .allow_cookie_placement_before_explicit_consent === "1" 1527 ) { 1528 sessionStorage.setItem( 1529 window.metrion.configuration.session_info_storage_name, 1530 current_session_info 1531 ); 1532 } else { 1533 // Otherwise just add it to the window for later placement 1534 window.metrion.session_info = JSON.stringify({ 1535 reasons: window.metrion.configuration.session_start_reasons, 1536 referrer: document.referrer, 1537 search: window.location.search, 1538 }); 1539 } 1540 } 1541 } 1542 // Otherwise just add it to the window for later placement 1543 else { 1544 window.metrion.session_info = JSON.stringify({ 1545 reasons: window.metrion.configuration.session_start_reasons, 1546 referrer: document.referrer, 1547 search: window.location.search, 1548 }); 1549 } 1550 1551 // No session id exists 1552 if ( 1553 window.metrion.helpers.get_cookie_value( 1554 window.metrion.configuration.session_cookie_name 1555 ) === null 1556 ) { 1557 window.metrion.configuration.session_start_trigger = true; 1558 window.metrion.configuration.session_start_reasons.push("no session id"); 1559 } 1560 1561 // Metrion default session evaluation 1562 if (!duplicate_session_start) { 1563 if ( 1564 window.metrion.configuration 1565 .allow_cookie_placement_before_explicit_consent === "1" && 1566 !window.metrion.configuration.floodgate_open && 1567 window.metrion.configuration.session_start_trigger 1568 ) { 1569 window.metrion.session_manager.create_session_cookie(); 1570 } else if ( 1571 window.metrion.configuration.floodgate_open && 1572 window.metrion.configuration.session_start_trigger 1573 ) { 1574 window.metrion.session_manager.create_session_cookie(); 1575 } 1576 window.metrion.helpers.log_debug( 1577 "Eligible session_start: " + 1578 window.metrion.configuration.session_start_trigger.toString(), 1579 "log" 1580 ); 1581 window.metrion.helpers.log_debug( 1582 "Eligible session_start reasons: " + 1583 window.metrion.configuration.session_start_reasons.toString(), 1584 "log" 1585 ); 1586 1587 if (window.metrion.configuration.session_start_trigger) { 1588 window.metrion.configuration.session_start_trigger = false; 1589 window.metrion.send_event( 1590 "session_start", 1591 { 1592 reason: 1593 window.metrion.configuration.session_start_reasons.toString(), 1594 trigger_type: "front-end", 1595 }, 1596 {} 1597 ); 1598 } 1599 } 1600 // Cover the cookie if the session cookie is gone in a duplicate session state 1601 else if ( 1602 duplicate_session_start && 1603 window.metrion.configuration.session_start_trigger && 1604 window.metrion.helpers.get_cookie_value( 1605 window.metrion.configuration.session_cookie_name 1606 ) === null && 1607 window.metrion.configuration.floodgate_open 1608 ) { 1609 window.metrion.session_manager.create_session_cookie(); 1610 } 1611 }, 1612 1613 // JS pageview event logic 1614 page_view_event_logic: function () { 1615 window.metrion.configuration.pageview_send = 1616 window.metrion.configuration.pageview_send || false; 1617 if (!window.metrion.configuration.pageview_send) { 1618 window.metrion.send_event("page_view", { 1619 trigger_type: "front-end", 1620 }); 1621 window.metrion.configuration.pageview_send = true; 1622 } 1623 }, 1624}; 1625 1626window.metrion.configuration.cookie_domain = window.metrion.helpers.get_cookie_domain(window.location.hostname); 1627; 1628 1629// Cookiebot 1630// This file can be evaluated more than once on a single page (it is part of the 1631// core bundle and is also served standalone by the /cmp fallback endpoint), so 1632// state that has to survive a re-evaluation is only initialised when undefined. 1633if (typeof window.metrion.consent_manager.initialized === "undefined") { 1634 window.metrion.consent_manager.initialized = false;
1635} 1636if (typeof window.metrion.consent_manager.listeners_initialized === "undefined") { 1637 window.metrion.consent_manager.listeners_initialized = false; 1638} 1639// Registry of the window listeners this file attached. cmp_init() runs on every 1640// initial_consent_enforcement() call, so without it every consent update would 1641// attach yet another copy of the same listener. 1642if (typeof window.metrion.consent_manager.cookiebot_listeners_attached === "undefined") { 1643 window.metrion.consent_manager.cookiebot_listeners_attached = {}; 1644} 1645// Raw CookieConsent value that was last mirrored into the Metrion consent cookie. 1646// Used to tell Cookiebot's on-load re-fire of CookiebotOnAccept/OnDecline apart 1647// from a real consent change on the same page. 1648if (typeof window.metrion.consent_manager.cookiebot_last_synced_consent === "undefined") { 1649 window.metrion.consent_manager.cookiebot_last_synced_consent = null; 1650} 1651window.metrion.consent_manager.cmp_version = "cookiebot-legacy"; 1652window.metrion.consent_manager.cmp_js_in_bundle = "cookiebot"; 1653 1654// Storage access can throw (Safari private mode, blocked storage, sandboxed iframes). 1655// Treat a throwing or missing storage the same as a missing key. 1656window.metrion.consent_manager.cookiebot_storage_get = function(storage_name, key) { 1657 try { 1658 var storage = window[storage_name]; 1659 if (typeof storage === "undefined" || storage === null) { 1660 return null; 1661 } 1662 return storage.getItem(key); 1663 } catch (e) { 1664 return null; 1665 } 1666}; 1667 1668// Attach a listener at most once per registry key, regardless of how often cmp_init() runs 1669window.metrion.consent_manager.cookiebot_add_listener_once = function(context, event_name, handler, registry_key) { 1670 var key = registry_key || event_name; 1671 var registry = window.metrion.consent_manager.cookiebot_listeners_attached; 1672 if (registry[key] === true) { 1673 return false; 1674 } 1675 registry[key] = true; 1676 window.metrion.helpers.log_debug(("Add listener for consent update: " + event_name), "log"); 1677 context.addEventListener(event_name, handler); 1678 return true; 1679}; 1680 1681// Usercentrics documents "EXPLICIT"/"IMPLICIT" for the dataLayer and uses lower case in 1682// several examples, so compare case-insensitively. 1683window.metrion.consent_manager.cookiebot_is_consent_type = function(type_value, expected) { 1684 return typeof type_value === "string" && type_value.toLowerCase() === expected; 1685}; 1686 1687// Marketing consent ("1"/"0") from a Usercentrics consent payload, or null when the 1688// payload carries no category information. Handles the shapes Usercentrics emits: 1689// - v2 dataLayer / window event: ucCategory.marketing = true|false|null 1690// - v3 UC_CONSENT: categories.marketing.state = "ALL_ACCEPTED"|..., 1691// categories.marketing = true|false, consent.status 1692window.metrion.consent_manager.cookiebot_uc_marketing_consent = function(payload) { 1693 if (!payload || typeof payload !== "object") { 1694 return null; 1695 } 1696 var marketing; 1697 if (payload.categories && typeof payload.categories === "object") { 1698 marketing = payload.categories.marketing; 1699 if (Array.isArray(payload.categories)) { 1700 marketing = undefined; 1701 for (var category_i = 0; category_i < payload.categories.length; category_i++) { 1702 var category = payload.categories[category_i]; 1703 if (category && (category.slug === "marketing" || category.categorySlug === "marketing")) { 1704 marketing = category; 1705 break; 1706 } 1707 } 1708 } 1709 if (marketing && typeof marketing === "object") { 1710 if (typeof marketing.state === "string") { 1711 return marketing.state === "ALL_ACCEPTED" ? "1" : "0"; 1712 } 1713 if (typeof marketing.consent === "boolean") { 1714 return marketing.consent ? "1" : "0"; 1715 } 1716 } 1717 if (typeof marketing === "boolean") { 1718 return marketing ? "1" : "0"; 1719 } 1720 } 1721 if (payload.ucCategory && typeof payload.ucCategory === "object" && typeof payload.ucCategory.marketing === "boolean") { 1722 return payload.ucCategory.marketing ? "1" : "0"; 1723 } 1724 if (payload.consent && typeof payload.consent.status === "string") { 1725 if (payload.consent.status === "ALL_ACCEPTED") { 1726 return "1"; 1727 } 1728 if (payload.consent.status === "ALL_DENIED") { 1729 return "0"; 1730 } 1731 } 1732 return null; 1733}; 1734 1735// Write the Metrion consent cookie. Marketing and PII share one flag, user id and session id share one flag. 1736window.metrion.consent_manager.cookiebot_write_consent_cookie = function(allow_marketing, allow_identifiers) { 1737 var encoded_cookie_value = encodeURIComponent(JSON.stringify({ 1738 "allow_marketing": allow_marketing, 1739 "allow_pii": allow_marketing, 1740 "allow_uid": allow_identifiers, 1741 "allow_sid": allow_identifiers, 1742 "b": window.metrion.configuration.blocking_detected, 1743 "unix": Date.now() 1744 })); 1745 window.metrion.helpers.log_debug(encoded_cookie_value, "log"); 1746 window.metrion.helpers.set_consent_cookie(encoded_cookie_value); 1747 window.metrion.helpers.log_debug("Metrion consent cookie set", "log"); 1748 return encoded_cookie_value; 1749}; 1750 1751// Explicit consent is known: open the floodgate and mark the CMP update as handled 1752window.metrion.consent_manager.cookiebot_open_floodgate = function(allow_marketing) { 1753 // Set the marketing metrion cookie value for dynamic loading of destinations 1754 window.metrion.configuration.cmp_allow_marketing = allow_marketing === "1"; 1755 window.metrion.configuration.floodgate_open = true; 1756 window.metrion.helpers.log_debug("Metrion floodgate_open set to true.", "log"); 1757 window.metrion.configuration.cmp_update_handled = true; 1758}; 1759 1760// Whether the current Metrion consent cookie already allows marketing 1761window.metrion.consent_manager.cookiebot_current_allow_marketing = function() { 1762 var metrion_consent_cookie = window.metrion.helpers.get_cookie_value(window.metrion.configuration.consent_cookie_name); 1763 if (metrion_consent_cookie === null) { 1764 return false;
1765 } 1766 try { 1767 return JSON.parse(decodeURIComponent(metrion_consent_cookie)).allow_marketing === "1"; 1768 } catch (e) { 1769 return false; 1770 } 1771}; 1772 1773// Mirror a Usercentrics consent change (UC_CONSENT / uc-event payload) into Metrion. 1774// Shared by the UC v3 UC_CONSENT listener and the UC v2 window event listener. 1775window.metrion.consent_manager.cookiebot_apply_uc_consent = function(payload) { 1776 // CMP update is already handled, if so, extend the timeout 1777 if (window.metrion.configuration.cmp_update_handled) { 1778 window.metrion.consent_manager.timeout_consent_listener_updates(); 1779 } 1780 1781 var uc_allow_marketing = window.metrion.consent_manager.cookiebot_uc_marketing_consent(payload) === "1" ? "1" : "0"; 1782 window.metrion.helpers.log_debug((uc_allow_marketing + "-" + uc_allow_marketing), "log"); 1783 1784 // Remember the state before overwriting the cookie so destinations can be loaded when marketing turns on 1785 var previous_allow_marketing = window.metrion.consent_manager.cookiebot_current_allow_marketing(); 1786 1787 // Sync/mirror the Metrion cookie based on the UC consent change 1788 window.metrion.consent_manager.cookiebot_write_consent_cookie(uc_allow_marketing, "1"); 1789 1790 // Update Metrion consent settings after setting the consent cookie 1791 window.metrion.consent_manager.cookiebot_open_floodgate(uc_allow_marketing); 1792 1793 // Trigger initial consent enforcement again (releases floodgated events) 1794 window.metrion.consent_manager.initial_consent_enforcement(); 1795 1796 // initial_consent_enforcement() reads the consent cookie before cmp_init(
1796) and only loads 1797 // destinations when that cookie still said "0". The cookie was already updated above, so 1798 // handle the "marketing just got allowed" transition here. 1799 if (uc_allow_marketing === "1" && !previous_allow_marketing) { 1800 window.metrion.consent_manager.load_destination_js_dynamically(); 1801 } 1802}; 1803 1804window.metrion.consent_manager.cmp_init = function(){ 1805 // Set basic detection variables 1806 var cookiebot_version = "cookiebot-legacy"; 1807 var cookiebot_initial_allow_marketing = "0"; 1808 var cookiebot_explicit_consent = false; 1809 1810 // Marketing consent from the Usercentrics legacy UC_UI API: "1" when any marketing service is consented 1811 function usercentrics_legacy_marketing_consent(){ 1812 var allow_marketing = "0"; 1813 if(typeof(window.UC_UI) === "undefined" || typeof(window.UC_UI.getServicesBaseInfo) !== "function"){ 1814 return allow_marketing; 1815 } 1816 var accepted_services = window.UC_UI.getServicesBaseInfo(); 1817 // Check if the marketing caterogy is accepted 1818 if(Array.isArray(accepted_services) && accepted_services.length > 0){ 1819 for(var service_i = 0; service_i < accepted_services.length; service_i++){ 1820 var service = accepted_services[service_i]; 1821 if(service && service.categorySlug === "marketing" && service.consent && service.consent.status === true){ 1822 allow_marketing = "1"; 1823 break; 1824 } 1825 } 1826 } 1827 return allow_marketing; 1828 } 1829 1830 // 1. Usercentrics legacy edition 1831 function usercentrics_legacy_init(){ 1832 if(window.metrion.consent_manager.cookiebot_storage_get("sessionStorage", "uc_user_country") !== null && 1833 window.metrion.consent_manager.cookiebot_storage_get("localStorage", "uc_ui_version") !== null){ 1834 cookiebot_version = "uc-version-legacy"; 1835 window.metrion.consent_manager.cmp_version = cookiebot_version; 1836 1837 // Check Usercentrics legacy window object and if there has been an user interaction 1838 if(typeof(window.UC_UI) !== "undefined" && window.metrion.consent_manager.cookiebot_storage_get("localStorage", "uc_user_interaction") === "true"){ 1839 var allow_marketing_initial = usercentrics_legacy_marketing_consent(); 1840 // Set Metrion consent cookie based on Usercentrics legacy 1841 window.metrion.consent_manager.cookiebot_write_consent_cookie(allow_marketing_initial, "1"); 1842 // Explicit consent exists: open the floodgate, same as the Cookiebot legacy branch does 1843 window.metrion.consent_manager.cookiebot_open_floodgate(allow_marketing_initial); 1844 } 1845 } 1846 // Set initial listeners 1847 window.metrion.consent_manager.cookiebot_add_listener_once(window, 'UC_UI_INITIALIZED', function(event) { 1848 window.metrion.helpers.log_debug("Cookiebot version listener: Usercentrics legacy edition"); 1849 if(typeof(window.UC_UI) !== "undefined"){ 1850 var allow_marketing_initial = usercentrics_legacy_marketing_consent(); 1851 // Set Metrion consent cookie based on Usercentrics legacy 1852 window.metrion.consent_manager.cookiebot_write_consent_cookie(allow_marketing_initial, "1"); 1853 } 1854 }, "UC_UI_INITIALIZED:legacy"); 1855 return "uc-legacy"; 1856 } 1857 1858 // 2. Usercentrics modern edition 1859 function usercentrics_modern_init(){ 1860 if(window.metrion.consent_manager.cookiebot_storage_get("localStorage", "ucData") !== null){ 1861 cookiebot_version = "uc-version-modern"; 1862 window.metrion.consent_manager.cmp_version = cookiebot_version; 1863 1864 // UC dataLayer 1865 if (window.dataLayer && 1866 Object.prototype.toString.apply(window.dataLayer) === '[object Array]') { 1867 for (var i = 0; i < window.dataLayer.length; i++) { 1868 var item = window.dataLayer[i]; 1869 if (item && 1870 typeof item === 'object' && 1871 item.hasOwnProperty('ucCategory') && 1872 item.hasOwnProperty('type') && 1873 item.hasOwnProperty('action') 1874 ) { 1875 // UC has posted data to the dataLayer 1876 window.metrion.helpers.log_debug("CMP Cookiebot UC detected in dataLayer", "log"); 1877 // UC has explicit consent 1878 if(window.metrion.consent_manager.cookiebot_is_consent_type(item['type'], 'explicit')){ 1879 cookiebot_explicit_consent = true; 1880 } 1881 // Set initial marketing consent (null means a mixed state, keep the previous value) 1882 if(item['ucCategory'] && item['ucCategory']["marketing"] === true){ 1883 cookiebot_initial_allow_marketing = "1"; 1884 } 1885 if(item['ucCategory'] && item['ucCategory']["marketing"] === false){ 1886 cookiebot_initial_allow_marketing = "0"; 1887 } 1888 } 1889 } 1890 } else { 1891 window.metrion.helpers.log_debug("CMP Cookiebot UC datalayer check fails, no UC dataLayer", "log"); 1892 } 1893 // UC explicit consent was given 1894 if(cookiebot_explicit_consent === true && window.metrion.consent_manager.initialized === false){ 1895 window.metrion.helpers.log_debug("CMP Cookiebot UC explicit consent found in dataLayer", "log"); 1896 //Prevent this script from running everytime 1897 window.metrion.consent_manager.initialized = true; 1898 // Sync/mirror the Metrion cookie based on the UC consent state 1899 window.metrion.consent_manager.cookiebot_write_consent_cookie(cookiebot_initial_allow_marketing, "1"); 1900 // Explicit consent exists: open the floodgate, same as the Cookiebot legacy branch does 1901 window.metrion.consent_manager.cookiebot_open_floodgate(cookiebot_initial_allow_marketing); 1902 } 1903 1904 // Modern usercentrics: the (configurable) UC window event mirrors the dataLayer object 1905 window.metrion.consent_manager.cookiebot_add_listener_once(window, 'UC_UI_INITIALIZED', function(event) { 1906 window.metrion.consent_manager.cookiebot_add_listener_once(window, 'uc-event', function (uc_event) { 1907 var uc_detail = uc_event ? uc_event.detail : null; 1908 if(!uc_detail || uc_detail.event !== 'consent_status'){ 1909 return; 1910 }
1911 window.metrion.consent_manager.cmp_version = "uc-version-modern"; 1912 // No explicit consent given yet 1913 if (window.metrion.consent_manager.cookiebot_is_consent_type(uc_detail.type, 'implicit')) { 1914 window.metrion.helpers.log_debug("CMP Cookiebot UC first visit with implicit consent state", "log"); 1915 return; 1916 } 1917 // Explicit consent given > Update Metrion cookie 1918 if (window.metrion.consent_manager.cookiebot_is_consent_type(uc_detail.type, 'explicit')) { 1919 window.metrion.helpers.log_debug("CMP Cookiebot UC has explicit consent", "log"); 1920 window.metrion.consent_manager.cookiebot_apply_uc_consent(uc_detail); 1921 } 1922 }, "uc-event"); 1923 }, "UC_UI_INITIALIZED:modern"); 1924 } 1925 return "uc-version-modern"; 1926 } 1927 1928 // 3. LEGACY COOKIEBOT 1929 function cookiebot_legacy_init(){ 1930 if(window.metrion.consent_manager.cmp_version === "cookiebot-legacy"){ 1931 // If the CookieConsent cookie is available (!== null), then sync this cookie with Metrion consent cookie 1932 var raw_cookiebot_cookie = window.metrion.helpers.get_cookie_value("CookieConsent"); 1933 if (raw_cookiebot_cookie !== null) { 1934 // Read Cookiebot's consent cookie 1935 var cookiebot_cookie = decodeURIComponent(raw_cookiebot_cookie); 1936 var cookiebot_allow_marketing = Number(cookiebot_cookie.indexOf("marketing:true") > -1).toString(); 1937 var cookiebot_allow_necessary = Number(cookiebot_cookie.indexOf("necessary:true") > -1).toString(); 1938 1939 // Set Metrion consent cookie 1940 window.metrion.consent_manager.cookiebot_write_consent_cookie(cookiebot_allow_marketing, cookiebot_allow_necessary); 1941 window.metrion.consent_manager.cookiebot_last_synced_consent = raw_cookiebot_cookie; 1942 1943 // Update Metrion consent settings after setting the consent cookie 1944 window.metrion.consent_manager.cookiebot_open_floodgate(cookiebot_allow_marketing); 1945 } 1946 } 1947 // Nonetheless of the settings, create listeners for CMP consent updates 1948 window.metrion.consent_manager.cmp_update_listener("CookiebotOnAccept", window); 1949 window.metrion.consent_manager.cmp_update_listener("CookiebotOnDecline", window); 1950 return "cookiebot-legacy"; 1951 } 1952 1953 // Run all init functions 1954 usercentrics_legacy_init(); 1955 usercentrics_modern_init(); 1956 cookiebot_legacy_init(); 1957 window.metrion.helpers.log_debug(("Set the consent update listeners for: " + window.metrion.configuration.cmp_selection), "log"); 1958 return "cmp init finished"; 1959}; 1960 1961// Function that adds event listeners for the chosen CMP 1962window.metrion.consent_manager.cmp_update_listener = function(cmp_update_trigger, context) { 1963 var trigger_uc_listeners = false; 1964 if(window.metrion.consent_manager.listeners_initialized === false){ 1965 window.metrion.consent_manager.listeners_initialized = true; 1966 trigger_uc_listeners = true; 1967 } 1968 1969 var cookiebot_version = "cookiebot-legacy"; 1970 // Only check for the ucData element for cmp update listeners 1971 if(window.metrion.consent_manager.cookiebot_storage_get("localStorage", "ucData") !== null){ 1972 cookiebot_version = "uc-version-modern"; 1973 } 1974 1975 // Legacy cookiebot listener 1976 if(cookiebot_version === "cookiebot-legacy"){ 1977 // FOR LEGACY COOIEBOT 1978 window.metrion.consent_manager.cookiebot_add_listener_once(context, cmp_update_trigger, function () { 1979 // Check current status of consent cookie 1980 var metrion_consent_cookie = window.metrion.helpers.get_cookie_value(window.metrion.configuration.consent_cookie_name); 1981 window.metrion.helpers.log_debug(("Event listener " + cmp_update_trigger + " triggered with metrion consent cookie value: " + (metrion_consent_cookie || "null")), "log"); 1982 1983 // Cookiebot fires CookiebotOnAccept/OnDecline on every page load with existing consent. 1984 // Skip that re-fire when the update was already handled AND the C
1984ookiebot cookie is 1985 // unchanged since it was mirrored. A changed cookie is a real (re)decision, including a 1986 // withdrawal on the same page, and is always processed. 1987 var current_cookiebot_cookie = window.metrion.helpers.get_cookie_value("CookieConsent"); 1988 if (window.metrion.configuration.cmp_update_handled && 1989 current_cookiebot_cookie === window.metrion.consent_manager.cookiebot_last_synced_consent) { 1990 // Extend timeout 1991 window.metrion.consent_manager.timeout_consent_listener_updates(); 1992 return; 1993 } 1994 1995 // update Metrion consent cookie 1996 window.metrion.helpers.log_debug("Trigger the initial consent enforcement after CMP update event listener", "log"); 1997 window.metrion.consent_manager.initial_consent_enforcement(); 1998 }); 1999 } 2000 2001 if(trigger_uc_listeners){ 2002 // The UC consent change event listener 2003 window.metrion.consent_manager.cookiebot_add_listener_once(window, 'UC_CONSENT', function (event) { 2004 window.metrion.helpers.log_debug("Cookiebot UC consent changed", "log"); 2005 window.metrion.helpers.log_debug(event, "log"); 2006 window.metrion.consent_manager.cookiebot_apply_uc_consent(event ? event.detail : null); 2007 }); 2008 } 2009}; 2010 2011// Check CMP consent status 2012window.metrion.consent_manager.has_explicit_cmp_consent = function(){ 2013 var has_explicit_consent = false; 2014 2015 // UC Legacy 2016 if(window.metrion.consent_manager.cookiebot_storage_get("localStorage", "uc_user_interaction") === "true"){ 2017 has_explicit_consent = true; 2018 } 2019 2020 // UC modern: an explicit consent_status entry in the dataLayer 2021 if(!has_explicit_consent && window.dataLayer && Object.prototype.toString.apply(window.dataLayer) === '[object Array]'){ 2022 for (var i = 0; i < window.dataLayer.length; i++) { 2023 var item = window.dataLayer[i]; 2024 if (item && typeof item === 'object' && item.event === "consent_status" && 2025 window.metrion.consent_manager.cookiebot_is_consent_type(item.type, 'explicit')) { 2026 has_explicit_consent = true; 2027 break; 2028 } 2029 } 2030 } 2031 2032 // LEGACY COOKIEBOT 2033 if(!has_explicit_consent){ 2034 has_explicit_consent = window.metrion.helpers.get_cookie_value("CookieConsent") !== null; 2035 } 2036 return has_explicit_consent; 2037}; 2038 2039 2040// This block should run ONLY ONCE on page load (e.g., in your main Metrion script initialization). 2041// It is wrapped in an IIFE so every wrapper keeps its own reference to the push 2042// it replaced. With a global "var original_push" a second evaluation of this 2043// file rebinds that single global to the first wrapper, which then calls itself 2044// endlessly (RangeError: Maximum call stack size exceeded). 2045(function () { 2046 window.dataLayer = window.dataLayer || []; 2047 2048 // Already patched by an earlier evaluation of this file: do nothing. 2049 if (typeof window.dataLayer.push === "function" && window.dataLayer.push.metrion_patched === true) { 2050 return; 2051 } 2052 2053 var data_layer = window.dataLayer; 2054 var original_push = data_layer.push; 2055 if (typeof original_push !== "function") { 2056 original_push = Array.prototype.push; 2057 } 2058 2059 var metrion_push = function () { 2060 // CRITICAL: Call the original push function exactly once, on the array 2061 // (an unbound call would otherwise push onto window). Push FIRST so the 2062 // dataLayer scan in cmp_init() can see the entry that triggered this. 2063 var target = (this && this !== window) ? this : data_layer; 2064 var result = original_push.apply(target, arguments); 2065 2066 // Check if the push call contains a relevant Usercentrics event 2067 try { 2068 if(arguments.length > 0 && typeof(arguments[0]) === "object" && arguments[0] !== null && typeof(arguments[0].event) !== "undefined"){ 2069 var event_object = arguments[0]; 2070 // Only proceed if it is an explicit consent update event 2071 if(event_object.event === "consent_status" && 2072 window.metrion && window.metrion.consent_manager && 2073 window.metrion.consent_manager.cookiebot_is_consent_type(event_object.type, 'explicit')){ 2074 // --- Custom Logic Triggered ONLY HERE --- 2075 // Trigger initial consent enforcement again 2076 if (typeof window.metrion.consent_manager.initial_consent_enforcement === "function") { 2077 window.metrion.consent_manager.initial_consent_enforcement(); 2078 } 2079 } 2080 } 2081 } catch (e) { 2082 // Never let Metrion logic break another script's dataLayer.push call 2083 } 2084 return result; 2085 }; 2086 2087 metrion_push.metrion_patched = true; 2088 window.dataLayer.push = metrion_push; 2089})(); 2090 2091; 2092 2093(function() { 2094 if (window.metrion && window.metrion_api) { 2095 // Check if there is a mismatch in CMP selection vs the code in the bundle 2096 if (window.metrion.configuration.cmp_selection !== window.metrion.consent_manager.cmp_js_in_bundle) { 2097 window.metrion.helpers.log_debug("CMP selection mismatch: " + window.metrion.configuration.cmp_selection + " vs " + window.metrion.consent_manager.cmp_js_in_bundle, "log"); 2098 2099 2100 // Get Metrion metadata 2101 var api_url = metrion_api.api_url.replace(/\/?$/, "/"); 2102 var cmp_bundle_path = "cmp"; 2103 var ver = metrion_api.bundle_version ? "?ver=" + metrion_api.bundle_version : ""; 2104 var cmp_bundle_url = api_url + cmp_bundle_path + ver; 2105 2106 // Async load the fallback CMP JS 2107 var s = document.createElement('script'); 2108 s.type = 'text/javascript'; 2109 s.async = true; 2110 s.defer = true; 2111 s.src = cmp_bundle_url; 2112 var x = document.getElementsByTagName('script')[0]; 2113 2114 // Insert the new script before the existing one 2115 if (x && x.parentNode) { 2116 x.parentNode.insertBefore(s, x); 2117 } else { 2118 document.head.appendChild(s); // Safe fallback just in case 2119 } 2120 2121 2122 // Send status event for missing mismatch JS with settings from Metrion sync platform 2123 window.metrion.send_event("status_event", { 2124 "message": "CMP mismatch fallback required", 2125 "id": "fallback_cmp_mismatch_js" 2126 }, {}); 2127 } 2128 } 2129})(); 2130 2131; 2132
2133// Prevent overwriting of objects and preserve existing properties 2134window.metrion = window.metrion || {}; 2135// Set the flag globally on the window to retain its state across page reloads 2136window.metrion.woocommerce_purchase_logic_triggered = false; 2137 2138// Key conversion front-end monitoring 2139window.metrion.purchase_conversion_network_monitoring = function(target_input, timeout_ms) { 2140 var duration = timeout_ms || 10000; 2141 2142 // Normalize input so it's ALWAYS an array of objects 2143 var raw_targets = Array.isArray(target_input) ? target_input : [target_input]; 2144 var new_targets = []; 2145 2146 for (var k = 0; k < raw_targets.length; k++) { 2147 var item = raw_targets[k]; 2148 if (typeof item === 'string') { 2149 new_targets.push({ target: item }); 2150 } else if (item && item.target) { 2151 new_targets.push(item); 2152 } 2153 } 2154 2155 if (new_targets.length === 0) return; 2156 2157 // --- Domain Filtering Helpers --- 2158 var get_root_domain = function(hostname) { 2159 var parts = hostname.split('.'); 2160 if (parts.length <= 2) return hostname; 2161 return parts.slice(-2).join('.'); 2162 }; 2163 2164 var current_root = get_root_domain(window.location.hostname); 2165 2166 var is_external = function(url) { 2167 if (!url) return false; 2168 if (url.indexOf('//') === -1 && url.indexOf('http') !== 0) return false; 2169 2170 try { 2171 var url_host = new URL(url, window.location.origin).hostname; 2172 return get_root_domain(url_host) !== current_root; 2173 } catch (e) { 2174 return url.indexOf(current_root) === -1; 2175 } 2176 }; 2177 // ------------------------------------- 2178 2179 // 1. If already active, safely add NEW target objects 2180 if (window.__network_monitor) { 2181 for (var i = 0; i < new_targets.length; i++) { 2182 var exists = false; 2183 for (var j = 0; j < window.__network_monitor.active_targets.length; j++) { 2184 if (window.__network_monitor.active_targets[j].target === new_targets[i].target) { 2185 exists = true; 2186 break; 2187 } 2188 } 2189 if (!exists) { 2190 window.__network_monitor.active_targets.push(new_targets[i]); 2191 } 2192 } 2193 window.__network_monitor.reset_timer(duration); 2194 window.metrion.helpers.log_debug("[NetworkListener] Added targets to active session."); 2195 return; 2196 } 2197 2198 // 2. Setup the Monitor State 2199 var originals = { 2200 fetch: window.fetch, 2201 xhr_open: XMLHttpRequest.prototype.open, 2202 xhr_send: XMLHttpRequest.prototype.send, 2203 beacon: navigator.sendBeacon 2204 }; 2205 2206 window.__network_monitor = { 2207 active_targets: new_targets, 2208 timer: null, 2209 reset_timer: function(ms) { 2210 if (this.timer) clearTimeout(this.timer); 2211 this.timer = setTimeout(function() { 2212 window.metrion.helpers.log_debug("[Network listener] Timeout reached."); 2213 window.__network_monitor.deactivate(); 2214 }, ms); 2215 }, 2216 deactivate: function() { 2217 if (this.timer) clearTimeout(this.timer); 2218 if (originals.fetch) window.fetch = originals.fetch; 2219 XMLHttpRequest.prototype.open = originals.xhr_open; 2220 XMLHttpRequest.prototype.send = originals.xhr_send; 2221 if (originals.beacon) navigator.sendBeacon = originals.beacon; 2222 2223 delete window.__network_monitor; 2224 window.metrion.helpers.log_debug("[Monitor] Deactivated and chain restored."); 2225 } 2226 }; 2227 2228 var check_match = function(data, url_context) { 2229 var state = window.__network_monitor; 2230 if (!data || !state) return; 2231 2232 if (!is_external(url_context)) return; 2233 2234 var content = (typeof data === 'string') ? data : JSON.stringify(data); 2235 var targets = state.active_targets; 2236 2237 for (var i = targets.length - 1; i >= 0; i--) { 2238 var target_obj = targets[i]; 2239 var search_str = target_obj.target; 2240 2241 if (content.indexOf(search_str) !== -1) { 2242 window.metrion.helpers.log_debug("[Network listener] MATCH: " + search_str + (url_context ? " | URL: " + url_context : "")); 2243 2244 target_obj.matched_url = url_context || "Unknown URL"; 2245 2246 window.metrion.send_event("purchase_network_request_found", target_obj); 2247 2248 targets.splice(i, 1); 2249 if (targets.length === 0) state.deactivate(); 2250 } 2251 } 2252 }; 2253 2254 // --- Hijack Logic (Only happens ONCE) --- 2255 if (window.fetch) { 2256 window.fetch = function(input, init) { 2257 var url = (typeof input === 'string') ? input : input.url; 2258 check_match(url, url); 2259 if (init) check_match(init.body, url); 2260 return originals.fetch.apply(this, arguments); 2261 }; 2262 } 2263 2264 XMLHttpRequest.prototype.open = function(method, url) { 2265 this.__metrion_url = url; 2266 check_match(url, url); 2267 return originals.xhr_open.apply(this, arguments); 2268 }; 2269 2270 XMLHttpRequest.prototype.send = function(body) { 2271 check_match(body, this.__metrion_url); 2272 return originals.xhr_send.apply(this, arguments); 2273 }; 2274 2275 if (navigator.sendBeacon) { 2276 navigator.sendBeacon = function(url, data) { 2277 check_match(url, url); 2278 check_match(data, url); 2279 return originals.beacon.apply(this, arguments); 2280 }; 2281 } 2282 2283 var target_names = []; 2284 for (var t = 0; t < new_targets.length; t++) target_names.push(new_targets[t].target); 2285 2286 window.__network_monitor.reset_timer(duration); 2287 window.metrion.helpers.log_debug("[Monitor] Started. Watching EXTERNAL requests for: " + target_names.join(", ")); 2288}; 2289 2290// Woocommerce Purchase event logic 2291window.metrion.woocommerce_purchase_logic = function (){ 2292 2293 // Wait to check if jQuery is ready 2294 // Function to check if metrion_purchase_data is available 2295 function check_purchase_data() { 2296 // Early exit if no purchase data exists 2297 if (typeof window.metrion_purchase_data === "undefined") { 2298 return; 2299 } 2300 2301 // Purchase 2302 // Ensure purchase_data is available 2303 if ( 2304 typeof window.metrion_purchase_data !== "undefined" && 2305 Array.isArray(window.metrion_purchase_data.items) && 2306 window.metrion_purchase_data.items.length > 0 2307 ) { 2308 var purchase_event_body = { 2309 "event": "purchase", 2310 "trigger_type": "front-end", 2311 "order_id": window.metrion_purchase_data.order_id, 2312 "order_total": window.metrion_purchase_data.order_total, 2313 "order_total_tax": window.metrion_purchase_data.order_total_tax, 2314 "order_total_shipping": window.metrion_purchase_data.order_total_shipping, 2315 "currency": window.metrion_purchase_data.currency, 2316 "items": window.metrion_purchase_data.items, 2317 "coupons": window.metrion_purchase_data.coupons 2318 } 2319 2320 // Example: Send purchase data to an analytics system 2321 window.metrion.send_event("purchase", purchase_event_body); 2322 window.metrion.woocommerce_purchase_logic_triggered = true; 2323 } else { 2324 window.metrion.helpers.log_debug("Purchase data is not available.", "log"); 2325 } 2326 } 2327 2328 // Wait for DOMContentLoaded first 2329 if (document.readyState === "loading") { 2330 document.addEventListener("DOMContentLoaded", function () { 2331 if(!window.metrion.woocommerce_purchase_logic_triggered){ 2332 check_purchase_data(); // Call the function once DOM is ready 2333 } 2334 }); 2335 } else { 2336 if(!window.metrion.woocommerce_purchase_logic_triggered){ 2337 // If DOM is already loaded, just run the function immediately 2338 check_purchase_data(); 2339 } 2340 } 2341 // Fallback: In case DOMContentLoaded isn't triggered (very rare case), use window.onload 2342 window.addEventListener('load', function () { 2343 if (!window.metrion.woocommerce_purchase_logic_triggered) { 2344 check_purchase_data(); 2345 } 2346 }); 2347 2348 // Final trigger method is after jQuery is ready 2349 window.metrion.helpers.wait_for_jquery(function(){ 2350 if(!window.metrion.woocommerce_purchase_logic_triggered){ 2351 check_purchase_data(); 2352 } 2353 }); 2354 2355}; 2356 2357// Call the function 2358window.metrion.woocommerce_purchase_logic(); 2359; 2360
2361// Prevent overwriting of objects and preserve existing properties 2362window.metrion = window.metrion || {}; 2363 2364// JS Woocommerce other event logic 2365window.metrion.woocommerce_event_logic = function () { 2366 // Wait to check if jQuery is ready 2367 window.metrion.helpers.wait_for_jquery(function(){ 2368 // jQuery is required for Woocommerce 2369 jQuery(function ($) { 2370 // Add-to-cart listener 2371 $("body").on("click", ".add_to_cart_button, .single_add_to_cart_button", function (event) { 2372 var google_analytics_enabled = window.metrion.google_analytics !== undefined; 2373 var event_unix = Date.now(); 2374 var engagement_time_msec = 500; 2375 if(google_analytics_enabled){ 2376 engagement_time_msec = (event_unix - window.metrion.google_analytics.latest_engagement_unix); 2377 } 2378 2379 try { 2380 var product_id, product_name, product_brand, product_url,product_price, product_regular_price, product_price_incl_vat, product_price_excl_vat, product_variation_id, product_ean, product_gtin, product_sku, category_1, category_2, category_3, category_4, quantity = 1; 2381 var currency = $(this).data("currency") || "EUR"; // Default currency 2382 2383 // Determine the source of the metrion_item_data span 2384 var product_data_span; 2385 2386 if ($(this).hasClass("single_add_to_cart_button")) { 2387 // For single product pages, get the span from the footer 2388 product_data_span = $("#metrion_item_data_single").find(".metrion_item_data_single"); 2389 } else { 2390 // For product loops, get the span from the closest product container 2391 product_data_span = $(this).closest(".product").find(".metrion_item_data_loop"); 2392 } 2393 2394 // Ensure the span exists before proceeding 2395 if (!product_data_span.length || product_data_span.length <= 0) { 2396 //console.error("No metrion_item_data span found for this product."); 2397 window.metrion.helpers.log_debug("No metrion_item_data span found for this product.", "log"); 2398 return; 2399 } 2400 2401 // Extract data correctly 2402 product_id = product_data_span.attr("data-item_id"); 2403 product_name = product_data_span.attr("data-item_name"); 2404 product_price = product_data_span.attr("data-item_price"); 2405 product_price_incl_vat = product_data_span.attr("data-item_price_incl_vat"); 2406 product_price_excl_vat = product_data_span.attr("data-item_price_excl_vat"); 2407 product_regular_price = product_data_span.attr("data-item_regular_price"); 2408 product_sku = product_data_span.attr("data-item_sku"); 2409 product_brand = product_data_span.attr("data-item_brand"); 2410 product_gtin = product_data_span.attr("data-item_gtin"); 2411 product_ean = product_data_span.attr("data-item_ean"); 2412 product_url = product_data_span.attr("data-item_url"); 2413 product_variation_id = product_data_span.attr("data-item_variation_id") || null; 2414 category_1 = product_data_span.attr("data-item_category_1") || null; 2415 category_2 = product_data_span.attr("data-item_category_2") || null; 2416 category_3 = product_data_span.attr("data-item_category_3") || null; 2417 category_4 = product_data_span.attr("data-item_category_4") || null; 2418 2419 // If a quantity input exists, update the quantity 2420 var quantity_input = $(this).closest("form.cart").find("input.qty"); 2421 if (quantity_input.length) { 2422 quantity = parseInt(quantity_input.val()) || 1; 2423 } 2424 2425 // Ensure we have the necessary data 2426 if (!product_id || !product_name || !product_price) { 2427 window.metrion.helpers.log_debug("Item data is missing. Cannot send event to Metrion.", "log"); 2428 return; 2429 } 2430 2431 var product_data = { 2432 "type": "product", 2433 "id": window.metrion.helpers.safe_to_string(product_id), 2434 "gtin": window.metrion.helpers.safe_to_string(product_gtin), 2435 "ean": window.metrion.helpers.safe_to_string(product_ean), 2436 "variation_id": window.metrion.helpers.safe_to_string(product_variation_id), 2437 "name": window.metrion.helpers.safe_to_string(product_name), 2438 "price": window.metrion.helpers.safe_to_string(product_price), 2439 "price_incl_vat": window.metrion.helpers.safe_to_string(product_price_incl_vat), 2440 "price_excl_vat": window.metrion.helpers.safe_to_string(product_price_excl_vat), 2441 "regular_price": window.metrion.helpers.safe_to_string(product_regular_price), 2442 "quantity": window.metrion.helpers.safe_to_string(quantity), 2443 "sku": window.metrion.helpers.safe_to_string(product_sku), 2444 "url": window.metrion.helpers.safe_to_string(product_url), 2445 "currency": currency, 2446 "category_1": window.metrion.helpers.safe_to_string(category_1), 2447 "category_2": window.metrion.helpers.safe_to_string(category_2), 2448 "category_3": window.metrion.helpers.safe_to_string(category_3), 2449 "category_4": window.metrion.helpers.safe_to_string(category_4), 2450 "brand": window.metrion.helpers.safe_to_string(product_brand) 2451 }; 2452 2453 // Send data to Metrion 2454 if (typeof metrion !== "undefined" && typeof metrion.send_event === "function") { 2455 metrion.send_event("add_to_cart", { 2456 "items": [product_data], 2457 "trigger_type": "front-end", 2458 "engaged_time_ms": engagement_time_msec 2459 }); 2460 } else { 2461 window.metrion.helpers.log_debug("Metrion is not available or send_event function is missing.", "log"); 2462 } 2463 } catch (error) { 2464 window.metrion.helpers.log_debug("Error in Add to Cart event: " + error.message, "log"); 2465 } 2466 }); 2467 2468 // Item view event for single product pages 2469 if ($("body").hasClass("single-product")) { 2470 try { 2471 var product_id, product_name, product_brand, product_url, product_price, product_regular_price, product_price_incl_vat, product_price_excl_vat, product_variation_id, product_ean, product_gtin, product_sku, category_1, category_2, category_3, category_4, quantity = 1; 2472 var currency = "EUR"; // Default currency 2473 2474 // Get data from the metrion_item_data_single span in the footer 2475 var product_data_span = $("#metrion_item_data_single").find(".metrion_item_data_single"); 2476 2477 if (product_data_span.length) { 2478 product_id = window.metrion.helpers.safe_to_string(product_data_span.data("item_id")); 2479 product_name = window.metrion.helpers.safe_to_string(product_data_span.data("item_name")); 2480 product_price = window.metrion.helpers.safe_to_string(product_data_span.data("item_price")); 2481 product_url = window.metrion.helpers.safe_to_string(product_data_span.data("item_url")); 2482 product_brand = window.metrion.helpers.safe_to_string(product_data_span.data("item_brand")); 2483 product_price_incl_vat = window.metrion.helpers.safe_to_string(product_data_span.data("item_price_incl_vat")); 2484 product_price_excl_vat = window.metrion.helpers.safe_to_string(product_data_span.data("item_price_excl_vat")); 2485 product_regular_price = window.metrion.helpers.safe_to_string(product_data_span.data("item_regular_price")); 2486 product_sku = window.metrion.helpers.safe_to_string(product_data_span.data("item_sku")); 2487 product_ean = window.metrion.helpers.safe_to_string(product_data_span.data("item_ean")); 2488 product_gtin = window.metrion.helpers.safe_to_string(product_data_span.data("item_gtin")); 2489 product_variation_id = window.metrion.helpers.safe_to_string(product_data_span.data("item_variation_id")) || null; 2490 category_1 = product_data_span.data("item_category_1") || null; 2491 category_2 = product_data_span.data("item_category_2") || null; 2492 category_3 = product_data_span.data("item_category_3") || null; 2493 category_4 = product_data_span.data("item_category_4") || null; 2494 currency = product_data_span.data("currency") || currency; 2495 } 2496 2497 // Ensure we have the necessary data 2498 if (product_id && product_name && product_price) { 2499 var product_data = { 2500 "type": "product", 2501 "id": window.metrion.helpers.safe_to_string(product_id), 2502 "name": window.metrion.helpers.safe_to_string(product_name), 2503 "price": window.metrion.helpers.safe_to_string(product_price), 2504 "price_incl_vat": window.metrion.helpers.safe_to_string(product_price_incl_vat), 2505 "price_excl_vat": window.metrion.helpers.safe_to_string(product_price_excl_vat), 2506 "ean": window.metrion.helpers.safe_to_string(product_ean), 2507 "gtin": window.metrion.helpers.safe_to_string(product_gtin), 2508 "variation_id": window.metrion.helpers.safe_to_string(product_variation_id), 2509 "regular_price": window.metrion.helpers.safe_to_string(product_regular_price), 2510 "quantity": window.metrion.helpers.safe_to_string(quantity), 2511 "sku": window.metrion.helpers.safe_to_string(product_sku), 2512 "url": product_url, // The URL of the item page 2513 "currency": currency, 2514 "category_1": window.metrion.helpers.safe_to_string(category_1), 2515 "category_2": window.metrion.helpers.safe_to_string(category_2), 2516 "category_3": window.metrion.helpers.safe_to_string(category_3), 2517 "category_4": window.metrion.helpers.safe_to_string(category_4), 2518 "brand": window.metrion.helpers.safe_to_string(product_brand) 2519 }; 2520 2521 // Send data to Metrion for item view event 2522 if (typeof metrion !== "undefined" && typeof metrion.send_event === "function") { 2523 metrion.send_event("item_view", { 2524 "items": [product_data], 2525 "trigger_type": "front-end" 2526 }); 2527 } else { 2528 window.metrion.helpers.log_debug("Metrion is not available or send_event function is missing.", "log"); 2529 } 2530 } else { 2531 window.metrion.helpers.log_debug("Item data is missing. Cannot send event to Metrion.", "log"); 2532 } 2533 } catch (error) { 2534 window.metrion.helpers.log_debug("Error in Item View event: " + error.message, "log"); 2535 } 2536 } 2537 2538 // Item lists / search results 2539 if (typeof window.metrion_loop !== "undefined" 2540 && typeof window.metrion_basket_items === "undefined" 2541 && typeof window.metrion_checkout_items === "undefined" 2542 && typeof window.metrion_purchase_data === "undefined") { 2543 // Event name logic 2544 var loop_event_name = "view_item_list"; 2545 if (window.metrion_loop.is_search_page) { 2546 loop_event_name = "view_search_results"; 2547 } 2548 2549 // Event body logic 2550 var loop_event_body = { 2551 "items": window.metrion_loop.loop_items, 2552 "trigger_type": "front-end" 2553 } 2554 // Category page context 2555 if (window.metrion_is_category_page) { 2556 loop_event_body["is_category_page"] = window.metrion_is_category_page; 2557 if (window.metrion_category_hierarchy && window.metrion_category_hierarchy.length > 0) { 2558 loop_event_body["category_hierarchy"] = window.metrion_category_hierarchy.join(", "); 2559 2560 } 2561 } 2562 // Search page context 2563 if (window.metrion_loop.is_search_page && typeof window.metrion_loop.search_term !== "undefined") { 2564 loop_event_body["is_search_page"] = window.metrion_loop.is_search_page; 2565 loop_event_body["search_term"] = window.metrion_loop.search_term; 2566 } 2567 2568 // Trigger the event 2569 window.metrion.send_event(loop_event_name, loop_event_body); 2570 2571 } 2572 2573 // Basket view 2574 // Ensure metrion_basket_items is available 2575 if (typeof window.metrion_basket_items !== "undefined") { 2576 window.metrion.send_event("basket_view", { 2577 "items": window.metrion_basket_items.items, 2578 "trigger_type": "front-end" 2579 }); 2580 } else { 2581 window.metrion.helpers.log_debug("No basket view items available for begin_checkout.", "log"); 2582 } 2583 2584 // Begin checkout 2585 // Ensure metrion_checkout_items is available 2586 if (typeof window.metrion_checkout_items !== "undefined") { 2587 window.metrion.send_event("begin_checkout", { 2588 "items": window.metrion_checkout_items.items, 2589 "trigger_type": "front-end" 2590 }); 2591 } else { 2592 window.metrion.helpers.log_debug("No checkout items available for begin_checkout.", "log"); 2593 } 2594 }); 2595 }); 2596}; 2597 2598// Call the function 2599window.metrion.woocommerce_event_logic(); 2600; 2601 2602// Initialization 2603window.metrion.initialisation_sequence = function(){ 2604 // Check if the user id cookie exists 2605 if (window.metrion.helpers.get_cookie_value(metrion_api.user_cookie_name + "_js") === null) { 2606 // Only start this process if cookie and data placement is allowed 2607 if (window.metrion.configuration.allow_cookie_placement_before_explicit_consent === "1") { 2608 window.metrion.user_manager.ensure_user_cookie_existance(function () { 2609 window.metrion.consent_manager.initial_consent_enforcement(); 2610 //window.metrion.consent_manager.evaluate_consent_floodgate(); 2611 window.metrion.session_event_logic(); 2612 window.metrion.page_view_event_logic(); 2613 }); 2614 } 2615 else { 2616 // Check if consent is already provided, but only Metrion is not initialized properly yet 2617 var has_explicit_cmp_consent = window.metrion.consent_manager.has_explicit_cmp_consent(window.metrion.configuration.cmp_selection); 2618 if(has_explicit_cmp_consent){ 2619 window.metrion.user_manager.ensure_user_cookie_existance(function () { 2620 window.metrion.consent_manager.initial_consent_enforcement(); 2621 window.metrion.session_event_logi
2621c(); 2622 window.metrion.page_view_event_logic(); 2623 }); 2624 } 2625 // Metrion is not initialized yet, there is no user id and no permission for placement before consent. There is also no explicit consent found for the CMP yet. Floodgate everything 2626 else{ 2627 window.metrion.consent_manager.initial_consent_enforcement(); 2628 window.metrion.session_event_logic(); 2629 window.metrion.page_view_event_logic(); 2630 } 2631 } 2632 } 2633 else { 2634 window.metrion.consent_manager.initial_consent_enforcement(); 2635 // window.metrion.consent_manager.evaluate_consent_floodgate(); 2636 window.metrion.session_event_logic(); 2637 window.metrion.page_view_event_logic(); 2638 } 2639}; 2640 2641window.metrion.initialisation_sequence(); 2642; 2643 2644//Prevent overwriting of objects and preserve existing properties 2645window.metrion.google_ads = window.metrion.google_ads || {}; 2646 2647//Initialize consent mode 2648window.metrion.google_ads.init_consent_mode = function(){ 2649 // Define dataLayer and the gtag function. 2650 window.dataLayer = window.dataLayer || []; 2651 2652 // Check if the gtag function is already available or not 2653 if(typeof window.gtag === "undefined"){ 2654 function gtag(){dataLayer.push(arguments);} 2655 // Make the gtag function global 2656 window.gtag = gtag; 2657 } 2658 2659 // Set default consent to 'denied' as a placeholder 2660 window.gtag('consent', 'default', { 2661 'ad_storage': 'denied', 2662 'ad_user_data': 'denied', 2663 'ad_personalization': 'denied', 2664 'analytics_storage': 'denied' 2665 }); 2666}; 2667window.metrion.google_ads.init_consent_mode(); 2668 2669//Function to update consent mode 2670window.metrion.google_ads.set_consent_mode_to_granted = function(){ 2671 if(typeof window.gtag !== "undefined"){ 2672 // Set consent mode values to 'granted' 2673 window.gtag('consent', 'update', { 2674 'ad_storage': 'granted', 2675 'ad_user_data': 'granted', 2676 'ad_personalization': 'granted', 2677 'analytics_storage': 'granted' 2678 }); 2679 } 2680}; 2681 2682; 2683 2684//JS file containing the user engagement to capture the time elapsed variable 2685//Prevent overwriting of objects and preserve existing properties 2686window.metrion.google_analytics = window.metrion.google_analytics || {}; 2687// Set base value for the elapsed time metric in miliseconds 2688window.metrion.google_analytics.latest_engagement_unix = Date.now(); 2689window.metrion.google_analytics.initialize_event_listeners = function(){ 2690 2691 'use strict'; // Enables standard compliant mode 2692 // Replaces the modern .closest() method using a standard while loop 2693 function getParentAnchor(element) { 2694 var current = element; 2695 // Walk up the DOM tree until we find an anchor or hit the document root 2696 while (current && current.nodeType === 1) { 2697 if (current.tagName === 'A') { 2698 return current; 2699 } 2700 current = current.parentNode; 2701 } 2702 return null; 2703 } 2704 2705 // Click engagements 2706 document.addEventListener('click', function(event) { 2707 var event_unix = Date.now(); 2708 // Standard event.target (no deprecated srcElement) 2709 var target = event.target; 2710 var link = getParentAnchor(target); 2711 if (link && link.href) { 2712 // 1. Ignore "javascript:" pseudo-protocols 2713 var isScript = link.href.indexOf('javascript:') === 0; 2714 // 2. Ignore pure hash anchors (e.g. href="#top") which don't reload page 2715 var rawHref = link.getAttribute('href'); 2716 // 3. Detect Modifier Keys (Ctrl/Cmd+Click) which open new tabs 2717 var isNewTab = event.metaKey || event.ctrlKey || event.shiftKey || (link.target === '_blank'); 2718 // 4. Detect Download attributes 2719 var isDownload = link.hasAttribute('download'); 2720 var click_metadata = { 2721 id: target.id || null, 2722 // 'className' is the standard property, not 'class' 2723 classes: target.className || null, 2724 // Useful for tracking buttons like "Buy Now" 2725 text: (target.innerText || target.textContent || '').trim().substring(0, 50), 2726 engaged_time_ms: (event_unix - window.metrion.google_analytics.latest_engagement_unix), 2727 is_download: isDownload, 2728 is_new_tab: isNewTab, 2729 is_script: isScript, 2730 href: rawHref 2731 }; 2732 2733 // Trigger Metrion event 2734 window.metrion.send_event("link_click", click_metadata); 2735 // Reset engagement unix 2736 window.metrion.google_analytics.latest_engagement_unix = Date.now(); 2737 } 2738 }); 2739 2740 // Form Submit engagements 2741 document.addEventListener('submit', function(event) { 2742 var form = event.target; 2743 var event_unix = Date.now(); 2744 var form_metadata = { 2745 id: form.id, 2746 name: form.name, 2747 destination: form.action, 2748 method: (form.method || 'GET').toUpperCase(), 2749 encoding: form.enctype, 2750 engaged_time_ms: (event_unix - window.metrion.google_analytics.latest_engagement_unix) 2751 }; 2752 2753 //Trigger Metrion event 2754 window.metrion.send_event("form_submit", form_metadata); 2755 // Reset engagement unix 2756 window.metrion.google_analytics.latest_engagement_unix = Date.now(); 2757 }); 2758 2759 // Visibility change engagements 2760 try { 2761 // 1. Check if the browser supports the API (avoids logic errors) 2762 if (typeof document.visibilityState !== 'undefined') { 2763 // 2. Attach the listener 2764 document.addEventListener('visibilitychange', function() { 2765 var event_unix = Date.now(); 2766 if (document.visibilityState === 'hidden') { 2767 window.metrion.send_event("visibility_change", { 2768 engaged_time_ms: (event_unix - window.metrion.google_analytics.latest_engagement_unix) 2769 }); 2770 window.metrion.google_analytics.latest_engagement_unix = Date.now(); 2771 } else { 2772 //Only reset engagement time don't send the hidden engagement time 2773 window.metrion.google_analytics.latest_engagement_unix = Date.now(); 2774 window.metrion.send_event("visibility_change", { 2775 "type": "visibility_change", 2776 engaged_time_ms: 10 2777 }); 2778 } 2779 }); 2780 } 2781 } 2782 catch (e) { 2783 window.metrion.helpers.log_debug('Browser too old for visibility tracking'); 2784 } 2785}; 2786// Init additional GA4 event listeners 2787window.metrion.google_analytics.initialize_event_listeners(); 2788; 2789 2790(function() { 2791 if (window.metrion && window.metrion_api) { 2792 2793 // Get consent info safely 2794 var cookie_raw = window.metrion.helpers.get_cookie_value(window.metrion_api.consent_cookie_name); 2795 var mtrn_consent = cookie_raw ? JSON.parse(decodeURIComponent(cookie_raw)) : null; 2796 2797 // If marketing consent is given, async load the destination JS 2798 if (mtrn_consent && mtrn_consent.allow_marketing === "1") { 2799 2800 var ver = metrion_api.bundle_version ? "?ver=" + metrion_api.bundle_version : ""; 2801 var api_route_url = metrion_api.api_url + "destinations" + ver; 2802 var direct_file_url = metrion_api.bundle_dir_url + "destinations.js" + ver; 2803 2804 // Helper function to load a script with an optional fallback URL 2805 function dynamic_load_destinations(primary_endpoint, fallback_endpoint) { 2806 var s = document.createElement('script'); 2807 s.type = 'text/javascript'; 2808 s.async = true; 2809 s.defer = true; 2810 s.src = primary_endpoint; 2811 2812 // If a fallback is provided, attach an error listener 2813 if (fallback_endpoint) { 2814 s.onerror = function() { 2815 // Send the status event silently 2816 if (window.metrion && typeof window.metrion.send_event === 'function') { 2817 window.metrion.send_event("status_event", { 2818 "message": "Direct destination JS load failed, falling back to API/WP-json route.", 2819 "id": "fallback_destination_js_through_wp_json" 2820 }, {}); 2821 } 2822 2823 // Call the function again with ONLY the fallback URL to prevent infinite loops 2824 dynamic_load_destinations(fallback_endpoint, null); 2825 }; 2826 } 2827 2828 var x = document.getElementsByTagName('script')[0]; 2829 x.parentNode.insertBefore(s, x); 2830 window.metrion.helpers.log_debug( 2831 "Destination JS inserted dynamically", 2832 "log" 2833 ); 2834 } 2835 2836 // Determine which route to attempt first based on settings 2837 if (metrion_api.bundle_custom_endpoints === "1" || metrion_api.bundle_custom_endpoints === true) { 2838 // User explicitly enabled API route, just use that 2839 dynamic_load_destinations(api_route_url, null); 2840 window.metrion.helpers.log_debug( 2841 "Use API route for destination JS loading as per configuration.", 2842 "log" 2843 ); 2844 } else { 2845 // Try the direct path first, fallback to the API route if blocked/failed 2846 dynamic_load_destinations(direct_file_url, api_route_url); 2847 window.metrion.helpers.log_debug( 2848 "Use Direct JS route for destination JS loading as per configuration.", 2849 "log" 2850 ); 2851 } 2852 } 2853 } 2854})(); 2855; 2856 2857// Generated on 2026-10-05 11:43:28 UTC
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.