https://www.teva.org.il/app/plugins/activetrail-for-woocommerce/assets/js/event-tracker.js?ver=1.2.4
1/** 2 * ActiveTrail Event Tracker 3 * Frontend JavaScript for tracking ecommerce events 4 */ 5 6(function ($) { 7 'use strict'; 8 9 // Event tracker object 10 var ActiveTrailEventTracker = { 11 12 // Configuration 13 config: { 14 ajax_url: activetrail_events.ajax_url, 15 nonce: activetrail_events.nonce, 16 customer_email: activetrail_events.customer_email, 17 session_id: activetrail_events.session_id, 18 currency: activetrail_events.currency, 19 debug: activetrail_events.debug 20 }, 21 22 // Event queue for batching 23 eventQueue: [], 24 25 // Initialize the tracker 26 init: function () { 27 this.bindEvents(); 28 this.trackPageView(); 29 this.setupPeriodicSync(); 30 31 if (this.config.debug) { 32 console.log( 'ActiveTrail Event Tracker initialized' ); 33 } 34 }, 35 36 // Bind event listeners 37 bindEvents: function () { 38 var self = this; 39 40 // Track product view on product pages 41 if ($( 'body' ).hasClass( 'single-product' )) { 42 this.trackProductView(); 43 } 44 45 // Track add to cart button clicks 46 $( document ).on( 47 'click', 48 '.single_add_to_cart_button', 49 function (e) { 50 var $form = $( this ).closest( 'form.cart' );
51 var productId = $form.find( 'input[name="add-to-cart"]' ).val() || 52 $form.find( 'button[name="add-to-cart"]' ).val(); 53 var quantity = $form.find( 'input[name="quantity"]' ).val() || 1; 54 55 if (productId) { 56 self.trackAddToCartIntent( productId, quantity ); 57 } 58 } 59 ); 60 61 // Track AJAX add to cart 62 $( document.body ).on( 63 'added_to_cart', 64 function (event, fragments, cart_hash, $button) { 65 var productId = $button.data( 'product_id' ) || $button.val(); 66 var quantity = $button.data( 'quantity' ) || 1; 67 68 if (productId) { 69 self.trackAddToCart( productId, quantity ); 70 } 71 } 72 ); 73 74 // Track cart page interactions 75 if ($( 'body' ).hasClass( 'woocommerce-cart' )) { 76 this.bindCartEvents(); 77 } 78 79 // Track checkout page 80 if ($( 'body' ).hasClass( 'woocommerce-checkout' )) { 81 this.trackCheckoutStart(); 82 this.bindCheckoutEvents(); 83 } 84 85 // Track remove from cart 86 $( document ).on( 87 'click', 88 '.remove', 89 function (e) { 90 var $link = $( this ); 91 var productId = self.extractProductIdFromRemoveLink( $link.attr( 'href' ) ); 92 93 if (productId) { 94 self.trackRemoveFromCart( productId ); 95 } 96 } 97 ); 98 99 // Track quantity updates in cart 100 $( document ).on( 101 'change', 102 '.cart .qty', 103 function () { 104 var $input = $( this ); 105 var productId = self.extractProductIdFromCartRow( $input.closest( 'tr' ) ); 106 var newQuantity = parseInt( $input.val() ) || 0; 107 var oldQuantity = parseInt( $input.data( 'old-quantity' ) ) || 0; 108 109 if (productId && newQuantity !== oldQuantity) { 110 self.trackCartQuantityUpdate( productId, oldQuantity, newQuantity ); 111 } 112 113 // Store new quantity as old quantity for next change 114 $input.data( 'old-quantity', newQuantity ); 115 } 116 ); 117 118 // Store initial quantities 119 $( '.cart .qty' ).each( 120 function () { 121 $( this ).data( 'old-quantity', parseInt( $( this ).val() ) || 0 ); 122 } 123 ); 124 }, 125 126 // Bind cart-specific events 127 bindCartEvents: function () { 128 var self = this; 129 130 // Track cart view 131 this.trackEvent( 132 'cart_view', 133 { 134 cart_total: this.getCartTotal(), 135 cart_items_count: this.getCartItemsCount() 136 } 137 ); 138 139 // Track proceed to checkout clicks 140 $( document ).on( 141 'click', 142 '.checkout-button', 143 function () { 144 self.trackEvent( 145 'proceed_to_checkout', 146 { 147 cart_total: self.getCartTotal(), 148 cart_items_count: self.getCartItemsCount() 149 } 150 ); 151 } 152 ); 153 }, 154 155 // Bind checkout-specific events 156 bindCheckoutEvents: function () { 157 var self = this; 158 159 // Track checkout steps 160 $( document ).on( 161 'change', 162 '#billing_email', 163 function () { 164 if ($( this ).val()) { 165 self.trackEvent( 166 'checkout_email_entered', 167 { 168 email: $( this ).val(), 169 cart_total: self.getCartTotal() 170 } 171 ); 172 } 173 } 174 ); 175 176 // Track payment method selection 177 $( document ).on( 178 'change', 179 'input[name="payment_method"]', 180 function () { 181 self.trackEvent( 182 'checkout_payment_method_selected', 183 { 184 payment_method: $( this ).val(), 185 cart_total: self.getCartTotal() 186 } 187 ); 188 } 189 ); 190 191 // Track place order attempts 192 $( document ).on( 193 'click', 194 '#place_order', 195 function () { 196 self.trackEvent( 197 'checkout_place_order_clicked', 198 { 199 cart_total: self.getCartTotal(), 200 payment_method: $( 'input[name="payment_method"]:checked' ).val() 201 } 202 ); 203 } 204 ); 205 }, 206 207 // Track page view 208 trackPageView: function () { 209 var pageType = this.getPageType(); 210 211 this.trackEvent( 212 'page_view', 213 { 214 page_type: pageType, 215 page_url: window.location.href, 216 page_title: document.title, 217 referrer: document.referrer 218 } 219 ); 220 }, 221 222 // Track product view 223 trackProductView: function () { 224 var productData = this.getProductData(); 225 226 if (productData.product_id) { 227 this.trackEvent( 'product_view', productData, productData.product_price ); 228 } 229 }, 230 231 // Track add to cart intent (button click) 232 trackAddToCartIntent: function (productId, quantity) { 233 var productData = this.getProductData( productId ); 234 productData.quantity = quantity; 235 productData.intent = true; 236 237 this.trackEvent( 'add_to_cart_intent', productData, productData.product_price * quantity ); 238 }, 239 240 // Track successful add to cart 241 trackAddToCart: function (productId, quantity) { 242 var productData = this.getProductData( productId ); 243 productData.quantity = quantity; 244 245 this.trackEvent( 'add_to_cart', productData, productData.product_price * quantity ); 246 }, 247 248 // Track remove from cart 249 trackRemoveFromCart: function (productId) { 250 this.trackEvent( 251 'remove_from_cart', 252 { 253 product_id: productId, 254 cart_total: this.getCartTotal() 255 } 256 ); 257 }, 258 259 // Track cart quantity update 260 trackCartQuantityUpdate: function (productId, oldQuantity, newQuantity) { 261 this.trackEvent( 262 'cart_quantity_update', 263 { 264 product_id: productId, 265 old_quantity: oldQuantity, 266 new_quantity: newQuantity, 267 quantity_change: newQuantity - oldQuantity, 268 cart_total: this.getCartTotal() 269 } 270 ); 271 }, 272 273 // Track checkout start 274 trackCheckoutStart: function () { 275 this.trackEvent( 276 'checkout_start', 277 { 278 cart_total: this.getCartTotal(), 279 cart_items_count: this.getCartItemsCount(), 280 currency: this.config.currency 281 }, 282 this.getCartTotal() 283 ); 284 }, 285 286 // Generic event tracking method 287 trackEvent: function (eventType, eventData, eventValue) { 288 eventData = eventData || {}; 289 eventValue = eventValue || 0; 290 291 // Add common data 292 eventData.timestamp = new Date().toISOString(); 293 eventData.user_agent = navigator.userAgent; 294 eventData.screen_resolution = screen.width + 'x' + screen.height; 295 eventData.viewport_size = $( window ).width() + 'x' + $( window ).height(); 296 297 var event = { 298 event_type: eventType, 299 event_data: eventData, 300 event_value: parseFloat( eventValue ) || 0, 301 customer_email: this.config.customer_email, 302 session_id: this.config.session_id, 303 currency: this.config.currency 304 }; 305 306 // Add to queue 307 this.eventQueue.push( event ); 308 309 // Send immediately for critical events 310 if (this.isCriticalEvent( eventType )) { 311 this.sendEvent( event ); 312 } 313 314 if (this.config.debug) { 315 console.log( 'Event tracked:', eventType, eventData ); 316 } 317 }, 318 319 // Check if event is critical and should be sent immediately 320 isCriticalEvent: function (eventType) { 321 var criticalEvents = [ 322 'add_to_cart', 323 'remove_from_cart', 324 'checkout_start', 325 'checkout_place_order_clicked' 326 ]; 327 328 return criticalEvents.indexOf( eventType ) !== -1; 329 }, 330 331 // Send single event to server 332 sendEvent: function (event) { 333 var self = this; 334 335 $.ajax( 336 { 337 url: this.config.ajax_url, 338 type: 'POST', 339 data: { 340 action: 'activetrail_track_event', 341 nonce: this.config.nonce, 342 event_type: event.event_type, 343 event_data: event.event_data, 344 event_value: event.event_value 345 }, 346 success: function (response) { 347 if (self.config.debug) { 348 console.log( 'Event sent successfully:', event.event_type ); 349 } 350 }, 351 error: function (xhr, status, error) { 352 if (self.config.debug) { 353 console.error( 'Failed to send event:', event.event_type, error ); 354 } 355 356 // Store in localStorage for retry 357 self.storeEventForRetry( event ); 358 } 359 } 360 ); 361 }, 362 363 // Send queued events in batch 364 sendQueuedEvents: function () { 365 if (this.eventQueue.length === 0) { 366 return; 367 } 368 369 var eventsToSend = this.eventQueue.splice( 0, 10 ); // Send max 10 at a time 370 var self = this; 371
372 eventsToSend.forEach( 373 function (event) { 374 if ( ! self.isCriticalEvent( event.event_type )) { 375 self.sendEvent( event ); 376 } 377 } 378 ); 379 }, 380 381 // Store event in localStorage for retry 382 storeEventForRetry: function (event) { 383 try { 384 var storedEvents = JSON.parse( localStorage.getItem( 'activetrail_failed_events' ) || '[]' ); 385 storedEvents.push( event ); 386 387 // Keep only last 50 failed events 388 if (storedEvents.length > 50) { 389 storedEvents = storedEvents.slice( -50 ); 390 } 391 392 localStorage.setItem( 'activetrail_failed_events', JSON.stringify( storedEvents ) ); 393 } catch (e) { 394 if (this.config.debug) { 395 console.error( 'Failed to store event for retry:', e ); 396 } 397 } 398 }, 399 400 // Retry failed events from localStorage 401 retryFailedEvents: function () { 402 try { 403 var storedEvents = JSON.parse( localStorage.getItem( 'activetrail_failed_events' ) || '[]' ); 404 405 if (storedEvents.length > 0) { 406 var self = this; 407 408 storedEvents.forEach( 409 function (event) { 410 self.sendEvent( event ); 411 } 412 ); 413 414 // Clear stored events after retry 415 localStorage.removeItem( 'activetrail_failed_events' ); 416 } 417 } catch (e) { 418 if (this.config.debug) { 419 console.error( 'Failed to retry events:', e ); 420 } 421 } 422 }, 423 424 // Setup periodic sync 425 setupPeriodicSync: function () { 426 var self = this; 427 428 // Send queued events every 30 seconds 429 setInterval( 430 function () { 431 self.sendQueuedEvents(); 432 }, 433 30000 434 ); 435 436 // Retry failed events every 5 minutes 437 setInterval( 438 function () { 439 self.retryFailedEvents(); 440 }, 441 300000 442 ); 443 444 // Send events before page unload 445 $( window ).on( 446 'beforeunload', 447 function () { 448 self.sendQueuedEvents(); 449 } 450 ); 451 }, 452 453 // Helper methods 454 getPageType: function () { 455 var body = $( 'body' ); 456 457 if (body.hasClass( 'single-product' )) { 458 return 'product'; 459 } 460 if (body.hasClass( 'woocommerce-cart' )) { 461 return 'cart'; 462 } 463 if (body.hasClass( 'woocommerce-checkout' )) { 464 return 'checkout'; 465 } 466 if (body.hasClass( 'woocommerce-account' )) { 467 return 'account'; 468 } 469 if (body.hasClass( 'woocommerce-shop' ) || body.hasClass( 'archive' )) { 470 return 'shop'; 471 } 472 473 return 'other'; 474 }, 475 476 getProductData: function (productId) { 477 var data = { 478 product_id: productId || this.getProductIdFromPage(), 479 product_name: this.getProductName(), 480 product_price: this.getProductPrice(), 481 product_category: this.getProductCategory(), 482 product_sku: this.getProductSku(), 483 product_url: window.location.href 484 }; 485 486 return data; 487 }, 488 489 getProductIdFromPage: function () { 490 // Try multiple methods to get product ID 491 var productId = $( 'input[name="add-to-cart"]' ).val() || 492 $( 'button[name="add-to-cart"]' ).val() || 493 $( '.single_add_to_cart_button' ).data( 'product_id' ) || 494 $( 'body' ).attr( 'class' ).match( /postid-(\d+)/ ); 495 496 if (Array.isArray( productId )) { 497 productId = productId[1]; 498 } 499 500 return parseInt( productId ) || 0; 501 }, 502 503 getProductName: function () { 504 return $( '.product_title' ).text() || 505 $( 'h1.entry-title' ).text() || 506 document.title.split( ' - ' )[0] || ''; 507 }, 508 509 getProductPrice: function () { 510 var priceText = $( '.price .amount' ).first().text() || 511 $( '.price' ).text() || 512 $( '.woocommerce-Price-amount' ).first().text() || ''; 513 514 // Extract numeric value 515 var price = priceText.replace( /[^\d.,]/g, '' ).replace( ',', '.' ); 516 return parseFloat( price ) || 0; 517 }, 518 519 getProductCategory: function () { 520 var categories = []; 521 $( '.posted_in a' ).each( 522 function () { 523 categories.push( $( this ).text() ); 524 } 525 ); 526 return categories.join( ', ' ); 527 }, 528 529 getProductSku: function () { 530 return $( '.sku' ).text() || ''; 531 }, 532 533 getCartTotal: function () { 534 var totalText = $( '.cart-subtotal .amount' ).text() || 535 $( '.order-total .amount' ).text() || 536 $( '.cart_totals .amount' ).last().text() || ''; 537 538 var total = totalText.replace( /[^\d.,]/g, '' ).replace( ',', '.' ); 539 return parseFloat( total ) || 0; 540 }, 541 542 getCartItemsCount: function () { 543 return parseInt( $( '.cart_item' ).length ) || 544 parseInt( $( '.cart-contents-count' ).text() ) || 0; 545 }, 546 547 extractProductIdFromRemoveLink: function (href) { 548 if ( ! href) { 549 return null; 550 } 551 552 var match = href.match( /remove_item=([^&]+)/ ); 553 return match ? match[1] : null; 554 }, 555 556 extractProductIdFromCartRow: function ($row) { 557 var classes = $row.attr( 'class' ) || ''; 558 var match = classes.match( /cart_item_(\d+)/ ); 559 return match ? parseInt( match[1] ) : null; 560 } 561 }; 562 563 // Initialize when document is ready 564 $( document ).ready( 565 function () { 566 ActiveTrailEventTracker.init(); 567 } 568 ); 569 570 // Make it globally available for manual tracking 571 window.ActiveTrailEventTracker = ActiveTrailEventTracker; 572 573})( jQuery );
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.