PageSourceSearch

https://przedobrazem.pl/wp-content/plugins/full-picture-analytics-…/public/modules/woo/fupi-woo.js?ver=10.1.1

js przedobrazem.pl collected 2026-10-02 20:41:05 UTC 27,999 bytes, 816 lines download raw bytes

1function fupi_woo(){
2
3	if ( ! fp.woo ) return;
4
5	fp.loaded.push('woo'); // checked in other functions
6
7	// Helpers
8
9	FP.fns.get_woo_prod_id = prod => { // This must be equivalent to "get_woo_prod_id" in class-fupi-public-woo.php
10		if ( prod.type == 'variation' && fp.woo.variable_tracking_method == 'track_parents' ) {
11			return fp.woo.sku_is_id ? prod.parent_sku || prod.parent_id : prod.parent_id;
12		} else {
13			return fp.woo.sku_is_id ? prod.sku || prod.id : prod.id;
14		};
15	}
16
17	FP.fns.get_woo_prod_name = prod => { // This must be equivalent to "get_woo_prod_name" in class-fupi-public-woo.php
18		return prod.type == 'variation' && fp.woo.variable_tracking_method == 'track_parents' ? prod.parent_name || prod.name : prod.name;
19	}
20
21	function get_current_variant_id(){
22		
23		// Get fields with the main product ID and the currently selected variation ID
24		let product_id_field = FP.findFirst( 'form.variations_form input[name="product_id"]' ),
25			variant_id_field = FP.findFirst( 'form.variations_form input[name="variation_id"]' );
26
27		if ( ! ( product_id_field && variant_id_field ) ) return false;
28
29		let product_id = product_id_field.value,
30			variant_id = variant_id_field.value;
31
32		// Check if main prod and variant are the same
33		if ( product_id == variant_id ) {
34
35			// Mark this variant as viewed and return
36			fpdata.woo.viewed_variants.push(variant_id);
37			return false;
38		};
39
40		// Check if this variant has already been tracked
41		if ( fpdata.woo.viewed_variants && fpdata.woo.viewed_variants.includes( variant_id ) ) return false;
42
43		// Mark this variant as viewed
44		fpdata.woo.viewed_variants.push(variant_id);
45
46		return variant_id;
47	}
48
49	
50
51	// PREPARE GROUPED PRODUCTS
52
53	function prepare_grouped_prods_for_addtocart(){
54	
55		FP.findAll( 'form.grouped_form' ).forEach( form_el => {
56	
57			let item_wrap = FP.findAll('.woocommerce-grouped-product-list-item', form_el),
58				dataset_info = [],
59				added_value = false;
60	
61			if ( item_wrap.length > 0 ){
62	
63				// save info on products with qty > 0 in a data property in the form element
64				item_wrap.forEach( item => {
65					
66					let qty = FP.findFirst( 'input.qty', item ),
67						id = item.id.replace('product-', '');
68	
69					if ( id && qty && qty.value && qty.value > 0 ) {
70						dataset_info.push( [ id, qty.value ] );
71						added_value = true;
72					}
73				});
74			};
75			
76			form_el.dataset.fupi_products = added_value ? JSON.stringify(dataset_info) : '';
77		} )
78	};
79
80	setTimeout( prepare_grouped_prods_for_addtocart, 500 );
81	document.addEventListener( 'change', prepare_grouped_prods_for_addtocart );
82	document.addEventListener( 'click', () => { setTimeout( prepare_grouped_prods_for_addtocart, 100 ); } ); // this makes sure the script will not slow down tracking
83
84	// PREPARE SINGLE PRODUCTS AND TEASERS
85	// TRACK IMPRESSIONS OF SINGLE PRODS AND TEASERS
86
87	function get_teaser_list_name( teaser_wrap, el ){
88
89		let list = teaser_wrap.closest("ul") || teaser_wrap.parentElement,
90			list_name = el.dataset.list_name || list && list.dataset.fupi_list_name;
91
92		if ( list && ! list_name ) {
93				
94			if ( list.parentElement.parentElement.classList.contains('wp-block-woocommerce-related-products') ) {
95				list_name = 'woo related block';
96			} else if ( list.parentElement.classList.contains('wp-block-handpicked-products') ) {
97				list_name = 'woo handpicked block';
98			} else if ( list.parentElement.classList.contains('wp-block-product-best-sellers') ) {
99				list_name = 'woo bestsellers block';
100			} else if ( list.parentElement.classList.contains('wp-block-product-new') ) {
101				list_name = 'woo new products block';
102			} else if ( list.parentElement.classList.contains('wp-block-product-on-sale') ) {
103				list_name = 'woo on sale block';
104			} else if ( list.parentElement.classList.contains('wp-block-product-top-rated') ) {
105				list_name = 'woo top rated block';
106			} else if ( list.parentElement.classList.contains('wc-block-products-by-attribute') ) {
107				list_name = 'woo products by attribute block';
108			} else {
109				list_name = 'woo products'; // default
110			};
111		};
112
113		list.classList.add('fupi_products_list');
114		
115		// save list name in the list's data attrib
116		if ( list && list_name ) list.dataset.fupi_list_name = list_name;
117
118		return list_name;
119	}
120
121	function process_new_html_prod_data(){
122		
123		let prod_data_els = FP.findAll(".fupi_prod_data:not(.fupi_ready)");
124
125		if ( prod_data_els.length == 0 ) return false;
126
127		prod_data_els.forEach( el => {
128
129			let list_name = 'single',
130				id = el.dataset.id,
131				prod_data = JSON.parse ( el.dataset.data ),
132				prod_type = el.dataset.type || prod_data.type,
133				prod_wrap = false,
134				closest_form_element = el.closest("form.cart, form.wp-block-add-to-cart-with-options");
135
136			// add prod data to fpdata.woo.products
137			fpdata.woo.products[id] = fpdata.woo.products[id] ? { ...fpdata.woo.products[id], ...prod_data } : prod_data;
138			
139			// mark as processed
140			el.classList.add("fupi_ready");
141
142			// skip variants
143			if ( prod_type == 'variant' ) return;
144
145			// add classes to prod wrapper
146
147			// if is product
148			if ( closest_form_element ) {
149
150				prod_wrap = closest_form_element.parentElement.classList.contains("summary") ? closest_form_element.parentElement : closest_form_element.parentElement.parentElement;
151				prod_wrap.classList.add('fupi_woo_single_product', 'fupi_woo_product');
152
153			// probably is teaser
154			} else {
155
156				let wrapper_selector = 'li';
157
158				if ( fp.woo.teaser_wrapper_sel ) {
159					wrapper_selector += ', ' + fp.woo.teaser_wrapper_sel;
160				}
161
162				prod_wrap = el.closest( wrapper_selector ) || el.parentElement;
163				list_name = get_teaser_list_name( prod_wrap, el );
164				prod_wrap.classList.add("fupi_woo_teaser", "fupi_woo_product");
165				
166				let btns_and_links = FP.findAll('a, button', prod_wrap );
167				
168				// add class to ATC buttons inside the wrapper
169				btns_and_links.forEach( btn => {
170
171					if ( btn.classList.contains('single_add_to_cart') ) return;
172					if ( fp.woo.wishlist_btn_sel && btn.classList.contains( fp.woo.wishlist_btn_sel ) ) return;
173
174					let href = btn.getAttribute('href');
175
176					if ( btn.classList.contains('add_to_cart_button') && ( btn.tagName == 'BUTTON' || ( href && href.includes('add-to-cart=') ) ) ) {
177						btn.classList.add('fupi_add_to_cart_button');
178					}
179
180				} );
181			}
182
183			// save product ID in the fpdata.woo.lists
184			if ( prod_wrap ) {
185				prod_wrap.dataset.fupi_woo_prod_id = id;
186				fpdata.woo.lists[list_name] = fpdata.woo.lists[list_name] || [];
187				if ( ! fpdata.woo.lists[list_name].includes(id) ) fpdata.woo.lists[list_name].push( id );
188			}
189		});
190
191		return true;
192	}
193
194	function process_new_allprods_block_data(){
195
196		if ( ! fp.woo.products_from_all_products_block || fp.woo.products_from_all_products_block.length == 0 ) return false;
197		
198		fp.woo.products_from_all_products_block.forEach( prod => {
199
200			// get cats
201			let price = parseInt( prod.prices.price ) / 100,
202				categories = [];
203
204			prod.categories.forEach( cat => categories.push( cat.name ) );
205
206			// get all data
207			let data = {
208				'categories' : categories,
209				'id' : prod.id,
210				'name' : prod.name,
211				'sku' : prod.sku,
212				'price' : price,
213				'type' : prod.type,
214				// 'parent_id', parent_sku' and 'brand' are not available in the data obj
215			}
216			
217			// update prod in the fpdata obj
218			fpdata.woo.products[prod.id] = fpdata.woo.products[prod.id] ? { ...fpdata.woo.products[prod.id], ...data } : data;
219
220			// mark prod in HTML
221
222			let prod_link = FP.findFirst('div.wp-block-woocommerce-all-products li.wc-block-grid__product:not(.fupi_ready) a[href="' + prod.permalink + '"]');
223
224			if ( prod_link ) {
225				
226				let prod_wrap = prod_link.closest("li.wc-block-grid__product"),
227					list = prod_link.closest('ul');
228
229				if ( ! prod_wrap ) return;
230				
231				list_name = 'woo all products block';
232				list.dataset.fupi_list_name = list_name;
233				list.classList.add('fupi_products_list');
234
235				prod_wrap.classList.add("fupi_woo_teaser", "fupi_woo_product");
236				prod_wrap.dataset.fupi_woo_prod_id = prod.id;
237
238				let add_to_cart_btns = FP.findAll('.add_to_cart_button, .ajax_add_to_cart', prod_wrap );
239
240				add_to_cart_btns.forEach( btn => btn.classList.add('fupi_add_to_cart_button') );
241
242				// save product data in the fpdata.woo.lists
243				fpdata.woo.lists[list_name] = fpdata.woo.lists[list_name] || [];
244				if ( ! fpdata.woo.lists[list_name].includes(prod.id) ) fpdata.woo.lists[list_name].push( prod.id );
245			};
246		});
247
248		return true;
249	}
250
251	function prepare_prod_data(){
252		
253		let new_prods_added = process_new_html_prod_data();
254		let new_allprods_blocks_added = process_new_allprods_block_data();
255		
256		if ( ! new_prods_added && ! new_allprods_blocks_added ) return;
257		if ( fp.woo.dont_track_views_after_refresh && fpdata.refreshed ) return;
258		
259		FP.doActions( "woo_impress" );
260	}
261
262	function track_def_variant_view(){
263
264		if ( fp.woo.variable_tracking_method != 'track_def_var' ) return;
265		if ( fp.woo.dont_track_views_after_refresh && fpdata.refreshed ) return;
266			
267		let variant_id_field = FP.findFirst( 'form.variations_form input[name="variation_id"]' );
268
269		if ( ! variant_id_field ) return;
270
271		let timer = 1000;
272
273		let	variant_check_interval = setInterval( () => {
274
275			if ( variant_id_field.value != '0' ) {
276				clearInterval( variant_check_interval );
277
278				let variant_id = get_current_variant_id();
279	
280				if ( variant_id && fpdata.woo.products[variant_id] && FP.hasActions( 'woo_def_variant_view' ) ) {
281					FP.doActions( "woo_def_variant_view", variant_id );
282				}
283			}
284
285			if ( timer >= 4000 ) {
286				clearInterval( variant_check_interval );
287				return;
288			}
289
290			timer += 1000;
291
292		}, 1000 );
293	}
294
295	// Prepare teasers and single products for tracking and init tracking
296	if ( document.readyState === "complete" ) {
297		prepare_prod_data();
298		track_def_variant_view();
299	} else {
300		document.addEventListener('DOMContentLoaded', ()=>{
301			prepare_prod_data();
302			track_def_variant_view();
303		} );
304	}
305
306	setInterval( ()=>{prepare_prod_data();}, 1000 );
307
308	// TRACKING HELPERS
309
310	function get_teaser_data(){
311
312		let teaser_el = fpdata.clicked.element.closest('.fupi_woo_teaser'),
313			prod_id = teaser_el.dataset.fupi_woo_prod_id;
314		
315		if ( prod_id && fpdata.woo.products[prod_id] ) {
316
317			let prod = { ...fpdata.woo.products[prod_id] },
318				teasers_wrapper = teaser_el.closest('.fupi_products_list');
319
320			if ( ! teasers_wrapper ) return false;
321
322			let	list_name = teasers_wrapper.dataset.fupi_list_name || 'woo products',
323				index = fpdata.woo.lists[list_name] && fpdata.woo.lists[list_name].includes(prod_id) ? fpdata.woo.lists[list_name].indexOf(prod_id) + 1 : 1;
324
325			prod['list_name'] = list_name;
326			prod['index'] = index;
327
328			return prod;
329		}
330
331		return false;
332	}
333
334	// TRACK CLICKS IN TEASER
335
336	FP.addAction( ['click'], () => {
337
338		if ( ! FP.hasActions( 'woo_teaser_click' ) ) return;
339
340		let selector = '.fupi_woo_teaser a:not(.fupi_add_to_cart_button)' + ( fp.wo
340o.wishlist_btn_sel ? ':not(' + fp.woo.wishlist_btn_sel + ')' : '' );
341
342		if ( FP.isClickTarget( selector ) ) {
343			let teaser_data = get_teaser_data();
344			if ( teaser_data ) FP.doActions( 'woo_teaser_click', { 'products' : [[teaser_data, 1]], 'value' : teaser_data.price } );
345		}
346	} );
347
348	// TRACK ADD TO CART FROM A TEASER
349
350	FP.addAction( ['click'], function(){
351		
352		// Skip if tracking should happen in cart instead
353		if ( fp.woo.where_track_addtocart === 'in_cart' ) return;
354		
355		if ( ! FP.hasActions( 'woo_add_to_cart' ) || ( !! fp.woo.disable_woo_events && fp.woo.disable_woo_events.includes('add_to_cart_teaser') ) ) return;
356
357		// prevent "add to cart" event from being fired twice - 1st time when the Add to cart button is clicked and the other one when cart is updated (tracked by a different script)
358		// if ( document.body.classList.contains('woocommerce-cart') ) return;
359
360		if ( FP.isClickTarget( '.fupi_add_to_cart_button, .fupi_add_to_cart_button *' ) ) {
361			let teaser_data = get_teaser_data();
362			if ( teaser_data ) FP.doActions( 'woo_add_to_cart', { 'products' : [[teaser_data, 1]], 'value' : teaser_data.price } );
363		}
364	} );
365
366	// TRACK ADD TO CART FOR A SIMPLE, VARIABLE OR GROUPED PRODUCT
367
368	FP.addAction( ['click'], function(){
369		
370		// Skip if tracking should happen in cart instead
371		if ( fp.woo.where_track_addtocart === 'in_cart' ) return;
372		
373		if ( ! FP.hasActions( 'woo_add_to_cart' ) || ( !! fp.woo.disable_woo_events && fp.woo.disable_woo_events.includes('add_to_cart_full') ) ) return;
374
375		if ( FP.isClickTarget( '.single_add_to_cart_button:not(.disabled), .single_add_to_cart_button:not(.disabled) *' ) ) {
376			
377			let form_el = fpdata.clicked.element.closest('form.cart');
378				form_found = false;
379
380			// if no classic form element
381			if ( ! form_el ) {
382
383				let atc_plus_opts_form = fpdata.clicked.element.closest('form.wp-block-add-to-cart-with-options')
384
385				// if this is a form element from "Add to cart + options (beta) block"
386				if ( atc_plus_opts_form ){
387
388					let atc_single_btn = FP.findFirst('.add_to_cart_button.product_type_simple', atc_plus_opts_form)
389
390					// check what type of product is being clicked
391					if ( atc_single_btn ) {
392						
393						// we can get prod id from the button itself
394						let prod_id = atc_single_btn.dataset.product_id,
395							prod = prod_id ? fpdata.woo.products[prod_id] : false;
396						
397						if ( prod ) {
398
399							let qty_el = FP.findFirst( '.qty', atc_plus_opts_form ),
400								qty = qty_el ? qty_el.value : 1,
401								value = Math.round( prod.price * qty * 100 ) / 100;
402
403							FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } );
404						}
405
406					} else {
407						if ( fp.main.debug ) console.log('[FP] At the moment only tracking single products is supported in the "Add to cart + Options (beta) block!');
408					}
409
410				} else {
411					
412					// this is a single button
413					if ( fpdata.clicked.element?.href && fpdata.clicked.element.href.includes('add-to-cart=') ) {
414						
415						let prod_id = FP.getUrlParamByName( 'add-to-cart', fpdata.clicked.element.href );
416	
417						if ( fpdata.woo.products[prod_id] ) {
418							
419							let qty_from_url = FP.getUrlParamByName( 'quantity', fpdata.clicked.element.href ),
420								qty = qty_from_url || 1,
421								prod = fpdata.woo.products[prod_id],
422								value = Math.round( prod.price * qty * 100 ) / 100;
423	
424							FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } );
425							FP.setCookie( 'fp_last_atc', prod_id, 0 );
426						}
427					}
428				}
429			
430			// track variable product
431			} else if ( form_el.classList.contains('variations_form') ) {
432
433				let prod_id = FP.findFirst( 'input.variation_id', form_el ).value;
434
435				if ( fpdata.woo.products[prod_id] ) {
436
437					let qty_el = FP.findFirst( 'input.qty', form_el ),
438						qty = qty_el && qty_el.value && qty_el.value > 0 ? parseInt( qty_el.value ) : 1,
439						prod = fpdata.woo.products[prod_id],
440						value = Math.round( prod.price * qty * 100 ) / 100;
441
442					FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } );
443				}
444
445			// track grouped product
446			} else if ( form_el.classList.contains('grouped_form') ) {
447				
448				let prods_data_arr = form_el.dataset.fupi_products ? JSON.parse( form_el.dataset.fupi_products ) : [],
449					items_a = [],
450					value = 0;
451
452				if ( prods_data_arr.length > 0 ) {
453					
454					prods_data_arr.forEach( prod_a => {
455						
456						let prod_id = prod_a[0],
457							qty = parseFloat( prod_a[1] );
458	
459						if ( fpdata.woo.products[prod_id] ) {
460							
461							let prod = fpdata.woo.products[prod_id];
462							
463							value += prod.price * qty;
464							items_a.push( [prod, qty] );
465						}
466					} );
467	
468					value = Math.round( value * 100 ) / 100;
469	
470					if ( items_a.length > 0 ) FP.doActions( 'woo_add_to_cart', { 'products' : items_a, 'value' : value } );
471				}
472				
473
474			// track simple product
475			} else if ( form_el ) {
476
477				let atc_btn = FP.findFirst( '.single_add_to_cart_button', form_el ),
478					prod_id = atc_btn?.value || atc_btn.dataset?.prod_id || atc_btn.dataset?.prodid || atc_btn.dataset?.product_id || atc_btn.dataset?.pid;
479
480				if ( !! prod_id && fpdata.woo.products[prod_id] ) {
481
482					let qty_el = FP.findFirst( 'input.qty', form_el ),
483						qty = qty_el && qty_el.value && qty_el.value > 0 ? parseInt( qty_el.value ) : 1,
484						prod = fpdata.woo.products[prod_id],
485						value = Math.round( prod.price * qty * 100 ) / 100;
486
487					FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty]], 'value': value } );
488				};
489			}
490		};
491	} );
492
493	// TRACK REMOVE ITEMS FROM CLASSIC MINI-CART
494
495	FP.addAction( ['click'], function(){
496
497		// Skip if tracking should happen in cart instead
498		if ( fp.woo.where_track_addtocart === 'in_cart' ) return;
499
500		if ( ! FP.hasActions( 'woo_remove_from_cart' ) ) return;
501
502		if ( FP.isClickTarget( 'a.remove.remove_from_cart_button' ) ) {
503
504			let product_wrap = fpdata.clicked.element.closest('li'),
505				fupi_product_data_el = FP.findFirst('.fupi_cart_item_data', product_wrap),
506				prod_id = fupi_product_data_el.dataset.product_id;
507			
508			if ( fpdata.woo.cart.items[prod_id] ) {
509
510				let prod = { ...fpdata.woo.cart.items[prod_id] },
511					value = Math.round( prod.price * prod.qty * 100 ) / 100;
512
513				FP.doActions( 'woo_remove_from_cart', { 'products' : [[prod, false]], 'value' : value } );
514			}
515		}
516	} );
517
518	// TRACK ADD/REMOVE ITEMS IN THE CLASSIC CART
519
520	// PREPARE CLASSIC CART FOR TRACKING QUANTITY CHANGES
521	// makes sure that adding and removing products in cart is tracked
522
523	function prepare_classic_cart( ajax_update ){
524
525		let cart_data_el = FP.findFirst('span.fupi_cart_data:not(.fupi_ready)');
526
527		if ( cart_data_el && !! cart_data_el.innerHTML ) {
528
529			let cart_data = JSON.parse( cart_data_el.innerHTML );
530
531			if ( ajax_update ) fpdata.woo.cart_old = {...fpdata.woo.cart};
532			
533			fpdata.woo.cart = cart_data;
534			cart_data_el.classList.add('fupi_ready'); // Mark as processed
535
536			if ( fpdata.page_type == "Woo Cart" && fpdata.woo.cart.items && fp.woo.where_track_addtocart === 'in_cart' ) do_addToCart_in_cart();
537		}
538	};
539
540	function compare_old_and_new_carts(){
541		
542		let old_cart_items = fpdata.woo.cart_old.items,
543			old_cart_item_keys = Object.keys( old_cart_items ),
544			new_cart_items = fpdata.woo.cart.items,
545			new_cart_item_keys = Object.keys( new_cart_items ),
546			removed = [],
547			removed_val = 0;
548			added = [],
549			added_val = 0;
550
551		old_cart_item_keys.forEach( old_item_key => {
552
553			let old_prod = fpdata.woo.cart_old.items[old_item_key];
554
555			if ( new_cart_item_keys.includes( old_item_key ) ) {
556				
557				let old_qty = old_cart_items[old_item_key].qty,
558					new_qty = new_cart_items[old_item_key].qty;
559
560				if ( old_qty > new_qty ) {
561					let qty_change = old_qty - new_qty;
562					removed.push( [old_prod, qty_change ] );
563					removed_val += old_prod.price * qty_change;
564				} else if ( old_qty < new_qty ) {
565					let qty_change = new_qty - old_qty;
566					added.push( [old_prod, qty_change ] );
567					added_val += old_prod.price * qty_change;
568				};
569
570			} else {
571				removed.push([old_prod, false]);
572				removed_val += old_prod.price * old_prod.qty;
573			};
574		} );
575
576		new_cart_item_keys.forEach( new_item_key => {
577			if ( ! old_cart_item_keys.includes( new_item_key ) ) {
578				let new_prod = fpdata.woo.cart.items[new_item_key];
579				added.push( [new_prod, false ] );
580				added_val += new_prod.price * new_prod.qty;
581			};
582		} );
583
584		if ( added.length > 0 ) {
585			added_val = Math.round( added_val * 100 ) / 100;
586			FP.doActions( 'woo_add_to_cart', { 'products' : added, 'value' : added_val } );
587		}
588
589		if ( removed.length > 0 ) {
590			removed_val = Math.round( removed_val * 100 ) / 100;
591			FP.doActions( 'woo_remove_from_cart', { 'products' : removed, 'value' : removed_val } );
592		}
593	};
594
595	// puts <span>cart_data</span> into fpdata.woo.cart. Must run on page load. We add timeout to wait for the cart contents to load
596	setTimeout( prepare_classic_cart, 300 );
597
598	// Bind event handler immediately (no timeout)
599	if ( jQuery ) jQuery(document).ready(function($) {
600		$('body').on('updated_cart_totals', function(){
601			prepare_classic_cart(true);
602			compare_old_and_new_carts();
603			update_cart_cookie();
604		});
605	});
606
607	function update_cart_cookie(){
608		// update or create an "fp_woo_cart" cookie, create it based on the value of fpdata.woo.cart.items - it should contain an object of elements, where the keys are the IDs of products and the values are their quantities
609		let cart_cookie_data = {};
610		for ( let prod_id in fpdata.woo.cart.items ) {
611			cart_cookie_data[prod_id] = fpdata.woo.cart.items[prod_id].qty;
612		}
613		FP.setCookie( 'fp_woo_cart', JSON.stringify(cart_cookie_data), 90 );
614	}
615
616	// This is used when add to cart events are to be sent only in cart
617	// It creates a "fp.woo.cart_to_track" object with all products that should be added to cart > this is later processed by tracking tools when they load
618	function do_addToCart_in_cart(){
619		
620		// check if there is an "fp_woo_cart" cookie with FP.readCookie() function
621		let fp_woo_cart_cookie = FP.readCookie('fp_woo_cart'),
622			added = [],
623			added_val = 0,
624			cart_to_track = {};
625		
626		// if there is a cookie
627		if ( fp_woo_cart_cookie ) {
628
629			let saved_cart = JSON.parse(fp_woo_cart_cookie);
630			
631			// compare ids and quantities of products currently in fpdata.woo.cart.items and those saved in a cookie
632			// get an array of all product IDs and quantities of products which are now in cart but were not in the cookie
633			for ( let prod_id in fpdata.woo.cart.items ) {
634				
635				let current_qty = fpdata.woo.cart.items[prod_id].qty;
636				let saved_qty = saved_cart[prod_id] || 0;
637				
638				if ( current_qty > saved_qty ) {
639					let qty_change = current_qty - saved_qty;
640					let prod = fpdata.woo.cart.items[prod_id];
641					added.push( [prod, qty_change] );
642					added_val += prod.price * qty_change;
643				}
644			}
645			
646			// Track added products
647			if ( added.length > 0 ) {
648				added_val = Math.round( added_val * 100 ) / 100;
649				cart_to_track = { 'products' : added, 'value' : added_val };
650			}
651		
652		// if there is no cookie
653		} else {
654			
655			// change object with cart items into an array
656			for ( let prod_id in fpdata.woo.cart.items ) {
657				let prod = fpdata.woo.cart.items[prod_id];
658				added.push( [prod, prod.qty] );
659			}
660			
661			cart_to_track = { 'products' : added, 'value' : fpdata.woo.cart.value };
662		}
663		
664		update_cart_cookie();
665		FP.doActions( 'woo_add_to_cart', cart_to_track );
666	}
667
668	// TRACK ADD TO WISHLIST
669
670	if ( fp.woo.wishlist_btn_sel ){
671
672		FP.addAction( ['click'], function(){
673
674			if ( ! FP.hasActions( 'woo_add_to_wishlist' ) ) return;
675
676			if ( FP.isClickTarget( fp.woo.wishlist_btn_sel + ', ' + fp.woo.wishlist_btn_sel + ' *' ) ) {
677
678				let product_el = fpdata.clicked.element.closest('.fupi_woo_product');
679				
680				if ( product_el ) {
681					let prod_id = product_el.dataset.fupi_woo_prod_id,
682						prod = fpdata.woo.products[prod_id];
683
684					if ( prod ) FP.doActions( 'woo_add_to_wishlist', { 'products' : [[prod, 1]], 'value' : prod.price } );
685				}
686			};
687		} );
688	};
689
690	// TRACK VIEWED VARIANTS AS PRODUCT VIEWS
691
692	if ( fp.woo.variable_tracking_method != 'track_parents' && fp.woo.track_variant_views ) {
693
694		jQuery(document).ready(function($) {
695			
696			// Listen for variation change events on the variations form
697			$( 'form.variations_form' ).on( 'woocommerce_variation_has_changed' , function() { // !! do NOT change into an arrow function
698				
699				let variant_id = get_current_variant_id();
700
701				if ( variant_id && fpdata.woo.products[variant_id] && FP.hasActions( 'woo_variant_view' ) ) {
702					FP.doActions( 'woo_variant_view', variant_id );
703				}
704			} )
705		} ) 
706	}
707
708	function update_fpdata_cart_items( id, new_qty ){
709		let prod = fpdata.woo.cart.items[id];
710		if ( prod ) {
711			if ( ! new_qty ) { // product removed
712				// make sure we have a backup of this prod in "products"
713				fpdata.woo.products[id] = structuredClone(prod);
714				delete fpdata.woo.cart.items[id];
715			} else if ( new_qty != prod.qty ) {
716				prod.qty = new_qty;
717			}
718		}
719	};
720
721	function add_block_hooks(){
722
723		// "All products" block (deprecated but still used on some sites)
724		if ( window.wp && window.wp.hooks ) window.wp.hooks.addAction(
725			"experimental__woocommerce_blocks-product-list-render",
726			"fupi-tracking",
727			( {products, listName } ) => {
728				if ( products.length > 0 && listName == 'woocommerce/all-products' ) {
729					fp.woo.products_from_all_products_block = products;
730				}
731			}
732		);
733
734		// Use wp.data to subscribe to product changes in the BIG cart block
735		if ( window.wp && window.wp.data && window.wp.data.select( 'wc/store/cart' ) ) {
736			
737			const { subscribe } = wp.data;
738			const cartStore = wp.data.select( 'wc/store/cart' );
739			
740			// Initialize with current cart items
741			// The selector is getCartData(), which returns an object containing 'items'
742			let previousCartItems = cartStore.getCartData().items;
743
744			subscribe( () => {
745				const newCartItems = cartStore.getCartData().items;
746
747				// If the array reference is the same, nothing changed
748				if ( newCartItems === previousCartItems ) return;
749
750				// Map items by key for easy comparison
751				const prevMap = new Map( previousCartItems.map( i => [ i.key, i ] ) );
752				const newMap = new Map( newCartItems.map( i => [ i.key, i ] ) );
753
754				// 1. Check for REMOVED items
755				for ( const [ key, prevItem ] of prevMap ) {
756					if ( ! newMap.has( key ) ) {
757						let prod = get_prod_from_store_item( prevItem );
758						let value = Math.round( prod.price * prevItem.quantity * 100 ) / 100;
759						// Trigger remove action
760						FP.doActions( 'woo_remove_from_cart', { 'products' : [[prod, false]], 'value' : value } );
761						if ( fpdata.page_type == 'Woo Cart' ) update_fpdata_cart_items(prod.id, false);
762					}
763				}
764
765				// 2. Check for QUANTITY changes
766				for ( const [ key, newItem ] of newMap ) {
767					const prevItem = prevMap.get( key );
768					
769					if ( prevItem && newItem.quantity !== prevItem.quantity ) {
770						
771						let prod = get_prod_from_store_item( newItem );
772						let qty_change = Math.abs( newItem.quantity - prevItem.quantity );
773						let value = Math.round( prod.price * qty_change * 100 ) / 100;
774
775						if ( newItem.quantity > prevItem.quantity ) {
776							// Quantity Increased
777							FP.doActions( 'woo_add_to_cart', { 'products' : [[prod, qty_change]], 'value': value } );
778							if ( fpdata.page_type == 'Woo Cart' ) update_fpdata_cart_items(prod.id, newItem.quantity);
779						} else {
780							// Quantity Decreased
781							FP.doActions( 'woo_remove_from_cart', { 'products' : [[prod, qty_change]], 'value' : value } );
782							if ( fpdata.page_type == 'Woo Cart' ) update_fpdata_cart_items(prod.id, newItem.quantity);
783						}
784					}
785				}
786
787				update_cart_cookie();
788				// Update previous items for next change
789				previousCartItems = newCartItems;
790			} );
791		}
792
793		// Helper function to normalize product data from the store item
794		function get_prod_from_store_item( item ) {
795			// Prefer data from fpdata if available (contains more info like categories)
796			if ( fpdata.woo.cart.items && fpdata.woo.cart.items[item.id] ) {
797				return { ...fpdata.woo.cart.items[item.id] };
798			}
799			
800			// Fallback to data available in the store item
801			return {
802				id: item.id,
803				name: item.name,
804				sku: item.sku,
805				price: parseInt( item.prices.price ) / 100, // Price is in minor units
806				qty: item.quantity
807			};
808		}
809	}
810
811	add_block_hooks();
812	
813	FP.loaded('woo'); // fp.loaded.push('woo'); is at the top
814};
815
816FP.load('woo', 'fupi_woo', ['footer_helpers']);

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.