1(function() { 2 'use strict'; 3 4 var API_BASE = window.SOL24_TRACKING_API; 5 var COOKIE_NAME = 'sol24_vid'; 6 var SESSION_KEY = 'sol24_session'; 7 var SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes 8 9 // ========== SESSION MANAGEMENT (like GA) ========== 10 function getOrCreateSession() { 11 var sessionData = sessionStorage.getItem(SESSION_KEY); 12 var now = Date.now(); 13 14 if (sessionData) { 15 try { 16 var session = JSON.parse(sessionData); 17 // Check if session is still valid (within timeout) 18 if (now - session.lastActivity < SESSION_TIMEOUT) { 19 session.lastActivity = now; 20 session.pageCount++; 21 session.isNew = false; 22 sessionStorage.setItem(SESSION_KEY, JSON.stringify(session)); 23 return session; 24 } 25 } catch (e) {} 26 } 27 28 // Create new session 29 var newSession = { 30 id: 'ses_' + now + '_' + Math.random().toString(36).substr(2, 9), 31 startTime: now, 32 lastActivity: now, 33 pageCount: 1, 34 isNew: true 35 }; 36 sessionStorage.setItem(SESSION_KEY, JSON.stringify(newSession)); 37 return newSession; 38 } 39 40 // ========== DEVICE DETECTION ========== 41 function getDeviceInfo() { 42 var ua = navigator.userAgent; 43 var deviceType = 'desktop'; 44 if (/Mobile|Android|iPhone|iPad|iPod|Windows Phone/i.test(ua)) { 45 deviceType = /iPad|Tablet/i.test(ua) ? 'tablet' : 'mobile'; 46 } 47 48 var browser = 'other'; 49 if (/Chrome/i.test(ua) && !/Edge|Edg/i.test(ua)) browser = 'Chrome'; 50 else if (/Firefox/i.test(ua)) browser = 'Firefox'; 51 else if (/Safari/i.test(ua) && !/Chrome/i.test(ua)) browser = 'Safari'; 52 else if (/Edge|Edg/i.test(ua)) browser = 'Edge'; 53 else if (/MSIE|Trident/i.test(ua)) browser = 'IE'; 54 55 var os = 'other'; 56 if (/Windows/i.test(ua)) os = 'Windows'; 57 else if (/Mac OS/i.test(ua)) os = 'macOS'; 58 else if (/Android/i.test(ua)) os = 'Android'; 59 else if (/iOS|iPhone|iPad|iPod/i.test(ua)) os = 'iOS'; 60 else if (/Linux/i.test(ua)) os = 'Linux'; 61 62 return { 63 screen_width: screen.width, 64 screen_height: screen.height, 65 viewport_width: window.innerWidth, 66 viewport_height: window.innerHeight, 67 device_type: deviceType, 68 browser: browser, 69 os: os, 70 language: navigator.language || navigator.userLanguage 71 }; 72 } 73 74 // ========== PAGE TYPE DETECTION ========== 75 function getPageType() { 76 var body = document.body; 77 if (body.classList.contains('home') || body.classList.contains('front-page')) return 'home'; 78 if (body.classList.contains('single-product')) return 'product'; 79 if (body.classList.contains('woocommerce-cart')) return 'cart'; 80 if (body.classList.contains('woocommerce-checkout')) return 'checkout'; 81 if (body.classList.contains('archive') || body.classList.contains('product-category')) return 'category'; 82 if (body.classList.contains('woocommerce-account')) return 'account'; 83 if (body.classList.contains('search-results')) return 'search'; 84 return 'other'; 85 } 86 87 function getCategory() { 88 // Try to get category from breadcrumbs or body class 89 var breadcrumb = document.querySelector('.woocommerce-breadcrumb'); 90 if (breadcrumb) { 91 var links = breadcrumb.querySelectorAll('a'); 92 if (links.length > 1) return links[links.length - 1].textContent.trim(); 93 } 94 return null; 95 } 96 97 // Store visitor_id in localStorage for persistence across pages 98 var STORAGE_KEY = 'sol24_visitor_id'; 99 var currentVisitorId = null; 100 101 function loadVisitorId() { 102 // 1. Check URL parameter first (from email click) 103 var params = new URLSearchParams(window.location.search); 104 var vid = params.get('vid'); 105 if (vid) { 106 localStorage.setItem(STORAGE_KEY, vid); 107 currentVisitorId = vid; 108 return vid; 109 } 110 // 2. Check localStorage (persists across pages) 111 var stored = localStorage.getItem(STORAGE_KEY); 112 if (stored) { 113 currentVisitorId = stored; 114 return stored; 115 } 116 // 3. Check cookie as fallback 117 var match = document.cookie.match(new RegExp('(^| )' + COOKIE_NAME + '=([^;]+)')); 118 if (match) { 119 currentVisitorId = match[2]; 120 localStorage.setItem(STORAGE_KEY, match[2]); 121 return match[2]; 122 } 123 return null; 124 } 125 126 function getVisitorId() { 127 if (currentVisitorId) return currentVisitorId; 128 return loadVisitorId(); 129 } 130 131 function track(endpoint, data) { 132 // Always include visitor_id in request body 133 var vid = getVisitorId(); 134 if (vid && !data.visitor_id) { 135 data.visitor_id = vid; 136 } 137 var xhr = new XMLHttpRequest(); 138 xhr.open('POST', API_BASE + '/api/v1/tracking/' + endpoint, true); 139 xhr.setRequestHeader('Content-Type', 'application/json'); 140 xhr.withCredentials = true; 141 xhr.send(JSON.stringify(data)); 142 } 143 144 // Parse German price format "1.234,56 â¬" to float 1234.56 145 function parseGermanPrice(text) { 146 if (!text) return 0; 147 // Remove currency symbols, whitespace 148 var cleaned = text.replace(/[â¬$£\s]/g, '').trim(); 149 // Check if it's German format (has comma as decimal separator) 150 if (cleaned.indexOf(',') > -1) { 151 // German format: 1.234,56 -> remove dots (thousands), replace comma with dot 152 cleaned = cleaned.replace(/\./g, '').replace(',', '.'); 153 } 154 // Otherwise assume it's already in standard format (1234.56) 155 var result = parseFloat(cleaned); 156 return isNaN(result) ? 0 : result; 157 } 158
159 function init() { 160 // Load visitor_id from URL/localStorage/cookie 161 loadVisitorId(); 162 163 var xhr = new XMLHttpRequest(); 164 var initUrl = API_BASE + '/api/v1/tracking/init'; 165 166 // Pass visitor_id if we have one 167 if (currentVisitorId) { 168 initUrl += '?vid=' + encodeURIComponent(currentVisitorId); 169 } 170 171 xhr.open('GET', initUrl, true); 172 xhr.withCredentials = true; 173 xhr.onload = function() { 174 if (xhr.status === 200) { 175 try { 176 var response = JSON.parse(xhr.responseText); 177 if (response.visitor_id) { 178 currentVisitorId = response.visitor_id; 179 localStorage.setItem(STORAGE_KEY, response.visitor_id); 180 } 181 } catch(e) {} 182 trackPageView(); 183 checkIdentification(); 184 initEngagementTracking(); 185 } 186 }; 187 xhr.onerror = function() { 188 console.log('Sol24 Tracking: Could not connect to API'); 189 }; 190 xhr.send(); 191 } 192 193 function trackPageView() { 194 var params = new URLSearchParams(window.location.search); 195 var productSku = getProductSku(); 196 var session = getOrCreateSession(); 197 var deviceInfo = getDeviceInfo(); 198 199 track('pageview', { 200 page_url: window.location.href, 201 page_title: document.title, 202 product_sku: productSku, 203 referrer: document.referrer, 204 utm_source: params.get('utm_source'), 205 utm_medium: params.get('utm_medium'), 206 utm_campaign: params.get('utm_campaign'), 207 // GA-like session tracking 208 session_id: session.id, 209 is_new_session: session.isNew, 210 // Page classification 211 page_type: getPageType(), 212 category: getCategory(), 213 // Device info 214 screen_width: deviceInfo.screen_width, 215 screen_height: deviceInfo.screen_height, 216 viewport_width: deviceInfo.viewport_width, 217 viewport_height: deviceInfo.viewport_height, 218 device_type: deviceInfo.device_type, 219 browser: deviceInfo.browser, 220 os: deviceInfo.os, 221 language: deviceInfo.language 222 }); 223 } 224 225 // ========== ENGAGEMENT TRACKING (like GA) ========== 226 var scrollDepthMarkers = [25, 50, 75, 90]; 227 var scrollDepthTracked = {}; 228 var pageLoadTime = Date.now(); 229 230 function initEngagementTracking() { 231 // Scroll depth tracking 232 window.addEventListener('scroll', throttle(function() { 233 var scrollPercent = Math.round((window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100); 234 scrollDepthMarkers.forEach(function(marker) { 235 if (scrollPercent >= marker && !scrollDepthTracked[marker]) { 236 scrollDepthTracked[marker] = true; 237 trackEvent('scroll_depth', null, { depth: marker }); 238 } 239 }); 240 }, 500)); 241 242 // Time on page tracking (at intervals and on unload) 243 var timeIntervals = [30, 60, 120, 300]; // 30s, 1m, 2m, 5m 244 var timeTracked = {}; 245 setInterval(function() { 246 var timeOnPage = Math.round((Date.now() - pageLoadTime) / 1000); 247 timeIntervals.forEach(function(interval) { 248 if (timeOnPage >= interval && !timeTracked[interval]) { 249 timeTracked[interval] = true; 250 trackEvent('time_on_page', null, { seconds: interval }); 251 } 252 }); 253 }, 10000); 254 255 // Track time on page before leaving 256 window.addEventListener('beforeunload', function() { 257 var timeOnPage = Math.round((Date.now() - pageLoadTime) / 1000); 258 if (timeOnPage > 5) { 259 trackEvent('page_exit', null, { 260 time_on_page: timeOnPage, 261 max_scroll_depth: Math.max.apply(null, Object.keys(scrollDepthTracked).map(Number)) || 0 262 }); 263 } 264 }); 265 266 // Track search 267 var searchForm = document.querySelector('.search-form, form.woocommerce-product-search'); 268 if (searchForm) { 269 searchForm.addEventListener('submit', function(e) { 270 var query = searchForm.querySelector('input[type="search"], input[name="s"]'); 271 if (query && query.value) { 272 trackEvent('search', null, { query: query.value }); 273 } 274 }); 275 } 276 277 // Track search result clicks (when on search results page) 278 if (document.body.classList.contains('search-results') || document.body.classList.contains('search')) { 279 var searchQuery = '';
280 var urlParams = new URLSearchParams(window.location.search); 281 searchQuery = urlParams.get('s') || ''; 282 283 if (searchQuery) { 284 // Find all product links in search results 285 var productLinks = document.querySelectorAll('.products .product a.woocommerce-loop-product__link, .products .product a.woocommerce-LoopProduct-link, ul.products li.product > a'); 286 287 productLinks.forEach(function(link, index) { 288 var product = link.closest('.product'); 289 if (!product) return; 290 291 // Get SKU from product element 292 var sku = ''; 293 var skuEl = product.querySelector('[data-product_sku]'); 294 if (skuEl) { 295 sku = skuEl.dataset.product_sku; 296 } else { 297 // Try to extract from product class (post-123) 298 var classMatch = product.className.match(/post-(\d+)/); 299 if (classMatch) { 300 sku = 'id:' + classMatch[1]; // Fallback to product ID 301 } 302 } 303 304 link.addEventListener('click', function() { 305 trackEvent('search_click', sku, { 306 query: searchQuery, 307 position: index + 1, 308 page_type: 'search' 309 }); 310 }); 311 }); 312 313 // Also track clicks on add-to-cart buttons in search results 314 var addToCartBtns = document.querySelectorAll('.products .product .add_to_cart_button, .products .product .ajax_add_to_cart'); 315 addToCartBtns.forEach(function(btn, index) { 316 btn.addEventListener('click', function() { 317 var sku = btn.dataset.product_sku || ''; 318 trackEvent('search_add_to_cart', sku, { 319 query: searchQuery, 320 position: index + 1, 321 page_type: 'search' 322 }); 323 }); 324 }); 325 } 326 } 327 328 // Track outbound links 329 document.addEventListener('click', function(e) { 330 var link = e.target.closest('a'); 331 if (link && link.href && link.hostname !== window.location.hostname) { 332 trackEvent('outbound_link', null, { url: link.href }); 333 } 334 }); 335 } 336 337 function throttle(func, limit) { 338 var inThrottle; 339 return function() { 340 if (!inThrottle) { 341 func.apply(this, arguments); 342 inThrottle = true; 343 setTimeout(function() { inThrottle = false; }, limit); 344 } 345 }; 346 } 347 348 function trackEvent(eventType, productSku, eventData) { 349 var session = getOrCreateSession(); 350 track('event', { 351 event_type: eventType, 352 product_sku: productSku, 353 event_data: eventData, 354 session_id: session.id 355 }); 356 } 357 358 function checkIdentification() { 359 var params = new URLSearchParams(window.location.search); 360 var email = params.get('email'); 361 var uid = params.get('uid'); 362 363 if (email || uid) { 364 var url = API_BASE + '/api/v1/tracking/identify?'; 365 if (email) url += 'email=' + encodeURIComponent(email); 366 if (uid) url += 'uid=' + encodeURIComponent(uid); 367 url += '&source=newsletter'; 368 369 var xhr = new XMLHttpRequest(); 370 xhr.open('GET', url, true); 371 xhr.withCredentials = true; 372 xhr.send(); 373 } 374 } 375 376 function getProductSku() { 377 // WooCommerce product page - check various data sources 378 // IMPORTANT: Check MAIN PRODUCT first, not recommendation widgets! 379 var sku = null; 380 381 // 1. PRIORITY: Check for WooCommerce product SKU in product meta area 382 // This is the most reliable source for the MAIN product 383 var skuWrapper = document.querySelector('.product_meta .sku, .sku_wrapper .sku, .summary .sku'); 384 if (skuWrapper && skuWrapper.textContent.trim()) { 385 return skuWrapper.textContent.trim(); 386 } 387 388 // 2. Check meta tags (set by WooCommerce for the main product) 389 var metaSku = document.querySelector('meta[property="product:sku"]'); 390 if (metaSku && metaSku.content) return metaSku.content; 391 392 // 3. Check WooCommerce product form (main product add-to-cart) 393 var form = document.querySelector('form.cart'); 394 if (form) { 395 // Check for data-product_sku on the form itself 396 if (form.dataset && form.dataset.product_sku) { 397 return form.dataset.product_sku; 398 } 399 // Check for hidden input with SKU 400 var skuInput = form.querySelector('input[name="sku"]'); 401 if (skuInput && skuInput.value) return skuInput.value; 402 } 403 404 // 4. Check for main product container (not recommendation widgets!) 405 // Look for data-product_sku ONLY in the main product area 406 var mainProduct = document.querySelector('.product.type-product, .single-product .product'); 407 if (mainProduct) { 408 var mainSkuEl = mainProduct.querySelector(':scope >
408 [data-product_sku], .summary [data-product_sku]'); 409 if (mainSkuEl && mainSkuEl.dataset.product_sku) { 410 return mainSkuEl.dataset.product_sku; 411 } 412 } 413 414 // 5. Extract SKU from URL as fallback (e.g., /produkt/SM-AU430/ or ?sku=SM-AU430) 415 var urlMatch = window.location.href.match(/[?&]sku=([^&]+)/); 416 if (urlMatch) return decodeURIComponent(urlMatch[1]); 417 418 // 6. Last resort: data-sku attribute (but not in recommendation sections) 419 var dataEl = document.querySelector('.product:not(.wc-product-rec) [data-sku]'); 420 if (dataEl && dataEl.dataset.sku) return dataEl.dataset.sku; 421 422 return null; 423 } 424 425 // ========== A/B TEST TRACKING ========== 426 // Track A/B test events (click, add_to_cart, conversion) 427 function trackABTestEvent(eventType, testId, variant, productId, recommendedProductId, position, revenue) { 428 if (!testId) return; 429 430 var xhr = new XMLHttpRequest(); 431 xhr.open('POST', API_BASE + '/api/v1/admin/ab-test-event', true); 432 xhr.setRequestHeader('Content-Type', 'application/json'); 433 xhr.withCredentials = true; 434 xhr.send(JSON.stringify({ 435 test_id: parseInt(testId), 436 variant: variant, 437 event_type: eventType, 438 session_id: getVisitorId(), 439 product_id: productId ? parseInt(productId) : null, 440 recommended_product_id: recommendedProductId ? parseInt(recommendedProductId) : null, 441 position: position ? parseInt(position) : null, 442 revenue: revenue ? parseFloat(revenue) : null 443 })); 444 } 445 446 // Get A/B test info from a recommendation element 447 function getABTestInfo(element) { 448 var track = element.closest('.wc-rec-track'); 449 if (!track) return null; 450 451 var testId = track.dataset.abTestId; 452 var variant = track.dataset.abVariant; 453 454 if (!testId || !variant) return null; 455 456 return { 457 testId: testId, 458 variant: variant, 459 recType: track.dataset.recType, 460 sourceProduct: track.dataset.sourceProduct 461 }; 462 } 463 464 // Public API - GA-like comprehensive tracking 465 window.Sol24Tracking = { 466 trackEvent: function(eventType, productSku, eventData) { 467 // Uses internal trackEvent which includes session_id 468 trackEvent(eventType, productSku, eventData); 469 }, 470 identify: function(email, source) { 471 track('identify', { 472 email: email, 473 source: source || 'form' 474 }); 475 }, 476 trackPageView: trackPageView, 477 trackABTestEvent: trackABTestEvent, 478 // Additional GA-like methods 479 getSessionId: function() { 480 return getOrCreateSession().id; 481 }, 482 getDeviceInfo: getDeviceInfo, 483 getPageType: getPageType 484 }; 485 486 // Initialize tracking immediately (before jQuery-dependent code) 487 if (document.readyState === 'loading') { 488 document.addEventListener('DOMContentLoaded', init); 489 } else { 490 init(); 491 } 492 493 // ========== META PIXEL-LIKE E-COMMERCE TRACKING ========== 494 495 // Track ViewContent (product page) 496 function trackViewContent() { 497 var pageType = getPageType(); 498 console.log('Sol24 Tracking: trackViewContent called, pageType=' + pageType); 499 if (pageType !== 'product') return; 500 501 var productData = getProductData(); 502 console.log('Sol24 Tracking: Product data:', productData); 503 if (productData) { 504 console.log('Sol24 Tracking: ViewContent', productData); 505 track('event', {
506 event_type: 'ViewContent', 507 product_sku: productData.sku, 508 event_data: { 509 content_type: 'product', 510 content_ids: [productData.sku], 511 content_name: productData.name, 512 content_category: productData.category, 513 value: productData.price, 514 currency: 'EUR' 515 } 516 }); 517 } 518 } 519 520 // Get product data from product page 521 function getProductData() { 522 try { 523 var sku = getProductSku(); 524 var name = document.querySelector('.product_title, h1.entry-title'); 525 var priceEl = document.querySelector('.price .woocommerce-Price-amount, .summary .price ins .amount, .summary .price .amount'); 526 var category = getCategory(); 527 528 var price = null; 529 if (priceEl) { 530 price = parseGermanPrice(priceEl.textContent); 531 } 532 533 return { 534 sku: sku, 535 name: name ? name.textContent.trim() : null, 536 price: price, 537 category: category 538 }; 539 } catch(e) { 540 return null; 541 } 542 } 543 544 // Track cart contents (ViewCart event) 545 function trackViewCart() { 546 var pageType = getPageType(); 547 console.log('Sol24 Tracking: trackViewCart called, pageType=' + pageType); 548 if (pageType !== 'cart') return; 549 550 var cartItems = getCartItems(); 551 console.log('Sol24 Tracking: Cart items found:', cartItems); 552 if (cartItems && cartItems.length > 0) { 553 var totalValue = cartItems.reduce(function(sum, item) { 554 return sum + (item.price * item.quantity); 555 }, 0); 556 557 console.log('Sol24 Tracking: ViewCart', { items: cartItems.length, value: totalValue }); 558 track('event', { 559 event_type: 'ViewCart', 560 event_data: { 561 content_type: 'product', 562 contents: cartItems, 563 content_ids: cartItems.map(function(i) { return i.sku; }), 564 num_items: cartItems.reduce(function(sum, i) { return sum + i.quantity; }, 0), 565 value: totalValue, 566 currency: 'EUR' 567 } 568 }); 569 } else { 570 console.log('Sol24 Tracking: No cart items found on cart page'); 571 } 572 } 573 574 // Track InitiateCheckout 575 function trackInitiateCheckout() { 576 var pageType = getPageType(); 577 console.log('Sol24 Tracking: trackInitiateCheckout called, pageType=' + pageType); 578 if (pageType !== 'checkout') return; 579 580 var cartItems = getCartItems(); 581 console.log('Sol24 Tracking: Checkout items found:', cartItems); 582 if (cartItems && cartItems.length > 0) { 583 var totalValue = cartItems.reduce(function(sum, item) { 584 return sum + (item.price * item.quantity); 585 }, 0); 586 587 console.log('Sol24 Tracking: InitiateCheckout', { items: cartItems.length, value: totalValue }); 588 track('event', { 589 event_type: 'InitiateCheckout', 590 event_data: { 591 content_type: 'product', 592 contents: cartItems, 593 content_ids: cartItems.map(function(i) { return i.sku; }), 594 num_items: cartItems.reduce(function(sum, i) { return sum + i.quantity; }, 0), 595 value: totalValue, 596 currency: 'EUR' 597 } 598 }); 599 } else { 600 console.log('Sol24 Tracking: No items found on checkout page'); 601 } 602 } 603 604 // Get cart items from page 605 function getCartItems() { 606 var items = []; 607 try { 608 console.log('Sol24 Tracking: getCartItems() searching for cart rows...'); 609 610 // WooCommerce cart page - try multiple selectors 611 var cartRows = document.querySelectorAll('.woocommerce-cart-form .cart_item, .woocommerce-checkout-review-order-table .cart_item, table.cart .cart_item, tr.cart_item, .shop_table .cart_item'); 612 console.log('Sol24 Tracking: Found ' + cartRows.length + ' cart rows'); 613 614 cartRows.forEach(function(row, idx) { 615 // IMPORTANT: Exclude .remove links (Ã button) - they are NOT the product name! 616 var nameEl = row.querySelector('.product-name a:not(.remove), td.product-name a:not(.remove), .cart_item a[href*="/produkt/"]:not(.remove)'); 617 var skuEl = row.querySelector('[data-product_sku]'); 618 var qtyEl = row.querySelector('.quantity input, input.qty, .product-quantity, td.product-quantity input'); 619 var priceEl = row.querySelector('.product-price .amount, td.product-price .amount, .product-price bdi'); 620 var subtotalEl = row.querySelector('.product-subtotal .amount, td.product-subtotal .amount, .product-subtotal bdi'); 621 622 // Get name, fallback to data attribute or URL parsing 623 var name = nameEl ? nameEl.textContent.trim() : ''; 624 625 // If name is empty or just special chars, try to get it from the product link 626 if (!name || name === 'Ã' || name.length < 3) { 627 // IMPORTANT: Also exclude .remove links in the fallback! 628 var productLink = row.querySelector('a[href*="/produkt/"]:not(.remove)'); 629 if (productLink && productLink.textContent.trim().length > 2 && productLink.textContent.trim() !== 'Ã') { 630 name = productLink.textContent.trim(); 631 } 632 } 633 634 // Final validation: if name is still invalid, try alternative selectors 635 if (!name || name === 'Ã' || name.length < 3) { 636 // Try td.product-name direct text or data-title attribute 637 var productNameTd = row.querySelector('.product-name, td[data-title="Product"], td[data-title="Produkt"]'); 638 if (productNameTd) { 639 var links = productNameTd.querySelectorAll('a:not(.remove)'); 640 for (var i = 0; i < links.length; i++) { 641 var linkText = links[i].textContent.trim(); 642 if (linkText && linkText !== 'Ã' && linkText.length > 2) { 643 name = linkText; 644 break; 645 } 646 } 647 } 648 } 649 var sku = skuEl ? skuEl.dataset.product_sku : (nameEl ? nameEl.href.split('/').filter(
649Boolean).pop() : ''); 650 var qty = qtyEl ? (parseInt(qtyEl.value || qtyEl.textContent) || 1) : 1; 651 var price = 0; 652 653 if (priceEl) { 654 price = parseGermanPrice(priceEl.textContent) || 0; 655 } else if (subtotalEl) { 656 var subtotal = parseGermanPrice(subtotalEl.textContent) || 0; 657 price = qty > 0 ? subtotal / qty : 0; 658 } 659 660 console.log('Sol24 Tracking: Row ' + idx + ': name=' + name + ', sku=' + sku + ', qty=' + qty + ', price=' + price); 661 662 if (name || sku) { 663 items.push({ 664 sku: sku, 665 name: name, 666 quantity: qty, 667 price: price 668 }); 669 } 670 }); 671 672 // Also try mini-cart / fragments if no items found 673 if (items.length === 0) { 674 console.log('Sol24 Tracking: No cart rows, trying mini-cart...'); 675 var miniCartItems = document.querySelectorAll('.mini_cart_item, .woocommerce-mini-cart-item'); 676 console.log('Sol24 Tracking: Found ' + miniCartItems.length + ' mini-cart items'); 677 678 miniCartItems.forEach(function(item) { 679 var nameEl = item.querySelector('a:not(.remove)'); 680 var qtyEl = item.querySelector('.quantity'); 681 if (nameEl) { 682 var qtyMatch = qtyEl ? qtyEl.textContent.match(/(\d+)/) : null; 683 items.push({ 684 sku: nameEl.href.split('/').filter(Boolean).pop(), 685 name: nameEl.textContent.trim(), 686 quantity: qtyMatch ? parseInt(qtyMatch[1]) : 1, 687 price: 0 688 }); 689 } 690 }); 691 } 692 693 // Try order review table on checkout (different structure) 694 if (items.length === 0) { 695 console.log('Sol24 Tracking: Trying order review table...'); 696 var reviewRows = document.querySelectorAll('.woocommerce-checkout-review-order-table tbody tr, .order-total, .cart_totals tr'); 697 reviewRows.forEach(function(row) { 698 var nameEl = row.querySelector('.product-name, td:first-child'); 699 var qtyMatch = nameEl ? nameEl.textContent.match(/Ã\s*(\d+)/) : null; 700 var priceEl = row.querySelector('.product-total .amount, td:last-child .amount'); 701 702 if (nameEl && !row.classList.contains('order-total') && !row.classList.contains('cart-subtotal')) { 703 var nameText = nameEl.textContent.replace(/Ã\s*\d+/, '').trim(); 704 if (nameText && nameText.length > 2) { 705 items.push({ 706 sku: '', 707 name: nameText, 708 quantity: qtyMatch ? parseInt(qtyMatch[1]) : 1, 709 price: priceEl ? parseGermanPrice(priceEl.textContent) : 0 710 }); 711 } 712 } 713 }); 714 } 715 } catch(e) { 716 console.log('Sol24 Tracking: Error reading cart', e); 717 } 718 console.log('Sol24 Tracking: getCartItems() returning ' + items.length + ' items'); 719 return items; 720 } 721 722 // Enhanced page tracking based on page type 723 function trackEcommerceEvents() { 724 var pageType = getPageType(); 725 console.log('Sol24 Tracking: trackEcommerceEvents called, pageType=' + pageType); 726 727 if (pageType === 'product') { 728 trackViewContent(); 729 } else if (pageType === 'cart') { 730 trackViewCart(); 731 } else if (pageType === 'checkout') { 732 trackInitiateCheckout(); 733 } 734 } 735 736 // ========== RECOMMENDATION CLICK TRACKING ========== 737 // Track clicks on recommended products (jQuery-dependent, runs after jQuery loads) 738 function initJQueryTracking() { 739 if (typeof jQuery === 'undefined') { 740 // Wait for jQuery 741 setTimeout(initJQueryTracking, 100); 742 return; 743 } 744 745 // Track e-commerce events after jQuery is ready 746 trackEcommerceEvents(); 747 748 // Track clicks on BOTH regular slides (.wc-rec-slide) AND FBT
748cards (.wc-rec-fbt-card) 749 jQuery(document).on('click', '.wc-rec-slide a, .wc-rec-fbt-card a', function(e) { 750 var $slide = jQuery(this).closest('.wc-rec-slide, .wc-rec-fbt-card'); 751 var abInfo = getABTestInfo($slide[0]); 752 753 if (abInfo) { 754 var productId = $slide.data('product-id'); 755 var position = $slide.data('position'); 756 var sourceProduct = abInfo.sourceProduct; 757 758 // Track click event for A/B test 759 trackABTestEvent('click', abInfo.testId, abInfo.variant, sourceProduct, productId, position, null); 760 761 // Also track in visitor events 762 var sku = $slide.data('product-sku'); 763 if (sku) { 764 window.Sol24Tracking.trackEvent('recommendation_click', sku, { 765 position: position, 766 rec_type: abInfo.recType, 767 ab_test_id: abInfo.testId, 768 ab_variant: abInfo.variant 769 }); 770 } 771 } 772 }); 773 774 // ========== ADD TO CART TRACKING (Meta Pixel style) ========== 775 // Auto-track add to cart (WooCommerce AJAX) with full product details 776 jQuery(document.body).on('added_to_cart', function(event, fragments, cart_hash, $button) { 777 var productId = $button.data('product_id'); 778 var sku = $button.data('product_sku') || getProductSku(); 779 var quantity = parseInt($button.closest('form').find('input.qty').val()) || 1; 780 781 // Get product details from page 782 var productName = jQuery('.product_title, h1.entry-title').first().text().trim(); 783 var priceEl = jQuery('.price .woocommerce-Price-amount, .summary .price ins .amount, .summary .price .amount').first(); 784 var price = priceEl.length ? parseGermanPrice(priceEl.text()) : 0; 785 var category = getCategory(); 786 787 // Check if this is from a recommendation (regular slide OR FBT card) 788 var $slide = $button.closest('.wc-rec-slide, .wc-rec-fbt-card'); 789 var abInfo = $slide.length ? getABTestInfo($slide[0]) : null; 790 791 if (abInfo) { 792 var position = $slide.data('position'); 793 var sourceProduct = abInfo.sourceProduct; 794 795 // Track add_to_cart for A/B test 796 trackABTestEvent('add_to_cart', abInfo.testId, abInfo.variant, sourceProduct, productId, position, price * quantity); 797 798 // Store A/B test info in sessionStorage for conversion tracking 799 var abCartItems = JSON.parse(sessionStorage.getItem('sol24_ab_cart_items') || '{}'); 800 abCartItems[productId] = { 801 testId: abInfo.testId, 802 variant: abInfo.variant, 803 position: position, 804 sourceProduct: sourceProduct, 805 addedAt: Date.now() 806 }; 807 sessionStorage.setItem('sol24_ab_cart_items', JSON.stringify(abCartItems)); 808 } 809 810 // Meta Pixel style AddToCart event 811 track('event', { 812 event_type: 'AddToCart', 813 product_sku: sku, 814 event_data: { 815 content_type: 'product', 816 content_ids: [sku], 817 content_name: productName, 818 content_category: category, 819 quantity: quantity, 820 value: price * quantity, 821 currency: 'EUR', 822 product_id: productId, 823 from_recommendation: !!abInfo, 824 ab_test_id: abInfo ? abInfo.testId : null, 825 ab_variant: abInfo ? abInfo.variant : null 826 } 827 }); 828 }); 829 830 // Auto-track add to cart (form submit for variable products) 831 jQuery(document).on('submit', 'form.cart', function() { 832 var sku = getProductSku(); 833 if (sku) { 834 window.Sol24Tracking.trackEvent('add_to_cart', sku, { 835 quantity: jQuery(this).find('input[name="quantity"]').val() || 1 836 }); 837 } 838 }); 839 // Track AddPaymentInfo when payment method is selected or form is filled 840 jQuery(document).on('change', 'input[name="payment_method"]', function() { 841 var cartItems = getCartItems(); 842 var totalValue = cartItems.reduce(function(sum, item) { 843 return sum + (item.price * item.quantity); 844 }, 0); 845 846 console.log('Sol24 Tracking: AddPaymentInfo', { items: cartItems.length, value: totalValue }); 847 track('event', { 848 event_type: 'AddPaymentInfo', 849 event_data: { 850 content_type: 'product', 851 contents: cartItems, 852 content_ids: cartItems.map(function(i) { return i.sku; }), 853 value: totalValue, 854 currency: 'EUR', 855 payment_method: jQuery(this).val() 856 } 857 }); 858 }); 859 860 // Track checkout form submission (before actual purchase) 861 jQuery(document).on('submit', 'form.checkout', function() { 862 var cartItems = getCartItems(); 863 var totalValue = cartItems.reduce(function(sum, item) { 864 return sum + (item.price * item.quantity); 865 }, 0); 866 867 console.log('Sol24 Tracking: SubmitCheckout', { items: cartItems.length, value: totalValue }); 868 track('event', { 869 event_type: 'SubmitCheckout', 870 event_data: { 871 content_type: 'product', 872 contents: cartItems, 873 content_ids: cartItems.map(function(i) { return i.sku; }), 874 value: totalValue, 875 currency: 'EUR' 876 } 877 }); 878 }); 879 880 } // end initJQueryTracking 881 882 // Start jQuery tracking when ready 883 initJQueryTracking(); 884})();
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.