1/** 2 * Google Analytics : GA4 3 * 4 * @author https://versus.pro/ 5 * @copyright Business Tech - https://www.businesstech.fr/ 6 * @license see file: LICENSE.txt 7 * 8 * ____ _______ 9 * | _ \ |__ __| 10 * | |_) | | | 11 * | _ < | | 12 * | |_) | | | 13 * |____/ |_| 14 */ 15 16// Expose gtag globally as early as possible 17window.dataLayer = window.dataLayer || []; 18window.gtag = window.gtag || function () { dataLayer.push(arguments); } 19 20/** 21 * Set default consent to denied (privacy by default) 22 */ 23function setDefaultConsentDenied() { 24 if (btGapTag && (btGapTag.bUseConsent || btGapTag.bforceTag)) { 25 gtag("consent", "default", { 26 ad_storage: "denied", 27 analytics_storage: "denied", 28 ad_user_data: "denied", 29 ad_personalization: "denied", 30 }); 31 } 32} 33 34function getGtagConfigOptions(extraOptions) { 35 const options = extraOptions ? Object.assign({}, extraOptions) : {}; 36 37 if ( 38 btGapTag && 39 btGapTag.gaUserIdMode === "hidden" && 40 Number(btGapTag.gaUserId) > 0 41 ) { 42 options.user_id = String(btGapTag.gaUserId); 43 } 44 45 return options; 46} 47 48// Initialize gtag as early as possible 49setDefaultConsentDenied(); 50gtag('js', new Date()); 51gtag('config', btGapTag.gaId, getGtagConfigOptions()); 52 53document.addEventListener('DOMContentLoaded', function () { 54 // Handle consent mode 55 handleConsentMode(); 56 57 // Process refunds and partial refunds 58 processRefunds(); 59 60 // Handle specific tracking types 61 if (btGapTag.tagContent && btGapTag.tagContent.tracking_type) { 62 handleTrackingType(); 63 } 64 65 // Add event listeners for cart updates and user actions 66 addEventListeners(); 67}); 68 69/** 70 * Handle consent mode based on configuration 71 */ 72function handleConsentMode() { 73 if (btGapTag.bUseConsent) { 74 if (btGapTag.bUseAxeption == 1) { 75 setDefaultConsentDenied(); 76 handleAxeptioConsent(); 77 } else if (btGapTag.bUseAxeption == 0) { 78 setDefaultConsentDenied(); 79 handleCustomConsent(); 80 } else { 81 setDefaultConsentDenied(); 82 } 83 } else if (btGapTag.bforceTag) { 84 setDefaultConsentDenied(); 85 } 86} 87 88/** 89 * Handle Axeptio consent 90 */ 91function handleAxeptioConsent() { 92 function launchGoogleAnalytics() { 93 gtag("js", new Date()); 94 gtag("config", btGapTag.gaId, getGtagConfigOptions()); 95 } 96 97 window._axcb = window._axcb || []; 98 window._axcb.push(function (axeptio) { 99 axeptio.on("cookies:complete", function (choices) { 100 if (choices.google_analytics) { 101 gtag("consent", "update", { analytics_storage: "granted" }); 102 launchGoogleAnalytics(); 103 } 104 }); 105 }); 106} 107 108/** 109 * Handle custom consent 110 */ 111function handleCustomConsent() { 112 // Set initial consent state 113 setConsentState(btGapTag.iConsentConsentLvl); 114 115 // Handle consent button clicks 116 [btGapTag.bConsentHtmlElement, btGapTag.bConsentHtmlElementSecond].forEac
116h(element => { 117 if (element) { 118 $(element).on("click", updateConsent); 119 } 120 }); 121 122 // Remove ACB referrer cookie if installed 123 if (btGapTag.acbIsInstalled) { 124 $.ajax({ 125 type: "POST", 126 url: btGapTag.ajaxUrl, 127 dataType: "json", 128 data: { ajax: 1, action: "removedAcbReferrer", token: btGapTag.token }, 129 }); 130 } 131} 132 133/** 134 * Set consent state based on consent level 135 */ 136function setConsentState(consentLevel) { 137 let consentState = { 138 ad_storage: "denied", 139 analytics_storage: "denied", 140 ad_user_data: "denied", 141 ad_personalization: "denied", 142 }; 143 144 switch (consentLevel) { 145 case 1: // stats 146 consentState.analytics_storage = "granted"; 147 break; 148 case 2: // market 149 consentState.ad_storage = "granted"; 150 consentState.ad_user_data = "granted"; 151 consentState.ad_personalization = "granted"; 152 break; 153 case 3: // all 154 consentState = { 155 ad_storage: "granted", 156 analytics_storage: "granted", 157 ad_user_data: "granted", 158 ad_personalization: "granted", 159 }; 160 break; 161 } 162 163 gtag("consent", "update", consentState); 164 165 if (btGapTag.referer != null) { 166 gtag('set', 'page_referrer', btGapTag.referer); 167 } 168} 169 170/** 171 * Update consent via AJAX 172 */ 173function updateConsent() { 174 $.ajax({ 175 type: "POST", 176 url: btGapTag.ajaxUrl, 177 dataType: "json", 178 data: { ajax: 1, action: "updateConsent", token: btGapTag.token }, 179 success: function () { 180 gtag("js", new Date()); 181 gtag("config", btGapTag.gaId, getGtagConfigOptions({ cookie_update: false })); 182 gtag("consent", "update", { 183 ad_storage: "granted", 184 analytics_storage: "granted", 185 ad_user_data: "granted", 186 ad_personalization: "granted", 187 }); 188 }, 189 }); 190} 191 192/** 193 * Process refunds and partial refunds 194 */ 195function processRefunds() { 196 if (btGapTag.bRefund) {
197 sendRefundEvent("sendRefund", "refunds"); 198 } 199 if (btGapTag.bPartialRefund) { 200 sendRefundEvent("sendPartialRefund", "refunds_partial"); 201 } 202} 203 204/** 205 * Send refund event via AJAX 206 */ 207function sendRefundEvent(action, dataKey) { 208 $.ajax({ 209 type: "POST", 210 url: btGapTag.ajaxUrl, 211 dataType: "json", 212 data: { ajax: 1, action: action, token: btGapTag.token }, 213 success: function (jsonData) { 214 jsonData[dataKey].forEach(function (data) { 215 let eventData = { 216 currency: data.currency || data.refund_data.currency, 217 value: data.value || data.refund_data.value, 218 transaction_id: data.transaction_id || data.refund_data.transaction_id, 219 shipping: data.shipping || data.refund_data.shipping, 220 tax: data.tax || data.refund_data.tax, 221 }; 222 if (data.has_product) { 223 eventData.items = data.product; 224 } 225 gtag("event", "refund", eventData); 226 }); 227 }, 228 }); 229} 230 231/** 232 * Handle specific tracking types 233 */ 234function handleTrackingType() { 235 const trackingType = btGapTag.tagContent.tracking_type.value; 236 const handlers = { 237 view_item_list: handleViewItemList, 238 view_item: handleViewItem, 239 view_promotion: handleViewPromotion, 240 view_cart: handleViewCart, 241 begin_checkout: handleBeginCheckout, 242 add_shipping_info: handleAddShippingInfo, 243 add_payment_info: handleAddPaymentInfo, 244 purchase: handlePurchase, 245 generate_lead: () => gtag("event", "generate_lead"), 246 search: () => gtag("event", "search", { search_term: btGapTag.tagContent.content_name.value }), 247 }; 248 249 if (handlers[trackingType]) { 250 handlers[trackingType](); 251 } 252} 253 254/** 255 * Handle view item list event 256 */ 257function handleViewItemList() { 258 if (typeof btGapTag.tagContent.contents !== "undefined") { 259 const items = btGapTag.tagContent.contents.value.map(item => item); 260 gtag("event", "view_item_list", { 261 item_list_id: btGapTag.tagContent.content_name.value, 262 item_list_name: btGapTag.tagContent.content_name.value, 263 items: items, 264 }); 265 } 266 267 // Add click event listeners for products and wishlist 268 addProductClickListeners(); 269 addWishlistClickListeners(); 270} 271 272/** 273 * Add click event listeners for products 274 */ 275function addProductClickListeners() { 276 $(btGapTag.elementCategoryProduct).each(function () { 277 $(this).on("click", function () { 278 sendSelectItemEvent($(this).attr("data-id-product"), "selectItem"); 279 }); 280 }); 281} 282 283/** 284 * Add click event listeners for wishlist 285 */ 286function addWishlistClickListeners() { 287 $(btGapTag.elementWishCat).each(function () { 288 $(this).on("click", function () { 289 sendSelectItemEvent($(this).attr("data-id-product"), "selectItem", "add_to_wishlist"); 290 }); 291 }); 292} 293 294/** 295 * Send select item event via AJAX 296 */ 297function sendSelectItemEvent(productId, action, eventName = "select_item") { 298 $.ajax({ 299 type: "POST", 300 url: btGapTag.ajaxUrl, 301 dataType: "json", 302 data: { ajax: 1, action: action, iProductId: productId, token: btGapTag.token }, 303 success: function (jsonData) { 304 gtag("event", eventName, { 305 item_list_id: btGapTag.tagContent.content_name.value, 306 item_list_name: btGapTag.tagContent.content_name.value, 307 items: jsonData.data, 308 }); 309 }, 310 }); 311} 312 313/** 314 * Handle view item event 315 */ 316function handleViewItem() { 317 const items = btGapTag.tagContent.contents.value.map(item => item); 318 gtag("event", "view_item", { 319 currency: btGapTag.tagContent.currency.value, 320 value: btGapTag.tagContent.value.value, 321 items: items, 322 }); 323 324 $(btGapTag.elementWishProd).on("click", function () { 325 gtag("event", "add_to_wishlist", { 326 currency: btGapTag.tagContent.currency.value, 327 value: btGapTag.tagContent.value.value, 328 items: items, 329 }); 330 }); 331} 332 333/** 334 * Handle view promotion event 335 */ 336function handleViewPromotion() { 337 const items = btGapTag.tagContent.contents.value.map(item => item); 338 gtag("event", "view_promotion", { 339 item_list_id: btGapTag.tagContent.content_name.value, 340 item_list_name: btGapTag.tagContent.content_name.value, 341 items: items, 342 }); 343 344 $(btGapTag.elementCategoryProduct).each(function () { 345 $(this).on("click", function () { 346 sendSelectItemEvent($(this).attr("data-id-product"), "selectPromotion", "select_promotion"); 347 }); 348 }); 349} 350 351/** 352 * Handle view cart event 353 */ 354function handleViewCart() { 355 const items = btGapTag.tagContent.contents.value.map(item => item); 356 gtag("event", "view_cart", { 357 currency: btGapTag.tagContent.currency.value, 358 value: btGapTag.tagContent.value.value, 359 items: items, 360 }); 361} 362 363/** 364 * Handle begin checkout event 365 */ 366function handleBeginCheckout() { 367 const items = btGapTag.tagContent.contents.value.map(item => item); 368 gtag("event", "begin_checkout", { 369 currency: btGapTag.tagContent.currency.value, 370 value: btGapTag.tagContent.value.value, 371 coupon: btGapTag.tagContent.coupon_name.value, 372 items: items, 373 }); 374} 375 376/** 377 * Handle add shipping info event 378 */ 379function handleAddShippingInfo() { 380 const items = btGapTag.tagContent.contents.value.map(item => item); 381 // Function to get the value of the checked delivery option 382 function getCheckedDeliveryOption() { 383 const checkedOption = document.querySelector('.delivery-options input[type="radio"]:checked'); 384 if (checkedOption) { 385 const label = document.querySelector(`label[for="${checkedOption.id}"]`); 386 if (label) { 387 const carrierName = label.querySelector('.carrier-name'); 388 return carrierName ? carrierName.textContent.trim() : ''; 389 } 390 } 391 return ''; 392 } 393 394 // Add event listener for selection changes 395 document.querySelectorAll('.delivery-options input[type="radio"]').forEach(radio => { 396 radio.addEventListener('change', () => { 397 const selectedCarrier = getCheckedDeliveryOption(); 398 if (selectedCarrier) { 399 sendShippingEvent(radio.value, selectedCarrier); 400 } 401 }); 402 }); 403 404 // Send event for default option on page load 405 const defaultCarrier = getCheckedDeliveryOption(); 406 if (defaultCarrier) { 407 const defaultRadio = document.querySelector('.delivery-options input[type="radio"]:checked'); 408 if (defaultRadio) { 409 sendShippingEvent(defaultRadio.value, defaultCarrier); 410 } 411 } 412 413 function sendShippingEvent(carrierId) { 414 $.ajax({ 415 type: "POST", 416 url: btGapTag.ajaxUrl, 417 dataType: "json", 418 data: { ajax: 1, action: "sendCarrier", idCarrier: carrierId, token: btGapTag.token }, 419 success: function (jsonData) { 420 gtag("event", "add_shipping_info", { 421 currency: btGapTag.tagContent.currency.value, 422 value: btGapTag.tagContent.value.value, 423 coupon: btGapTag.tagContent.coupon_name.value, 424 shipping_tier: jsonData.sCarrierName, 425 items: items, 426 }); 427 }, 428 }); 429 } 430 431 $(btGapTag.elementShipping).each(function () { 432 $(this).on("click", function () { 433 sendShippingEvent($(this).val()); 434 }); 435 }); 436 437 // Send default carrier on page load 438 sendShippingEvent($(btGapTag.elementShipping + ":checked").val()); 439} 440 441/** 442 * Handle add payment info event 443 */ 444function handleAddPaymentInfo() { 445 const items = btGapTag.tagContent.contents.value.map(item => item); 446 447 $('.ps-shown-by-js').each(function () { 448 $(this).on("click", function () { 449 gtag("event", "add_payment_info", { 450 currency: btGapTag.tagContent.currency.value, 451 value: btGapTag.tagContent.value.value, 452 coupon: btGapTag.tagContent.coupon_name.value, 453 payment_type: $('label[for="' + $(this).attr("id") + '"]').text().trim(), 454 items: items, 455 }); 456 }); 457 }); 458} 459 460/** 461 * Handle purchase event 462 */ 463function handlePurchase() { 464 const items = btGapTag.tagContent.contents.value.map(item => item); 465 gtag("event", "purchase", { 466 currency: btGapTag.tagContent.currency.value, 467 value: btGapTag.tagContent.value.value, 468 transaction_id: btGapTag.tagContent.content_id.value, 469 coupon: btGapTag.tagContent.coupon_name.value, 470 shipping: btGapTag.tagContent.value_shipping ? btGapTag.tagContent.value_shipping.value : 0, 471 tax: btGapTag.tagContent.value_tax ? btGapTag.tagContent.value_tax.value : 0, 472 items: items, 473 }); 474 475 // Check if enhanced conversion is enabled 476 if (btGapTag.gaEnhancedConversion) { 477 // Verify if any relevant data is present before triggering the tag 478 const hasRelevantData = btGapTag.gaEnhancedConversionData.email || 479 btGapTag.gaEnhancedConversionData.phone_number || 480 (btGapTag.gaEnhancedConversionData.address && Object.values(btGapTag.gaEnhancedConversionData.address).some(Boolean)); 481 482 if (hasRelevantData) { 483 gtag("set", "user_data", { 484 "email": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.email ? btGa
484pTag.gaEnhancedConversionData.email : '', 485 "phone_number": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.phone_number ? btGapTag.gaEnhancedConversionData.phone_number : '', 486 "address": { 487 "first_name": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.address && btGapTag.gaEnhancedConversionData.address.first_name ? btGapTag.gaEnhancedConversionData.address.first_name : '', 488 "last_name": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.address && btGapTag.gaEnhancedConversionData.address.last_name ? btGapTag.gaEnhancedConversionData.address.last_name : '', 489 "street": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.address && btGapTag.gaEnhancedConversionData.address.street ? btGapTag.gaEnhancedConversionData.address.street : '', 490 "city": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.address && btGapTag.gaEnhancedConversionData.address.city ? btGapTag.gaEnhancedConversionData.address.city : '', 491 "region": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.address && btGapTag.gaEnhancedConversionData.address.region ? btGapTag.gaEnhancedConversionData.address.region : '', 492 "postal_code": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.address && btGapTag.gaEnhancedConversionData.address.postal_code ? btGapTag.gaEnhancedConversionData.address.postal_code : '', 493 "country": btGapTag.gaEnhancedConversionData && btGapTag.gaEnhancedConversionData.address && btGapTag.gaEnhancedConversionData.address.country ? btGapTag.gaEnhancedConversionData.address.country : '' 494 } 495 }); 496 } 497 } 498 499} 500 501/** 502 * Add event listeners for cart updates and user actions 503 */ 504function addEventListeners() { 505 if (typeof prestashop !== 'undefined') { 506 prestashop.on('updatedProduct', handleUpdatedProduct); 507 prestashop.on('clickQuickView', handleQuickView); 508 prestashop.on('updateCart', handleCartUpdate); 509 } 510 511 $(btGapTag.elementlogin).on("click", () => gtag("event", "login", { method: "Website direct" })); 512 $(btGapTag.elementsignup).on("click", () => gtag("event", "sign_up", { method: "Website direct" })); 513 $("video.pvr-video-player").on("click", () => gtag("event", "video_start")); 514} 515 516/** 517 * Handle updated product event 518 */ 519function handleUpdatedProduct(event) { 520 if (event) { 521 $.ajax({ 522 type: "POST", 523 url: btGapTag.ajaxUrl, 524 dataType: "json", 525 data: { 526 ajax: 1, 527 action: "updateCombination", 528 id_product_attribute: event.id_product_attribute, 529 id_product: $('input[name="id_product"]').val(), 530 token: btGapTag.token, 531 }, 532 success: function (jsonData) { 533 gtag("event", "view_item", { 534 currency: jsonData.currency, 535 value: jsonData.value, 536 items: jsonData.data, 537 }); 538 }, 539 }); 540 } 541} 542 543/** 544 * Handle quick view event 545 */ 546function handleQuickView(event) { 547 if (event) { 548 $.ajax({ 549 type: "POST", 550 url: btGapTag.ajaxUrl, 551 dataType: "json", 552 data: { 553 ajax: 1, 554 action: "updateQuickView", 555 id_product_attribute: event.dataset.idProductAttribute, 556 id_product: event.dataset.idProduct, 557 token: btGapTag.token, 558 }, 559 success: function (jsonData) { 560 gtag("event", "view_item", { 561 currency: jsonData.currency, 562 value: jsonData.value, 563 items: jsonData.data, 564 }); 565 }, 566 }); 567 } 568} 569 570/** 571 * Handle cart update event 572 */ 573function handleCartUpdate(event) { 574 if (event && event.reason) { 575 if (event.reason.linkAction === "add-to-cart") { 576 handleAddToCart(event); 577 } else if (event.reason.linkAction === "delete-from-cart") { 578 handleRemoveFromCart(event); 579 } 580 } 581} 582 583/** 584 * Handle add to cart event 585 */ 586function handleAddToCart(event) { 587 const productData = extractProductData(event); 588 $.ajax({ 589 type: "POST", 590 url: btGapTag.ajaxUrl, 591 dataType: "json", 592 data: { 593 ajax: 1, 594 action: "cartPageList", 595 id_product_attribute: productData.idProductAttribute, 596 id_product: productData.idProduct, 597 quantity: productData.quantity, 598 token: btGapTag.token, 599 }, 600 success: function (jsonData) { 601 gtag("event", "add_to_cart", { 602 currency: jsonData.currency, 603 value: jsonData.value, 604 items: jsonData.data, 605 }); 606 }, 607 }); 608} 609 610/** 611 * Handle remove from cart event 612 */ 613function handleRemoveFromCart(event) { 614 const productData = extractProductData(event); 615 $.ajax({ 616 type: "POST", 617 url: btGapTag.ajaxUrl, 618 dataType: "json", 619 data: { 620 ajax: 1, 621 action: "removeCart", 622 iProductId: productData.idProduct, 623 token: btGapTag.token, 624 }, 625 success: function (jsonData) { 626 gtag("event", "remove_from_cart", { 627 currency: jsonData.currency, 628 value: jsonData.value, 629 items: jsonData.data, 630 }); 631 }, 632 }); 633} 634 635/** 636 * Extract product data from event 637 * 638 * This function handles different event object structures that can come from: 639 * 1. Direct add to cart events (event.reason.idProduct) 640 * 2. AJAX responses (event.resp) 641 * 3. Cart update events (event.reason.cart.products) 642 * 643 * The fallback chain for each property ensures we can extract the data regardless 644 * of which event triggered the action. This is necessary because: 645 * - Product attributes (like size/color) come in different formats 646 * - Product IDs may be nested differently based on the event source 647 * - Quantity can be found in multiple locations or default to 1 648 * 649 * Using optional chaining (?.) prevents errors when accessing nested properties 650 * that might not exist in all event structures. 651 */ 652function extractProductData(event) { 653 // More robust property verification 654 const idProductAttribute = event.reason?.idProductAttribute || 655 event.resp?.id_product_attribute || 656 event.reason?.cart?.products?.[0]?.id_product_attribute || 657 0; 658 659 const idProduct = event.reason?.idProduct || 660 event.resp?.id_product || 661 event.reason?.cart?.products?.[0]?.id_product || 662 0; 663 664 const quantity = event.reason?.cart?.products_count || 665 event.resp?.quantity || 666 event.reason?.cart?.products?.[0]?.quantity || 667 1; 668 669 return { 670 idProductAttribute, 671 idProduct, 672 quantity 673 }; 674}
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.