1(()=>{function e(e,n){if(e){if("string"==typeof e)return t(e,n);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?t(e,n):void 0}}function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function n(e,t,n){if(n){var r=e.getElementsByClassName(t)[0];r&&(r.textContent=n)}}function r(t,r){for(var l=0,a=Object.entries(r);l<a.length;l++){var o=(i=a[l],u=2,function(e){if(Array.isArray(e))return e}(i)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,l,a,o,i=[],u=!0,s=!1;try{if(a=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;u=!1}else for(;!(u=(r=a.call(n)).done)&&(i.push(r.value),i.length!==t);u=!0);}catch(e){s=!0,l=e}finally{try{if(!u&&null!=n.return&&(o=n.return(),Object(o)!==o))return}finally{if(s)throw l}}return i}}(i,u)||e(i,u)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}());n(t,o[0],o[1])}var i,u}!function(){var t,n=function(t,n){var r="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!r){if(Array.isArray(t)||(r=e(t))){r&&(t=r);var l=0,a=function(){};return{s:a,n:function(){return l>=t.length?{done:!0}:{done:!1,value:t[l++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,u=!1;return{s:function(){r=r.call(t)},n:function(){var e=r.next();return i=e.done,e},e:function(e){u=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(u)throw o}}}}(document.getElementsByClassName("cm-contact-form"));try{for(n.s();!(t=n.n()).done;)t.value.onsubmit=function(e){var t,n,l,a,o,i,u,s;e.preventDefault();var m=e.target,c=null===(t=m.getElementsByClassName("firstName")[0])||void 0===t?void 0:t.value,f=null===(n=m.getElementsByClassName("lastName")[0])||void 0===n?void 0:n.value,v=null===(l=m.getElementsByClassName("email")[0])||void 0===l?void 0:l.value,d=null===(a=m.getElementsByClassName("telephone")[0])||void 0===a?void 0:a.value,y=null===(o=m.getElementsByClassName("consent_check")[0])||void 0===o?void 0:o.checked,b=(null===(i=m.getElementsByClassName("list_id")[0])||void 0===i?void 0:i.value)||null;jQuery.post(null===(u=ce4wp_form_submit_data)||void 0===u?void 0:u.url,{action:"ce4wp_form_submission",
1nonce:null===(s=ce4wp_form_submit_data)||void 0===s?void 0:s.nonce,first_name:c,last_name:f,email:v,telephone:d,consent:y,list_id:b}).done((function(){var e,t=m.parentElement,n=null===(e=t.getElementsByClassName("onSubmissionSetting")[0])||void 0===e?void 0:e.value;if("redirect"===n){var l,a=new RegExp("^(https?)://"),o=null===(l=t.getElementsByClassName("redirect")[0])||void 0===l?void 0:l.value;a.test(o)||(o="https://"+o),window.location.href=o}else m.style.visibility="hidden","summary"===n&&r(t,{firstNameSummary:c?c+" ":null,lastNameSummary:f,telephoneSummary:d,emailSummary:v}),t.getElementsByClassName("onSubmission")[0].style.display="block"}))}}catch(e){n.e(e)}finally{n.f()}}()})();; 2(()=>{const e=document.querySelectorAll(".coblocks-animate");if("IntersectionObserver"in window){const t=new IntersectionObserver((e=>{e.forEach((e=>{e.isIntersecting&&(e.target.classList.add(e.target.dataset.coblocksAnimation),t.unobserve(e.target))}))}),{threshold:[.15]});e.forEach((e=>{t.observe(e)}))}else e.forEach((e=>{e.classList.remove("coblocks-animate"),delete e.dataset.coblocksAnimation}))})();; 3var mailchimp,mailchimp_cart,mailchimp_billing_email,mailchimp_username_email,mailchimp_registration_email,mailchimp_submitted_email=false,mailchimpReady=function(a){/in/.test(document.readyState)?setTimeout(()=>{mailchimpReady(a)},9):a()};function mailchimpHandleBillingEmail(selector){try{if(!mailchimp_public_data.allowed_to_set_cookies)return;if(mailchimp_public_data.disable_carts)return;var subscribed=document.querySelector("#mailchimp_woocommerce_newsletter");if(!subscribed)subscribed=document.querySelector("#subscribe-to-newsletter");if(!selector)selector="#billing_email";var a=document.querySelector(selector);var b=void 0!==a?a.value:"";if(!mailchimp_cart.valueEmail(b)||mailchimp_submitted_email===b){return false}mailchimp_cart.setEmail(b);var c=mailchimp_public_data.ajax_url+"?action=mailchimp_set_user_by_email";var d=new XMLHttpRequest;d.open("POST",c,!0);d.onload=function(){var successful=d.status>=200&&d.status<400;var msg=successful?"mailchimp.handle_billing_email.request.success":"mailchimp.handle_billing_email.request.error";if(successful){mailchimp_submitted_email=b}console.log(msg,d.responseText)};d.onerror=function(){console.log("mailchimp.handle_billing_email.request.error",d.responseText)};d.setRequestHeader("Content-Type","application/x-www-form-urlencoded");d.setRequestHeader("Accept","application/json");d.send("email="+b+"&mc_language="+mailchimp_public_data.language+"&subscribed="+(subscribed&&subscribed.checked?"1":"0"));return true}catch(a){console.log("mailchimp.handle_billing_email.error",a);mailchimp_submitted_email=!1}}!function(){"use strict";function mailchimpCart(){this.email_types="input[type=email]";this.regex_email=/^([A-Za-z0-9_+\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;this.current_email=null;this.previous_email=null;this.expireUser=function(){this.current_email=null;if(!mailchimp_public_data.allowed_to_set_cookies)return;mailchimp.storage.expire("mailchimp.cart.current_email")};this.expireSaved=function(){if(!mailchimp_public_data.allowed_to_set_cookies)return;mailchimp.storage.expire("mailchimp.cart.items")};this.setEmail=function(a){if(!mailchimp_public_data.allowed_to_set_cookies)return;if(!this.valueEmail(a))return false;this.setPreviousEmail(this.getEmail());mailchimp.storage.set("mailchimp.cart.current_email",this.current_email=a)};this.getEmail=function(){if(!mailchimp_public_data.allowed_to_set_cookies)return;if(this.current_email)return this.current_email;var a=mailchimp.storage.get("mailchimp.cart.current_email",!1);if(!a||!this.valueEmail(a))return false;return this.current_email=a};this.setPreviousEmail=function(a){if(!mailchimp_public_data.allowed_to_set_cookies)return;if(!this.valueEmail(a))return false;mailchimp.storage.set("mailchimp.cart.previous_email",this.previous_email=a)};this.valueEmail=function(a){return this.regex_email.test(a)};return this}var g={extend:function(a,b){for(var c in b||{})b.hasOwnProperty(c)&&(a[c]=b[c]);return a},getQueryStringVars:function(){var a=window.location.search||"",b=[],c={};if(a=a.substr(1),a.length){b=a.split("&");for(var d in b){var e=b[d];if("string"==typeof e){var f=e.split("="),g=f[0],h=f[1];g.length&&("undefined"==typeof c[g]&&(c[g]=[]),c[g].push(h))}}}return c},unEscape:function(a){return decodeURIComponent(a)},escape:function(a){return encodeURIComponent(a)},createDate:function(a,b){a||(a=0);var c=new Date,d=b?c.getDate()-a:c.getDate()+a;return c.setDate(d),c},arrayUnique:function(a){for(var b=a.concat(),c=0;c<b.length;++c)for(var d=c+1;d<b.length;++d)b[c]===b[d]&&b.splice(d,1);return b},objectCombineUnique:function(a){for(var b=a[0],c=1;c<a.length;c++){var d=a[c];for(var e in d)b[e]=d[e]}return b}},h=function(a,b){var c=function(a,b,d){return 1===arguments.length?c.get(a):c.set(a,b,d)};return c.get=function(b,d){return a.cookie!==c._cacheString&&c._populateCache(),void 0==c._cache[b]?d:c._cache[b]},c.defaults={path:"/",secure:true,samesite:"strict"}
3,c.set=function(d,e,f){switch(f={path:f&&f.path||c.defaults.path,domain:f&&f.domain||c.defaults.domain,expires:f&&f.expires||c.defaults.expires,secure:f&&f.secure!==b?f.secure:c.defaults.secure,samesite:f&&f.samesite||c.defaults.samesite},e===b&&(f.expires=-1),typeof f.expires){case"number":f.expires=new Date((new Date).getTime()+1e3*f.expires);break;case"string":f.expires=new Date(f.expires)}return d=encodeURIComponent(d)+"="+(e+"").replace(/[^!#-+\--:<-\[\]-~]/g,encodeURIComponent),d+=f.path?";path="+f.path:"",d+=f.domain?";domain="+f.domain:"",d+=f.expires?";expires="+f.expires.toGMTString():"",d+=f.secure?";secure":"",d+=f.samesite?";samesite="+f.samesite:"",a.cookie=d,c},c.expire=function(a,d){return c.set(a,b,d)},c._populateCache=function(){c._cache={};try{c._cacheString=a.cookie;for(var d=c._cacheString.split("; "),e=0;e<d.length;e++){var f=d[e].indexOf("="),g=decodeURIComponent(d[e].substr(0,f)),f=decodeURIComponent(d[e].substr(f+1));c._cache[g]===b&&(c._cache[g]=f)}}catch(a){console.log(a)}},c.enabled=function(){var a="1"===c.set("cookies.js","1").get("cookies.js");return c.expire("cookies.js"),a}(),c}(document);mailchimp={storage:h,utils:g};mailchimp_cart=new mailchimpCart}();mailchimpReady(function(){if(!mailchimp_public_data.allowed_to_set_cookies)return;if(mailchimp_public_data.disable_carts)return;if(void 0===a){var a={site_url:document.location.origin,defaulted:!0,ajax_url:document.location.origin+"/wp-admin?admin-ajax.php"}}try{var b=mailchimp.utils.getQueryStringVars();var subscribed=document.querySelector("#mailchimp_woocommerce_newsletter");var blockSubscribed=document.querySelector("#subscribe-to-newsletter");if(subscribed){subscribed.onchange=function(){mailchimp_submitted_email=null;mailchimpHandleBillingEmail("#billing_email")}}else if(blockSubscribed){blockSubscribed.onchange=function(){mailchimp_submitted_email=null;mailchimpHandleBillingEmail('#contact-fields input[type="email"]')}}mailchimp_username_email=document.querySelector("#username");mailchimp_billing_email=document.querySelector("#billing_email");mailchimp_registration_email=document.querySelector("#reg_email");var mailchimp_username_email_block=document.querySelector('#contact-fields input[type="email"]');if(mailchimp_billing_email){mailchimp_billing_email.onblur=function(){mailchimpHandleBillingEmail("#billing_email")};mailchimp_billing_email.onfocus=function(){mailchimpHandleBillingEmail("#billing_email")}}if(mailchimp_username_email){mailchimp_username_email.onblur=function(){mailchimpHandleBillingEmail("#username")};mailchimp_username_email.onfocus=function(){mailchimpHandleBillingEmail("#username")}}if(mailchimp_registration_email){mailchimp_registration_email.onblur=function(){mailchimpHandleBillingEmail("#reg_email")};mailchimp_registration_email.onfocus=function(){mailchimpHandleBillingEmail("#reg_email")}}if(mailchimp_username_email_block){mailchimp_username_email_block.onblur=function(){mailchimpHandleBillingEmail('#contact-fields input[type="email"]')};mailchimp_username_email_block.onfocus=function(){mailchimpHandleBillingEmail('#contact-fields input[type="email"]')};var typingTimer;mailchimp_username_email_block.addEventListener("keyup",function(){typingTimer&&clearTimeout(typingTimer);typingTimer=setTimeout(function(){if(mailchimp_cart.valueEmail(mailchimp_username_email_block.value)){mailchimpHandleBillingEmail('#contact-fields input[type="email"]')}},2e3)});mailchimp_username_email_block.addEventListener("keydown",function(){typingTimer&&clearTimeout(typingTimer)})}}catch(e){console.log("mailchimp ready error",e)}});; 4/** 5 * Mailchimp Pixel Tracking for WooCommerce 6 * 7 * Listens for WooCommerce events and sends them to the Mailchimp Pixel SDK. 8 * Only tracks if window.$mcSite.pixel.api is available (MC.js loaded). 9 * 10 * @package MailChimp_WooCommerce 11 * @since 1.0.0 12 */ 13 14(function ($) { 15 'use strict'; 16 17 /** 18 * Default config for waitForPixelSDK exponential backoff 19 */ 20 const PIXEL_SDK_WAIT_CONFIG = { 21 initialDelayMs: 100, 22 maxDelayMs: 5000, 23 maxAttempts: 20 24 }; 25 26 /** 27 * How long PRODUCT_VIEWED waits on a variable product for WooCommerce to resolve a 28 * default or deep-linked variation before settling for the parent. 29 */ 30 const VARIATION_RESOLVE_WAIT_MS = 1200; 31 32 /** 33 * Settle time after a form interaction before re-reading the variation id input. 34 * The blocks form writes it through the Interactivity API on the next render. 35 */ 36 const VARIATION_INPUT_SETTLE_MS = 150; 37 38 /** 39 * Mailchimp Pixel Tracking Handler 40 */ 41 const MailchimpPixelTracking = { 42 // Debounce timers for cart fetch calls 43 _atcTimer: null, 44 _rfcTimer: null, 45 _atcFetching: false, 46 _rfcFetching: false, 47 // PRODUCT_VIEWED state for a variable product being resolved to a variation 48 _viewTimer: null, 49 _viewedParent: null, 50 _viewedKeys: null, 51 _inputTimer: null, 52 53 /** 54 * Initialize tracking. 55 * Waits for Pixel SDK with exponential backoff, then sends page events and attaches handlers. 56 */ 57 init: function () { 58 const self = this; 59 60 // Registered before the SDK wait on purpose: that wait runs for seconds, and an 61 // integration that resolves its variation early must not race us. 62 window.mcPixel = window.mcPixel || {}; 63 window.mcPixel.setVariation = function (variationId, product) { 64 self.setVariation(variationId, product); 65 }; 66 document.addEventListener('mailchimp_woocommerce_set_variation', function (event) { 67 const detail = event.detail || {}; 68 self.setVariation(detail.variationId, detail.product); 69 }); 70 71 this.waitForPixelSDK(PIXEL_SDK_WAIT_CONFIG) 72 .then(function () { 73 self.sendPageEvents(); 74 self.attachCartEventListeners(); 75 self.interceptStoreApiRequests(); 76 console.log('Mailchimp Pixel SDK loaded.'); 77 }) 78 .catch(function (e) { 79 console.log('Mailchimp Pixel SDK not loaded within timeout. Tracking disabled.', e); 80 }); 81 }, 82 83 /** 84 * Wait for Pixel SDK to become available using exponential backoff. 85 * 86 * @param {Object} options Optional config 87 * @param {number} options.initialDelayMs First delay in ms (default 100) 88 * @param {number} options.maxDelayMs Cap on delay in ms (default 5000) 89 * @param {number} options.maxAttempts Max number of attempts (default 20) 90 * @return {Promise<void>}
90 Resolves when SDK is available, rejects after maxAttempts 91 */ 92 waitForPixelSDK: function (options) { 93 const config = options || {}; 94 const initialDelayMs = config.initialDelayMs !== undefined ? config.initialDelayMs : PIXEL_SDK_WAIT_CONFIG.initialDelayMs; 95 const maxDelayMs = config.maxDelayMs !== undefined ? config.maxDelayMs : PIXEL_SDK_WAIT_CONFIG.maxDelayMs; 96 const maxAttempts = config.maxAttempts !== undefined ? config.maxAttempts : PIXEL_SDK_WAIT_CONFIG.maxAttempts; 97 98 function isSDKReady() { 99 return typeof window.$mcSite !== 'undefined' && 100 window.$mcSite.pixel && 101 typeof window.$mcSite.pixel.api !== 'undefined' && 102 typeof window.$mcSite.pixel.api.track === 'function' && 103 window.$mcSite.pixel.installed === true; 104 } 105 106 return new Promise(function (resolve, reject) { 107 let attempt = 0; 108 109 function scheduleCheck() { 110 if (isSDKReady()) { 111 // FIXME: temporary wait to accommodate for not being able to detect ready state 112 console.warn('Pixel SDK - remediation for pixel ready issue') 113 setTimeout(function () { 114 resolve(); 115 }, 1000); 116 return; 117 } 118 if (attempt >= maxAttempts) { 119 reject(new Error('Pixel SDK not available')); 120 return; 121 } 122 123 const delay = Math.min( 124 initialDelayMs * Math.pow(2, attempt), 125 maxDelayMs 126 ); 127 attempt += 1; 128 129 setTimeout(scheduleCheck, delay); 130 } 131 132 scheduleCheck(); 133 }); 134 }, 135 136 /** 137 * Synchronous check if Pixel SDK is available (e.g. before each track call). 138 * 139 * @return {boolean} True if SDK is available 140 */ 141 isPixelSDKReady: function () { 142 return typeof window.$mcSite !== 'undefined' && 143 window.$mcSite.pixel && 144 typeof window.$mcSite.pixel.api !== 'undefined' && 145 typeof window.$mcSite.pixel.api.track === 'function'; 146 }, 147 148 /** 149 * Get cart ID from window.mcPixel 150 * 151 * @return {string} Cart ID 152 */ 153 getCartId: function () { 154 return window.mcPixel && window.mcPixel.cartId ? window.mcPixel.cartId : ''; 155 }, 156 157 /** 158 * Get the REST API base URL from the localized config. 159 * 160 * @return {string} REST base URL 161 */ 162 getRestBase: function () { 163 return (window.mcPixelConfig && window.mcPixelConfig.restBase) || '/wp-json/mailchimp-for-woocommerce/v1/'; 164 }, 165 166 /** 167 * Send page-level events based on pre-populated data 168 */ 169 sendPageEvents: function () { 170 if (!window.mcPixel || !window.mcPixel.data) { 171 return; 172 } 173 174 const data = window.mcPixel.data; 175 const events = data.events || []; 176 177 // Send events based on what was set by PHP 178 events.forEach((eventType) => { 179 //console.log('Mailchimp Pixel: Sending event', eventType, data); 180 switch (eventType) { 181 case 'PRODUCT_ADDED_TO_CART': 182 if (data.added_to_cart) { 183 var atcItems = Array.isArray(data.added_to_cart) ? data.added_to_cart : [data.added_to_cart]; 184 for (var ai = 0; ai < atcItems.length; ai++) { 185 this.sendProductAddedToCart(atcItems[ai]); 186 } 187 } 188 break; 189 case 'PRODUCT_REMOVED_FROM_CART': 190 if (data.removed_from_cart) { 191 var rfcItems = Array.isArray(data.removed_from_cart) ? data.removed_from_cart : [data.removed_from_cart]; 192 for (var ri = 0; ri < rfcItems.length; ri++) { 193 this.sendProductRemovedFromCart(rfcItems[ri]); 194 } 195 } 196 break; 197 case 'IDENTITY': 198 if (data.identity && data.identity.email) { 199 this.sendIdentityEvent(data.identity.email); 200 } 201 break; 202 case 'PRODUCT_VIEWED': 203 // Skip if an add-to-cart or remove-from-cart already fired for the same product. 204 // The cart action implies the view, so firing both is redundant. 205 if (data.product) { 206 const viewedId = String(data.product.productId || data.product.id); 207 const atcArr = Array.isArray(data.added_to_cart) ? data.added_to_cart : (data.added_to_cart ? [data.added_to_cart] : []); 208 const rfcArr = Array.isArray(data.removed_from_cart) ? data.removed_from_cart : (data.removed_from_cart ? [data.removed_from_cart] : []); 209 const atcMatch = atcArr.some(function (p) { return String(p.productId || p.id
209) === viewedId; }); 210 const rfcMatch = rfcArr.some(function (p) { return String(p.productId || p.id) === viewedId; }); 211 212 if ( 213 (events.includes('PRODUCT_ADDED_TO_CART') && atcMatch) || 214 (events.includes('PRODUCT_REMOVED_FROM_CART') && rfcMatch) 215 ) { 216 //console.log('Mailchimp Pixel: Skipping PRODUCT_VIEWED (superseded by cart event for same product)'); 217 break; 218 } 219 this.trackProductViewed(data.product); 220 } 221 break; 222 case 'CART_VIEWED': 223 if (data.cart) { 224 this.sendCartViewed(data.cart); 225 } 226 break; 227 case 'CHECKOUT_STARTED': 228 if (data.checkout) { 229 this.sendCheckoutStarted(data.checkout); 230 window.mcPixel._handled.checkout = true; 231 } 232 break; 233 case 'PURCHASED': 234 if (data.order) { 235 this.sendPurchased(data.order); 236 } 237 break; 238 case 'PRODUCT_CATEGORY_VIEWED': 239 if (data.category) { 240 this.sendCategoryViewed(data.category); 241 window.mcPixel._handled.category = true; 242 } 243 break; 244 case 'SEARCH_SUBMITTED': 245 if (data.search) { 246 this.sendSearchSubmitted(data.search); 247 window.mcPixel._handled.search = true; 248 } 249 break; 250 } 251 }); 252 }, 253 254 /** 255 * PRODUCT_VIEWED for the single product page. 256 * 257 * A variable product renders before the customer has picked anything, so the 258 * server-side payload only knows the parent. WooCommerce announces the chosen 259 * variation on the .variations_form with found_variation, so we hold the event 260 * until that fires - or until it's clear nothing is going to resolve. 261 * 262 * @param {Object} product Parent product data from PHP 263 */ 264 trackProductViewed: function (product) { 265 const self = this; 266 this._viewedKeys = this._viewedKeys || {}; 267 268 // not a variable product - the server already knew everything there is to know 269 if (!window.mcPixel.data.product_variations) { 270 this.sendProductViewedOnce(product, product.id); 271 return; 272 } 273 274 this._viewedParent = product; 275 this.attachVariationListeners(); 276 277 const resolved = this.currentVariationId(); 278 if (resolved) { 279 this.sendProductViewedOnce(this.resolveVariationPayload({ variation_id: resolved }), resolved); 280 return; 281 } 282 283 // nothing picked yet, but a default variation or ?attribute_* deep link still 284 // resolves a tick after the form initialises - give WooCommerce that tick 285 // before settling for the parent 286 this._viewTimer = setTimeout(function () { 287 self.sendProductViewedOnce(product, product.id); 288 }, VARIATION_RESOLVE_WAIT_MS); 289 }, 290 291 /** 292 * One PRODUCT_VIEWED per thing actually viewed: the parent, then each variation 293 * the customer lands on. Switching back to an earlier pick sends nothing, so 294 * clicking through a dropdown doesn't turn into a burst of events. 295 * 296 * @param {Object} product Product data to send 297 * @param {string} key Dedup key - the variation id, or the parent id 298 */ 299 sendProductViewedOnce: function (product, key) { 300 clearTimeout(this._viewTimer); 301 if (this._viewedKeys[key]) { 302 return; 303 } 304 // only claim the key once it actually went out - an integration can call 305 // setVariation() before the SDK is up, and that must not count as sent 306 if (this.sendProductViewed(product)) { 307 this._viewedKeys[key] = true; 308 } 309 }, 310 311 /** 312 * Public API for themes, swatch plugins and custom templates. 313 * 314 * Detection covers what WooCommerce itself ships: the classic variations form, 315 * and the hidden variation_id input the block form maintains. Anything else - a 316 * custom swatch UI, a page builder widget, a headless front end - can announce 317 * the selection itself, either directly: 318 * 319 * window.mcPixel.setVariation( 123 ); 320 * 321 * or, when it cannot reach our global, by dispatching an event: 322 * 323 * document.dispatchEvent( new CustomEvent( 324 * 'mailchimp_woocommerce_set_variation', 325 * { detail: { variationId: 123 } } 326 * ) ); 327 * 328 * Pass a second argument to supply the whole product payload instead of letting 329 * us resolve it. Sending the same variation twice is a no-op either way. 330 * 331 * @param {number|string} variationId Variation being viewed 332 * @param {Object} [product] Optional product payload, sent as-is 333 */ 334 setVariation: function (variationId, product) { 335 const id = String(variationId || ''); 336 337 if (!id || id === '0') { 338 return; 339 } 340 // an integration may beat PRODUCT_VIEWED to it - nothing to dedup against yet 341 this._viewedKeys = this._viewedKeys || {}; 342 this.sendProductViewedOnce( 343 product || this.resolveVariationPayload({ variation_id: id }), 344 id 345 ); 346 }, 347 348 /** 349 * found_variation is WooCommerce's own signal that every attribute is chosen and 350 * a variation matched. Bound delegated off body so a form rendered late - blocks, 351 * lazy themes, quick-view modals - is still covered. 352 */ 353 attachVariationListeners: function () { 354 const self = this; 355 356 $(document.body).on('found_variation', '.variations_form', function (event, variation) { 357 if (!variation || !variation.variation_id) { 358 return; 359 } 360 self.sendProductViewedOnce( 361 self.resolveVariationPayload(variation), 362 String(variation.variation_id) 363 ); 364 }); 365 366 // covers the block form, which fires no event at all 367 this.watchVariationInput(); 368 }, 369 370 /** 371 * WooCommerce often resolves a variation before we boot - the SDK wait runs over a 372 * second, and the form fires found_variation during its own init. The form parks
373 * the answer in its variation_id input, so read that rather than assuming we were 374 * listening at the right moment. 375 * 376 * @return {string} Resolved variation id, or '' when nothing is selected 377 */ 378 currentVariationId: function () { 379 // Classic puts this in .variations_form, blocks puts it in .single_variation_wrap 380 // inside its own form element - so key off the input, not either wrapper. 381 const inputs = document.querySelectorAll('form input[name="variation_id"], form input.variation_id'); 382 383 for (let i = 0; i < inputs.length; i++) { 384 const id = parseInt(inputs[i].value, 10); 385 if (id > 0) { 386 return String(id); 387 } 388 } 389 390 return ''; 391 }, 392 393 /** 394 * Catch variation changes on templates that announce nothing. 395 * 396 * The blocks "Add to Cart with Options" form runs on the Interactivity API and 397 * fires no jQuery event and no CustomEvent, so found_variation never arrives. 398 * What it does keep is the hidden variation_id input - WooCommerce renders it 399 * specifically so "extensions or Express Payment methods" can read the form 400 * state - so re-read that whenever the customer touches the form. Its value is 401 * set as a property by the Interactivity API, which a MutationObserver would 402 * never see, hence driving off the interaction instead. 403 */ 404 watchVariationInput: function () { 405 const self = this; 406 407 const recheck = function (event) { 408 // blocks renders its attribute options as buttons, so change alone misses them 409 if (!event.target || !event.target.closest || !event.target.closest('form')) { 410 return; 411 } 412 clearTimeout(self._inputTimer); 413 self._inputTimer = setTimeout(function () { 414 const id = self.currentVariationId(); 415 if (id) { 416 self.sendProductViewedOnce(self.resolveVariationPayload({ variation_id: id }), id); 417 } 418 }, VARIATION_INPUT_SETTLE_MS); 419 }; 420 421 document.addEventListener('change', recheck, true); 422 document.addEventListener('click', recheck, true); 423 }, 424 425 /** 426 * Turn a resolved variation into the product shape the pixel expects. 427 * 428 * Prefer the server-rendered variation: it carries the sku, categories and 429 * parent-derived permalink that the event payload has no idea about, and it 430 * matches what add-to-cart sends for the same variation. Past the pre-render cap 431 * there is nothing to look up, so patch the parent with what the event does give 432 * us - the id being right matters more than the rest. 433 * 434 * @param {Object} variation found_variation payload (or just {variation_id}) 435 * @return {Object} Product data for the pixel 436 */ 437 resolveVariationPayload: function (variation) { 438 const parent = this._viewedParent || {}; 439 const id = String(variation.variation_id); 440 const rendered = window.mcPixel.data.product_variations[id]; 441 442 if (rendered) { 443 return rendered; 444 } 445 446 const patched = $.extend({}, parent, { 447 id: id, 448 productId: parent.productId || parent.id || id 449 }); 450 if (variation.sku) { 451 patched.sku = variation.sku; 452 } 453 if (variation.display_price !== undefined && variation.display_price !== null) { 454 patched.price = parseFloat(variation.display_price); 455 } 456 if (variation.image && variation.image.full_src) { 457 patched.imageUrl = variation.image.full_src; 458 } 459 460 return patched; 461 }, 462 463 /** 464 * Send PRODUCT_VIEWED event 465 * 466 * @param {Object} product Product data 467 */ 468 sendProductViewed: function (product) { 469 if (!this.isPixelSDKReady()) return false; 470 471 window.$mcSite.pixel.api.track('PRODUCT_VIEWED', { 472 product: product 473 }).catch((error) => { 474 console.error('Mailchimp Pixel: Error tracking PRODUCT_VIEWED', error); 475 }); 476 477 return true; 478 }, 479 480 /** 481 * Send PRODUCT_ADDED_TO_CART event 482 * 483 * @param {Object} product Product data 484 */ 485 sendProductAddedToCart: function (product) { 486 if (!this.isPixelSDKReady()) return; 487 488 const cartId = this.getCartId(); 489 const eventData = { 490 cartId: cartId, 491 product: { 492 item: { 493 id: product.id, 494 productId: product.productId, 495 title: product.title, 496 price: product.price, 497 currency: product.currency, 498 sku: product.sku || '' 499 }, 500 quantity: product.quantity || 1, 501 price: product.price * (product.quantity || 1), 502 currency: product.currency 503 } 504 }; 505 506 window.$mcSite.pixel.api.track('PRODUCT_ADDED_TO_CART', eventData).catch((error) => { 507 console.error('Mailchimp Pixel: Error tracking PRODUCT_ADDED_TO_CART', error); 508 }); 509 }, 510 511 /** 512 * Send PRODUCT_REMOVED_FROM_CART event 513 * 514 * @param {Object} product Product data 515 */ 516 sendProductRemovedFromCart: function (product) { 517 if (!this.isPixelSDKReady()) return; 518 519 const cartId = this.getCartId(); 520 const eventData = { 521 cartId: cartId, 522 product: { 523 item: { 524 id: product.id, 525 productId: product.productId, 526 title: product.title, 527 price: product.price, 528 currency: product.currency, 529 sku: product.sku || '' 530 }, 531 quantity: product.quantity || 1, 532 price: product.price * (product.quantity || 1), 533 currency: product.currency 534 } 535 }; 536 537 window.$mcSite.pixel.api.track('PRODUCT_REMOVED_FROM_CART', eventData).catch((error) => { 538 console.error('Mailchimp Pixel: Error tracking PRODUCT_REMOVED_FROM_CART', error); 539 }); 540 }, 541 542 /** 543 * Send CART_VIEWED event 544 * 545 * @param {Object} cart Cart data 546 */ 547 sendCartViewed: function (cart) { 548 if (!this.isPixelSDKReady()) return; 549 550 window.$mcSite.pixel.api.track('CART_VIEWED', { 551 cart: cart 552 }).catch((error) => { 553 console.error('Mailchimp Pixel: Error tracking CART_VIEWED', error); 554 }); 555 }, 556 557 /** 558 * Send CHECKOUT_STARTED event 559 * 560 * @param {Object} checkout Checkout data 561 */ 562 sendCheckoutStarted: function (checkout) { 563 if (!this.isPixelSDKReady()) return; 564 565 window.$mcSite.pixel.api.track('CHECKOUT_STARTED', { 566 checkout: checkout 567 }).catch((error) => { 568 console.error('Mailchimp Pixel: Error tracking CHECKOUT_STARTED', error); 569 }); 570 }, 571 572 sendIdentityEvent: function(email) { 573 if (!this.isPixelSDKReady()) return; 574 window.$mcSite.pixel.api.identify({ 575 type: 'EMAIL', 576 value: email 577 }); 578 }, 579 580 /** 581 * Send PURCHASED event 582 * 583 * @param {Object} order Order data 584 */ 585 sendPurchased: function (order) { 586 if (!this.isPixelSDKReady()) return; 587 588 window.$mcSite.pixel.api.track('PURCHASED', { 589 order: order 590 }).catch((error) => { 591 console.error('Mailchimp Pixel: Error tracking PURCHASED', error); 592 }); 593 }, 594 595 /** 596 * Send PRODUCT_CATEGORY_VIEWED event 597 * 598 * @param {Object} category Category data 599 */ 600 sendCategoryViewed: function (category) { 601 if (!this.isPixelSDKReady()) return; 602 603 window.$mcSite.pixel.api.track('PRODUCT_CATEGORY_VIEWED', category).catch((error) => { 604 console.error('Mailchimp Pixel: Error tracking PRODUCT_CATEGORY_VIEWED', error); 605 }); 606 }, 607 608 /** 609 * Send SEARCH_SUBMITTED event 610 * 611 * @param {Object} search Search data 612 */ 613 sendSearchSubmitted: function (search) { 614 if (!this.isPixelSDKReady()) return; 615 616 window.$mcSite.pixel.api.track('SEARCH_SUBMITTED', search).catch((error) => { 617 console.error('Mailchimp Pixel: Error tracking SEARCH_SUBMITTED', error); 618 }); 619 }, 620 621 /** 622 * Find product by ID from pre-loaded products 623 * 624 * @param {number|string} productId Product ID 625 * @return {Object|null} Product data or null 626 */ 627 findProductById: function (productId) { 628 if (!window.mcPixel || !window.mcPixel.data || !window.mcPixel.data.products) { 629 return null; 630 } 631 632 const products = window.mcPixel.data.products; 633 const id = String(productId); 634 635 for (let i = 0; i < products.length; i++) { 636 if (String(products[i].id) === id) { 637 return products[i]; 638 } 639 } 640 641 return null; 642 }, 643 644 /** 645 * Debounced fetch for add-to-cart events. 646 * Each call resets the timer so rapid-fire triggers coalesce into one 647 * fetch that drains the entire PHP queue. 648 * If a fetch is already in flight, schedule another drain after it completes. 649 */ 650 fetchAndTrackAddToCart: function () { 651 var self = this; 652 if (!this.isPixelSDKReady()) return; 653 654 clearTimeout(this._atcTimer); 655 this._atcTimer = setTimeout(function () { 656 self._drainAddToCartQueue(); 657 }, 600); 658 }, 659 660 _drainAddToCartQueue: async function () { 661 if (this._atcFetching) return; 662 this._atcFetching = true; 663 664 try { 665 var res = await fetch(this.getRestBase() + 'pixel/atc', { 666 method: 'GET', 667 credentials: 'same-origin', 668 headers: { 'Accept': 'application/json' }, 669 }); 670 671 if (!res.ok) return; 672 673 var data = await res.json(); 674 if (!data) return; 675 676 var items = Array.isArray(data) ? data : [data]; 677 for (var i = 0; i < items.length; i++) { 678 this.sendProductAddedToCart(items[i]); 679 } 680 } catch (e) { 681 // no-op 682 } finally { 683 this._atcFetching = false;
684 } 685 }, 686 687 /** 688 * Debounced fetch for remove-from-cart events. 689 * Same debounce pattern as add-to-cart. 690 */ 691 fetchAndTrackRemoveFromCart: function () { 692 var self = this; 693 if (!this.isPixelSDKReady()) return; 694 695 clearTimeout(this._rfcTimer); 696 this._rfcTimer = setTimeout(function () { 697 self._drainRemoveFromCartQueue(); 698 }, 600); 699 }, 700 701 _drainRemoveFromCartQueue: async function () { 702 if (this._rfcFetching) return; 703 this._rfcFetching = true; 704 705 try { 706 var res = await fetch(this.getRestBase() + 'pixel/rfc', { 707 method: 'GET', 708 credentials: 'same-origin', 709 headers: { 'Accept': 'application/json' }, 710 }); 711 712 if (!res.ok) return; 713 714 var data = await res.json(); 715 if (!data) return; 716 717 var items = Array.isArray(data) ? data : [data]; 718 for (var i = 0; i < items.length; i++) { 719 this.sendProductRemovedFromCart(items[i]); 720 } 721 } catch (e) { 722 // no-op 723 } finally { 724 this._rfcFetching = false; 725 } 726 }, 727 728 /** 729 * Attach DOM and jQuery event listeners for cart add/remove. 730 * These serve as fallback triggers alongside the fetch interceptor. 731 */ 732 attachCartEventListeners: function () { 733 const self = this; 734 735 // Classic themes (jQuery events fired by WooCommerce's add-to-cart.js) 736 $(document.body).on('added_to_cart', function () { 737 self.fetchAndTrackAddToCart(); 738 }); 739 $(document.body).on('removed_from_cart', function () { 740 self.fetchAndTrackRemoveFromCart(); 741 }); 742 743 // WC Blocks DOM CustomEvents 744 document.body.addEventListener('wc-blocks_added_to_cart', function () { 745 self.fetchAndTrackAddToCart(); 746 }); 747 document.body.addEventListener('wc-blocks_removed_from_cart', function () { 748 self.fetchAndTrackRemoveFromCart(); 749 }); 750 }, 751 752 /** 753 * Intercept window.fetch to detect WooCommerce Store API cart mutations. 754 * 755 * This is the most reliable method for catching add-to-cart and remove-from-cart 756 * in block-based WooCommerce setups, where DOM events may not fire consistently. 757 * Watches for successful POST requests to the Store API cart endpoints. 758 */ 759 interceptStoreApiRequests: function () { 760 const self = this; 761 const originalFetch = window.fetch; 762 763 window.fetch = function (input, init) { 764 var promise = originalFetch.apply(this, arguments); 765 766 promise.then(function (response) { 767 try { 768 if (!response.ok) return; 769 770 var method = (init && init.method) ? init.method.toUpperCase() : 771 (input instanceof Request ? input.method.toUpperCase() : 'GET'); 772 if (method !== 'POST') return; 773 774 var url = typeof input === 'string' ? input : 775 (input instanceof Request ? input.url : String(input)); 776 777 if (/wc\/store\/v1\/cart\/add-item/.test(url) || /wc\/store\/v1\/cart\/update-item/.test(url)) { 778 self.fetchAndTrackAddToCart(); 779 } else if (/wc\/store\/v1\/cart\/remove-item/.test(url)) { 780 self.fetchAndTrackRemoveFromCart(); 781 } 782 } catch (e) { 783 // Silently ignore interceptor errors 784 } 785 }).catch(function () { 786 // Ignore - the original caller handles fetch errors 787 }); 788 789 return promise; 790 }; 791 }, 792 }; 793 794 // Initialize when DOM is ready 795 $(document).ready(function () { 796 MailchimpPixelTracking.init(); 797 }); 798 799})(jQuery); 800;
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.