1/* global pysOptions */ 2 3! function ($, options) { 4 if (options.debug) { 5 console.log('PYS:', options); 6 } 7 8 var uniqueId = {}; 9 10 // State tracking for AND trigger logic: { eventId: { triggered: {}, total: N, fired: false } } 11 var pysCustomEventsState = {}; 12 13 // State tracking for "once per page" fire frequency: { eventId: true } 14 var pysEventFiredOnce = {}; 15 if (options.triggerLogic) { 16 Object.entries(options.triggerLogic).forEach(function([eventId, logic]) { 17 if (logic === 'AND') { 18 var count = 0; 19 Object.values(options.triggerEventTypes).forEach(function(events) { 20 if (events.hasOwnProperty(eventId)) count++; 21 }); 22 pysCustomEventsState[eventId] = { triggered: {}, total: count, fired: false }; 23 } 24 }); 25 } 26 27 var firstVisit = false; 28 29 var isTrackEventForGA = []; 30 31 var isAdsLoad = false; 32 33 let gtm_variables = {}; 34 let gtm_datalayername = "dynamicVariable"; 35 // Resolved by cookieDomain() on first use, not here. 36 var probedCookieDomain = null; 37 38 /** 39 * Domain attribute for cookies we write. Probed once, on first use (not 40 * at load) so the probe's own write lands behind whatever consent gate 41 * the caller is behind. 42 * 43 * Not suppressed by options.cookie.disabled_all_cookie: deleting a 44 * cookie needs the same domain it was written on, so suppressing here 45 * would leave parent-scoped cookies in place while appearing removed. 46 * 47 * Memoised only after the probe runs, so a disabled_all_cookie or option 48 * arriving later over REST is still honored. 49 */ 50 function cookieDomain() { 51 if (probedCookieDomain !== null) { 52 return probedCookieDomain; 53 } 54 if (!options || !options.hasOwnProperty("track_cookie_for_subdomains") 55 || !options.track_cookie_for_subdomains) { 56 return ''; 57 } 58 probedCookieDomain = probeCookieDomain(); 59 return probedCookieDomain; 60 } 61 62 /** 63 * Find the broadest domain this browser will actually accept a cookie 64 * on, walking from the broadest candidate inward. 65 * 66 * We used to rely on tldjs.getDomain(), but its bundled Public Suffix 67 * List predates ~2018 and returns the public suffix itself for newer 68 * domains (vercel.app, pages.dev, wpcomstaging.com), so cookies scoped 69 * there were silently dropped. Asking the browser instead keeps us in 70 * sync with its own current PSL. 71 * 72 * The probe cookie is written and removed synchronously, carries no 73 * data, and never outlives this call. If a consent tool blocks the 74 * write, cookieDomainAccepted() reports it rejected and we fall through 75 * to host-only â the correct outcome. 76 * 77 * Returns '' when nothing works (IP/single-label host, or all writes 78 * blocked); host-only is never rejected, so it's the safe floor. 79 */ 80 function probeCookieDomain() { 81 var host = ''; 82 try { host = String(location.hostname || ''); } catch (e) { return ''; } 83 84 // No Domain attribute is valid for an IP literal or a dotless host. 85 if (!host || host.indexOf('.') === -1) return ''; 86 if (host.indexOf(':') !== -1) return ''; // IPv6 literal 87 if (/^[\d.]+$/.test(host)) return ''; // IPv4 literal 88 89 var labels = host.split('.'); 90 // Broadest first: last two labels, then three, ... up to the full host. 91 for (var take = 2; take <= labels.length; take++) { 92 var candidate = '.' + labels.slice(labels.length - take).join('.'); 93 if (cookieDomainAccepted(candidate)) return candidate; 94 } 95 return ''; 96 } 97 98 /** 99 * Write a throwaway cookie at $candidate and report whether it stuck. 100 * A false negative is possible if the jar is at its per-domain limit; 101 * we then fall through to a narrower candidate and ultimately host-only. 102 */ 103 function cookieDomainAccepted(candidate) { 104 var name = 'pys_dt' + Math.floor(Math.random() * 1e9); 105 var accepted = false; 106 try { 107 document.cookie = name + '=1; path=/; domain=' + candidate + '; SameSite=Lax'; 108 accepted = document.cookie.indexOf(name + '=1') !== -1; 109 } catch (e) { 110 return false; 111 } finally { 112 // Remove either way; deleting an unset cookie is a no-op. 113 try { 114 document.cookie = name + '=; path=/; domain=' + candidate + '; max-age=0'; 115 } catch (e) {} 116 } 117 return accepted; 118 } 119 120 /** 121 * Hand PHP the cookie domain that probeCookieDomain() verified, since 122 * PHP has no Public Suffix List and guesses wrong on hosts like 123 * shop.example.co.uk or mysite.github.io â scoping its cookies to a 124 * public suffix the browser then drops. 125 * 126 * Value is the site's registrable domain (no PII, same for every 127 * visitor); PHP validates it against the request host before use (see 128 * pys_cookie_domain_resolve). 129 * 130 * Written from manageCookies() under the same consent gates as every 131 * other cookie, not unconditionally. 132 */ 133 function reportCookieDomain(resolved) { 134 if (!resolved) return; 135 var bare = String(resolved).replace(/^\./, ''); 136 if (!bare || bare.indexOf('.') === -1) return; 137 try { 138 if (Cookies.get('pys_cd') === bare) return; // already reported 139 Cookies.set('pys_cd', bare, { 140 expires: 365, 141 path: '/', 142 domain: resolved, 143 sameSite: 'Lax' 144 }); 145 } catch (e) {} 146 } 147 148 // Per-page referrer for Facebook CAPI events. Captured once per script 149 // load so SPA-style navigations within the same HTML document do not 150 // overwrite it. Internal/external is NOT filtered â Meta expects this 151 // field to describe the URL of the previous page for THIS event. 152 var pageReferrer = ''; 153 try { 154 pageReferrer = stripUrlQuery((document.referrer || '').toString()); 155 } catch (e) {} 156 157 // Session-entry referrer (fallback). Captured at session start the first 158 // time the visitor lands from an EXTERNAL site, mirrored to a session 159 // cookie so PHP can read it for orphan events (async/cron) that arrive 160 // without a per-page payload. Never the primary source.
161 var PYS_SESSION_ENTRY_REFERRER_KEY = 'pys_session_entry_referrer'; 162 function stripUrlQuery(url) { 163 if (!url) return ''; 164 var clean = String(url); 165 var q = clean.indexOf('?'); 166 if (q !== -1) clean = clean.substring(0, q); 167 var h = clean.indexOf('#'); 168 if (h !== -1) clean = clean.substring(0, h); 169 return clean; 170 } 171 var sessionEntryReferrer = ''; 172 function getOrInitSessionEntryReferrer(consentGranted) { 173 var value = ''; 174 try { 175 if (typeof window.sessionStorage !== 'undefined') { 176 value = window.sessionStorage.getItem(PYS_SESSION_ENTRY_REFERRER_KEY) || ''; 177 } 178 } catch (e) {} 179 180 if (!value) { 181 var cookieValue = Cookies.get(PYS_SESSION_ENTRY_REFERRER_KEY); 182 if (cookieValue && cookieValue !== 'undefined') { 183 value = cookieValue; 184 try { 185 if (typeof window.sessionStorage !== 'undefined') { 186 window.sessionStorage.setItem(PYS_SESSION_ENTRY_REFERRER_KEY, value); 187 } 188 } catch (e) {} 189 } 190 } 191 192 if (!value) { 193 var raw = ''; 194 try { 195 raw = stripUrlQuery((document.referrer || '').toString()); 196 } catch (e) {} 197 if (raw) { 198 var refHost = ''; 199 try { 200 refHost = new URL(raw).hostname; 201 } catch (e) {} 202 if (refHost && refHost !== window.location.hostname) { 203 value = raw; 204 try { 205 if (typeof window.sessionStorage !== 'undefined') { 206 window.sessionStorage.setItem(PYS_SESSION_ENTRY_REFERRER_KEY, value); 207 } 208 } catch (e) {} 209 } 210 } 211 } 212 213 // Mirror to a session cookie only once consent is given. The cookie 214 // is session-scoped (no expires) and dies with the browser, just 215 // like the sessionStorage entry. 216 if (value && consentGranted && options && options.cookie 217 && !options.cookie.disabled_all_cookie) { 218 var existingCookie = Cookies.get(PYS_SESSION_ENTRY_REFERRER_KEY); 219 if (!existingCookie || existingCookie === 'undefined') { 220 Cookies.set(PYS_SESSION_ENTRY_REFERRER_KEY, value, { path: '/', domain: cookieDomain() }); 221 } 222 } 223 224 sessionEntryReferrer = value; 225 return value; 226 } 227 228 // Populate sessionStorage / sessionEntryReferrer immediately. Cookie 229 // mirroring is deferred to the consent-gated path in Facebook.loadPixel(). 230 getOrInitSessionEntryReferrer(false); 231 232 // Per-page event referrer (session cookie). Snapshot of document.referrer 233 // for the CURRENT page, refreshed on every page load. Read server-side 234 // by saveFbTagsInOrder() to capture the buyer's checkout-page referrer 235 // for orphan CAPI events (APT, gateway webhooks, manual status changes). 236 // Internal URLs are intentionally NOT filtered â the goal is the page 237 // that led the buyer to checkout, which is usually internal. 238 // If document.referrer is empty (direct navigation), the previous value 239 // is preserved so a session that began with an external referrer keeps 240 // it across same-tab navigations. 241 var PYS_EVENT_REFERRER_KEY = 'pys_event_referrer'; 242 function writeEventReferrerCookie() { 243 if (!pageReferrer) { 244 return; 245 } 246 if (!options || !options.cookie || options.cookie.disabled_all_cookie) { 247 return; 248 } 249 try { 250 Cookies.set(PYS_EVENT_REFERRER_KEY, pageReferrer, { path: '/', domain: cookieDomain() }); 251 } catch (e) {} 252 } 253 254 /** 255 * Resolve parameter value based on mode (static or dynamic) 256 * 257 * @param {Object|string} param - Parameter object with { value, selector } or string value 258 * @returns {string|null} - Resolved value 259 */ 260 function resolveParamValue(param, key = '') { 261 262 // 1ï¸â£ null / undefined - skip this parameter 263 if (param === null || param === undefined) { 264 return null; 265 } 266 267 // 2ï¸â£ primitive 268 if (typeof param !== 'object') { 269 return param; 270 } 271 272 // 3ï¸â£ array 273 if (Array.isArray(param)) { 274 return param.map(item => resolveParamValue(item, key)); 275 } 276 277 // 4ï¸â£ NEW FORMAT OBJECT (config-object) 278 const isConfigObject = 279 ('value' in param || 'selector' in param || 'dynamic' in param || 'input_type' in param) && 280 Object.keys(param).every(k => 281 ['value','selector','dynamic','input_type','name'].includes(k) 282 ); 283 284 if (isConfigObject) { 285 286 // STATIC MODE 287 const isStatic = 288 !param.dynamic || 289 !param.selector || 290 param.selector.trim() === ''; 291 292 if (isStatic) { 293 let value = param.value ?? null; 294 295 if (param.input_type === "float" || param.input_type === "int") { 296 value = extractNumericValue( 297 value, 298 param.input_type === "int" 299 ); 300 } 301 302 return value; 303 } 304 305 // DYNAMIC MODE 306 try { 307 const el = document.querySelector(param.selector); 308 if (!el) return null; 309 310 let value = 311 el.value ?? 312 el.innerText ?? 313 el.textContent ?? 314 el.getAttribute("content") ?? 315 el.getAttribute("data-value") ?? 316 null; 317 318 if (param.input_type === "float" || param.input_type === "int") { 319 value = extractNumericValue( 320 value, 321 param.input_type === "int" 322 ); 323 } 324 325 return value; 326 327 } catch { 328 return null; 329 } 330 } 331 332 // 5ï¸â£ Plain object (recursive traversal) - filter out null/undefined values 333 return Object.fromEntries( 334 Object.entries(param) 335 .map(([k, v]) => [k, resolveParamValue(v, k)]) 336 .filter(([k, v]) => v !== null && v !== undefined) 337 ); 338 } 339 340 function extractNumericValue(value, isInt=false) { 341 if (value === null || value === undefined) { 342 return null; 343 } 344 if (isInt) { 345 return parseInt(value); 346 } 347 return parseFloat(value); 348 } 349 350 var loadTags = []; 351 352 var dummyPinterest = function () { 353 354 /** 355 * Public API 356 */ 357 return { 358 tag: function() { 359 return "pinterest"; 360 }, 361 isEnabled: function () {}, 362 363 disable: function () {}, 364 365 loadPixel: function () {}, 366 367 fireEvent: function (name, data) { 368 return false; 369 }, 370 371 onAdSenseEvent: function (event) {}, 372 373 onClickEvent: function (params) {}, 374 375 onWatchVideo: function (params) {}, 376 377 onCommentEvent: function (event) {}, 378 379 onFormEvent: function (params) {}, 380 381 onDownloadEvent: function (params) {}, 382 383 384 385 onWooAddToCartOnButtonEvent: function (product_id) {}, 386 387 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form) {}, 388 389 onWooRemoveFromCartEvent: function (cart_item_hash) {}, 390 391 onWooAffiliateEvent: function (product_id) {}, 392 393 onWooPayPalEvent: function (event) {}, 394 395 onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {}, 396 397 onEddRemoveFromCartEvent: function (item) {}, 398 399 onPageScroll: function (event) {}, 400 onTime: function (event) { 401 402 }, 403 404 } 405 406 }(); 407 408 var dummyBing = function () { 409 410 /** 411 * Public API 412 */ 413 return { 414 tag: function() { 415 return "bing"; 416 }, 417 isEnabled: function () {}, 418 419 disable: function () {}, 420 421 loadPixel: function () {}, 422 423 fireEvent: function (name, data) { 424 return false; 425 }, 426 427 onAdSenseEvent: function (event) {}, 428 429 onClickEvent: function (params) {}, 430 431 onWatchVideo: function (params) {}, 432 433 onCommentEvent: function (event) {}, 434 435 onFormEvent: function (params) {}, 436 437 onDownloadEvent: function (params) {}, 438 439 440 onWooAddToCartOnButtonEvent: function (product_id) {}, 441 442 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form) {}, 443 444 onWooRemoveFromCartEvent: function (cart_item_hash) {}, 445 446 onWooAffiliateEvent: function (product_id) {}, 447 448 onWooPayPalEvent: function (event) {}, 449 450 onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {}, 451 452 onEddRemoveFromCartEvent: function (item) {}, 453 onPageScroll: function (event) {}, 454 onTime: function (event) { 455 456 }, 457 } 458 459 }(); 460 461 var dummyReddit = function () { 462 /** 463 * Public API 464 */ 465 return { 466 tag: function () { 467 return "reddit"; 468 }, 469 isEnabled: function () { 470 }, 471 472 disable: function () { 473 }, 474 475 loadPixel: function () { 476 }, 477 478 fireEvent: function ( name, data ) { 479 return false; 480 }, 481 482 onAdSenseEvent: function ( event ) { 483 }, 484 485 onClickEvent: function ( params ) { 486 }, 487 488 onWatchVideo: function ( params ) { 489 }, 490 491 onCommentEvent: function ( event ) { 492 }, 493 494 onFormEvent: function ( params ) { 495 }, 496 497 onDownloadEvent: function ( params ) { 498 }, 499 500 onWooAddToCartOnButtonEvent: function ( product_id ) { 501 }, 502 503 onWooAddToCartOnSingleEvent: function ( product_id, qty, product_type, is_external, $form ) { 504 }, 505 506 onWooRemoveFromCartEvent: function ( cart_item_hash ) { 507 }, 508 509 onWooAffiliateEvent: function ( product_id ) { 510 }, 511 512 onWooPayPalEvent: function ( event ) { 513 }, 514 515 onEddAddToCartOnButtonEvent: function ( download_id, price_index, qty ) { 516 }, 517 518 onEddRemoveFromCartEvent: function ( item ) { 519 }, 520 521 onPageScroll: function ( event ) { 522 }, 523 524 onTime: function ( event ) { 525 }, 526 } 527 }(); 528 529 var Utils = function (options) { 530 531 532 var Pinterest = dummyPinterest; 533 534 var Bing = dummyBing; 535 536 var Reddit = dummyReddit; 537 538 var gtag_loaded = false; 539 540 var gtm_loaded = false; 541 542 // The GTM container's consent state is sent once per page load. Two code 543 // paths reach it (loadGTMScript() and the gtm_just_data_layer branch of 544 // GTM.loadPixel(), which can both run for the same page), and Consent Mode 545 // expects a single transition, not a stream of them. 546 var gtm_consent_state_pushed = false; 547 548 let isNewSession = checkSession(); 549 550 // In-flight pys_get_pbid request. Several code paths call manageCookies() 551 // before the first reply lands; one request is enough for all of them. 552 var pbidRequest = null; 553 // True once a pixel has waited for that reply (or given up on it), so 554 // the hold in holdForIdentity() happens at most once per page. 555 var pbidSettled = false; 556 var PBID_WAIT_MS = 1000; 557 558 let dataLayerName = 'dataLayerPYS'; 559 560 let GTMdataLayerName = 'dataLayer'; 561 562 563 function loadPixels() { 564 565 Utils.refreshAddonPixels(); 566 567 if (!options.gdpr.all_disabled_by_api) { 568 if (!options.gdpr.tiktok_disabled_by_api) { 569 TikTok.loadPixel(); 570 } 571 572 if (!options.gdpr.openai_disabled_by_api) { 573 OpenAI.loadPixel(); 574 } 575 576 if (!options.gdpr.facebook_disabled_by_api) { 577 Facebook.loadPixel(); 578 } 579 580 if (!options.gdpr.analytics_disabled_by_api) { 581 Analytics.loadPixel(); 582 } 583 584 if (!options.gdpr.google_ads_disabled_by_api) { 585 GAds.loadPixel(); 586 } 587 588 if (!options.gdpr.analytics_disabled_by_api) { 589 GTM.loadPixel(); 590 } 591 592 if (!options.gdpr.pinterest_disabled_by_api) { 593 Pinterest.loadPixel(); 594 } 595 596 if (!options.gdpr.bing_disabled_by_api) { 597 Bing.loadPixel(); 598 } 599 600 if (!options.gdpr.reddit_disabled_by_api) { 601 Reddit.loadPixel(); 602 } 603 604 } 605 if (options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined") { 606 if (typeof CS_Data.cs_google_analytics_consent_mode !== "undefined" && CS_Data.cs_google_analytics_consent_mode == 1) { 607 Analytics.loadPixel(); 608 } 609 if (typeof CS_Data.cs_google_ads_consent_mode !== "undefined" && CS_Data.cs_google_ads_consent_mode == 1 ) { 610 GAds.loadPixel(); 611 } 612 } 613 } 614 615 /** 616 * WATCHVIDEO UTILS 617 */ 618 619 function isJSApiAttrEnabled(url) { 620 return url.indexOf('enablejsapi') > -1; 621 } 622 623 function isOriginAttrEnabled(url) { 624 return url.indexOf('origin') > -1; 625 } 626 627 // Returns key/value pairs of percentages: number of seconds to achieve 628 function getVideoCompletionMarks(duration) { 629 630 var marks = {}; 631 var points = [0, 10, 50, 90, 100]; 632 633 for (var i = 0; i < points.length; i++) { 634 635 var _point = points[i]; 636 var _mark = _point + '%'; 637 var _time = duration * _point / 100; 638 639 if (_point === 100) { 640 _time = _time - 1; 641 } 642 643 // 10% => 123 644 marks[_mark] = Math.floor(_time); 645 646 } 647 648 return marks; 649 650 } 651 652 // Determine if the element is a YouTube video or not 653 function tagIsYouTubeVideo(tag) { 654 var src = tag.src || ''; 655 return src.indexOf('youtube.com/embed/') > -1 || src.indexOf('youtube.com/v/') > -1; 656 } 657 658 function tagIsYouTubeAsyncVideo(tag) { 659 if(tag.src && tag.src.indexOf("data:image") !== -1) return false; // video is loaded 660 var keys = Object.keys(tag.dataset); 661 for(var i = 0;i<keys.length;i++) { 662 if(keys[i].toLowerCase().indexOf("src") > -1) { 663 var src = tag.dataset[keys[i]]; 664 return src.indexOf('youtube.com/embed/') > -1 || src.indexOf('youtube.com/v/') > -1; 665 } 666 } 667 return false; //not find src 668 } 669 670 // Turn embed objects into iframe objects and ensure they have the right parameters 671 function normalizeYouTubeIframe(tag) { 672 673 var loc = window.location; 674 var a = document.createElement('a'); 675 a.href = tag.src; 676 a.hostname = 'www.youtube.com'; 677 a.protocol = loc.protocol; 678 var tmpPathname = a.pathname.charAt(
6780) === '/' ? a.pathname : '/' + a.pathname; // IE10 shim 679 680 if (!isJSApiAttrEnabled(a.search)) { 681 a.search = (a.search.length > 0 ? a.search + '&' : '') + 'enablejsapi=1'; 682 } 683 684 // for security reasons, YouTube wants an origin parameter set that matches our hostname 685 if (!isOriginAttrEnabled(a.search) && loc.hostname.indexOf('localhost') === -1) { 686 687 var port = loc.port ? ':' + loc.port : ''; 688 var origin = loc.protocol + '%2F%2F' + loc.hostname + port; 689 690 a.search = a.search + '&origin=' + origin; 691 692 } 693 694 if (tag.type === 'application/x-shockwave-flash') { 695 696 var newIframe = document.createElement('iframe'); 697 newIframe.height = tag.height; 698 newIframe.width = tag.width; 699 tmpPathname = tmpPathname.replace('/v/', '/embed/'); 700 701 tag.parentNode.parentNode.replaceChild(newIframe, tag.parentNode); 702 703 tag = newIframe; 704 705 } 706 707 a.pathname = tmpPathname; 708 709 if (tag.src !== a.href + a.hash) { 710 tag.src = a.href + a.hash; 711 } 712 713 return tag; 714 715 } 716 717 // Add event handlers for events emitted by the YouTube API 718 function addYouTubeEvents(iframe) { 719 720 var player = YT.get(iframe.id); 721 if (!player) { 722 player = new YT.Player(iframe, {}); 723 } 724 725 if (typeof iframe.pauseFlag === 'undefined') { 726 727 iframe.pauseFlag = false; 728 player.addEventListener('onStateChange', function (evt) { 729 onYouTubePlayerStateChange(evt, iframe); 730 }); 731 732 player.addEventListener('onPlaybackRateChange', function(evt) { 733 var currRate = evt.data; 734 var prevRate = iframe._pysSpeedRate !== undefined ? iframe._pysSpeedRate : 1; 735 iframe._pysSpeedRate = currRate; 736 if(currRate == prevRate) return; 737 if (options.triggerEventTypes && options.triggerEventTypes.video_speed) { 738 var href = window.location.href; 739 var ytVideoId = ''; 740 try { ytVideoId = player.getVideoData().video_id || ''; } catch(e) {} 741 Object.keys(options.triggerEventTypes.video_speed).forEach(function(evtId) { 742 var evtConfig = options.triggerEventTypes.video_speed[evtId]; 743 var urls = Array.isArray(evtConfig) ? evtConfig : (evtConfig.urls || []); 744 var speedRate = Array.isArray(evtConfig) ? 'any' : (evtConfig.speed_rate || 'any'); 745 var evtTriggers = Array.isArray(evtConfig) ? [] : (evtConfig.triggers || []); 746 var pageOk = !urls || urls.length === 0 || urls.some(function(u) { return u === '*' || u === '' || href.indexOf(u) !== -1; }); 747 var rateOk = speedRate === 'any' ? currRate !== prevRate : (speedRate === 'increase' ? currRate > prevRate : currRate < prevRate); 748 var videoOk = evtTriggers.length === 0 || evtTriggers.some(function(t) { return t.type === 'youtube' && t.rule === ytVideoId; }); 749 if (pageOk && rateOk && videoOk) { 750 Utils.handleTriggerFired(evtId, 'video_speed'); 751 } 752 }); 753 } 754 if (options.dynamicEvents && options.dynamicEvents.automatic_event_video_speed) { 755 var ytSpeedUrl = iframe.src || ''; 756 var ytSpeedTitle = ''; 757 try { ytSpeedTitle = player.getVideoData().title || ''; } catch(e) {} 758 var params = { 759 video_url: ytSpeedUrl, 760 video_type: 'youtube', 761 video_title: ytSpeedTitle || 'Unknown Title', 762 playback_speed: currRate, 763 element: 'iframe' 764 }; 765 Object.keys(options.dynamicEvents.automatic_event_video_speed).forEac
765h(function(slug) { 766 var pysEvent = Utils.clone(options.dynamicEvents.automatic_event_video_speed[slug]); 767 Utils.copyProperties(params, pysEvent.params); 768 var px = getPixelBySlag(slug); if (px) px.fireEvent(pysEvent.name, pysEvent); 769 }); 770 } 771 }); 772 773 } 774 775 } 776 777 function addDynYouTubeVideos(el) { 778 // We only bind to iFrames with a YouTube URL with the enablejsapi=1 and 779 // origin=<<hostname>> parameters 780 if (el.tagName === 'IFRAME' 781 && tagIsYouTubeVideo(el) 782 && isJSApiAttrEnabled(el.src) 783 && isOriginAttrEnabled(el.src)) 784 { 785 addYouTubeEvents(el); 786 } 787 } 788 789 // Event handler for events emitted from the YouTube API 790 function onYouTubePlayerStateChange(evt, iframe) { 791 792 var stateIndex = evt.data; 793 var player = evt.target; 794 var targetVideoUrl = player.getVideoUrl(); 795 var targetVideoId = targetVideoUrl.match(/[?&]v=([^&#]*)/)[1]; // Extract the ID 796 var playerState = player.getPlayerState(); 797 var marks = getVideoCompletionMarks(player.getDuration()); 798 799 iframe.playTracker = iframe.playTracker || {}; 800 801 if (playerState === YT.PlayerState.PLAYING && !iframe.timer) { 802 803 clearInterval(iframe.timer); 804 805 // check every second to see if we've hit any of our percentage viewed marks 806 iframe.timer = setInterval(function () { 807 checkYouTubeCompletion(player, marks, iframe.videoId); 808 }, 1000); 809 810 } else { 811 812 clearInterval(iframe.timer); 813 iframe.timer = false; 814 815 } 816 817 // playlist edge-case handler 818 if (stateIndex === YT.PlayerState.PLAYING) { 819 iframe.playTracker[targetVideoId] = true; 820 iframe.videoId = targetVideoId; 821 iframe.pauseFlag = false; 822 } 823 824 if (!iframe.playTracker[iframe.videoId]) { 825 return false; // this video hasn't started yet, so this is spam 826 } 827 828 if (stateIndex === YT.PlayerState.PAUSED) { 829 830 if (!iframe.pauseFlag) { 831 iframe.pauseFlag = true; 832 } else { 833 return false; // we don't want to fire consecutive pause events 834 } 835 836 } 837 838 } 839 840 // Trigger event if YouTube video mark was reached 841 function checkYouTubeCompletion(player, marks, videoId) { 842 843 var currentTime = player.getCurrentTime(); 844 845 player[videoId] = player[videoId] || {}; 846 847 for (var key in marks) { 848 849 if (marks[key] <= currentTime && !player[videoId][key]) { 850 player[videoId][key] = true; 851 852 var data = player.getVideoData(); 853 854 if (key === '0%') { 855 key = 'play'; 856 } 857 858 var params = { 859 video_type: 'youtube', 860 video_id: videoId, 861 video_title: data.title, 862 }; 863 864 let disable_watch_video = []; 865 866 //Custom 867 if ( options.triggerEventTypes.hasOwnProperty( "video_view" ) ) { 868 Object.entries( options.triggerEventTypes.video_view ).forEach( function ( [ trigger_id, triggers ] ) { 869 triggers.forEach( function ( trigger ) { 870 if ( trigger.type === 'youtube' && trigger.rule === videoId ) { 871 872 let pixels = Object.keys( options.triggerEvents[ trigger_id ] ); 873 for ( let i = 0; i < pixels.length; i++ ) { 874 let event = Utils.clone( options.triggerEvents[ trigger_id ][ pixels[ i ] ] ); 875 876 if ( trigger.disable_watch_video ) { 877 disable_watch_video.push( pixels[ i ] ); 878 } 879 880 if ( currentTime >= marks[ trigger.value ] ) { 881 if (pixels[i] !== 'reddit') { 882 event.params["progress"] = key; 883 Utils.copyProperties( params, event.params ); 884 } 885 886 if ( event.fired !== true ) { 887 if ( Utils.isEventInTimeWindow( event.name, event, 'dyn_' + pixels[ i ] + '_' + trigger_id ) ) { 888 event = Utils.getFormFilledData( event ); 889 getPixelBySlag( pixels[ i ] ).fireEvent( event.name, event ); 890 options.triggerEvents[ trigger_id ][ pixels[ i ] ].fired = true; 891 } 892 } 893 } 894 } 895 } 896 } ) 897 } ) 898 } 899 900 // Auto 901 if( options.automatic.enable_video 902 && options.automatic.enable_youtube 903 && options.dynamicEvents.hasOwnProperty("automatic_event_video") 904 ) { 905 var pixels = Object.keys(options.dynamicEvents.automatic_event_video); 906 907 for (var i = 0; i < pixels.length; i++) { 908 if ( disable_watch_video.includes( pixels[ i ] ) ) { 909 continue; 910 } 911 var event = Utils.clone(options.dynamicEvents.automatic_event_video[pixels[i]]); 912 913 if ( pixels[ i ] !== 'reddit' ) {
914 event.params[ "progress" ] = key; 915 Utils.copyProperties(params, event.params) 916 } 917 918 if ( pixels[i] === 'tiktok' || pixels[i] === 'reddit' ) { 919 var time_trigger = event.automatic_event_video_trigger; 920 if ( currentTime >= marks[time_trigger] && event.fired !== true ) { 921 getPixelBySlag(pixels[i]).onWatchVideo(event); 922 923 //tiktok and reddit watch video fire once 924 options.dynamicEvents.automatic_event_video[pixels[i]].fired = true; 925 } 926 } else { 927 Utils.copyProperties(Utils.getRequestParams(), event.params); 928 getPixelBySlag(pixels[i]).onWatchVideo(event); 929 } 930 } 931 } 932 933 if(key == "play") { 934 935 $.each(options.triggerEventTypes, function (triggerType, events) { 936 $.each(events, function (eventId, triggers) { 937 switch (triggerType) { 938 case 'video_play': 939 Utils.handleTriggerFired(eventId, triggerType); 940 break; 941 } 942 943 }); 944 }); 945 } 946 947 } 948 949 } 950 951 } 952 953 // Determine if the element is a Vimeo video or not 954 function tagIsVimeoVideo(tag) { 955 var src = tag.src || ''; 956 return src.indexOf('player.vimeo.com/video/') > -1; 957 } 958 959 function tagIsAsincVimeoVideo(tag) { 960 if(tag.src) return false; // video is loaded 961 var keys = Object.keys(tag.dataset); 962 for(var i = 0;i<keys.length;i++) { 963 if(keys[i].toLowerCase().indexOf("src") > -1) { 964 var src = tag.dataset[keys[i]]; 965 return src.indexOf('player.vimeo.com/video/') > -1; 966 } 967 } 968 return false; //not find src 969 } 970 971 function attachVimeoPlayerToTag(tag) { 972 var player = new Vimeo.Player(tag); 973 974 player.getDuration().then(function (pl,seconds) { 975 pl.pysMarks = getVideoCompletionMarks(seconds); 976 }.bind(null,player)); 977 978 player.getVideoTitle().then(function (pl,title) { 979 pl.pysVideoTitle = title; 980 }.bind(null,player)); 981 982 player.getVideoId().then(function (pl,id) { 983 pl.pysVideoId = id; 984 }.bind(null,player)); 985 986 player.pysCompletedMarks = {}; 987 988 player.on('play', function () { 989 990 if (this.pysTimer) { 991 return; 992 } 993 994 clearInterval(this.pysTimer); 995 996 var player = this; 997 998 this.pysTimer = setInterval(function () { 999 checkVimeoCompletion(player); 1000 }, 1000); 1001 1002 }); 1003 1004 player.on('pause', function () { 1005 clearInterval(this.pysTimer); 1006 this.pysTimer = false; 1007 }); 1008 1009 player.on('ended', function () { 1010 clearInterval(this.pysTimer); 1011 this.pysTimer = false; 1012 }); 1013 1014 player.on('playbackratechange', function(data) { 1015 var currRate = data.playbackRate; 1016 var prevRate = player._pysSpeedRate !== undefined ? player._pysSpeedRate : 1; 1017 player._pysSpeedRate = currRate; 1018 if(currRate == prevRate) return; 1019 if (options.triggerEventTypes && options.triggerEventTypes.video_speed) { 1020 var href = window.location.href; 1021 var vmVideoId = player.pysVideoId ? String(player.pysVideoId) : ''; 1022 Object.keys(options.triggerEventTypes.video_speed).forEac
1022h(function(evtId) { 1023 var evtConfig = options.triggerEventTypes.video_speed[evtId]; 1024 var urls = Array.isArray(evtConfig) ? evtConfig : (evtConfig.urls || []); 1025 var speedRate = Array.isArray(evtConfig) ? 'any' : (evtConfig.speed_rate || 'any'); 1026 var evtTriggers = Array.isArray(evtConfig) ? [] : (evtConfig.triggers || []); 1027 var pageOk = !urls || urls.length === 0 || urls.some(function(u) { return u === '*' || u === '' || href.indexOf(u) !== -1; }); 1028 var rateOk = speedRate === 'any' ? currRate !== prevRate : (speedRate === 'increase' ? currRate > prevRate : currRate < prevRate); 1029 var videoOk = evtTriggers.length === 0 || evtTriggers.some(function(t) { return t.type === 'vimeo' && t.rule == vmVideoId; }); 1030 if (pageOk && rateOk && videoOk) { 1031 Utils.handleTriggerFired(evtId, 'video_speed'); 1032 } 1033 }); 1034 } 1035 if (options.dynamicEvents && options.dynamicEvents.automatic_event_video_speed) { 1036 var vimeoEl = player.element || null; 1037 var params = { 1038 video_url: vimeoEl ? (vimeoEl.src || '') : '', 1039 video_type: 'vimeo', 1040 video_title: player.pysVideoTitle || 'Unknown Title', 1041 playback_speed: currRate, 1042 element: 'iframe' 1043 }; 1044 Object.keys(options.dynamicEvents.automatic_event_video_speed).forEach(function(slug) { 1045 var pysEvent = Utils.clone(options.dynamicEvents.automatic_event_video_speed[slug]); 1046 Utils.copyProperties(params, pysEvent.params); 1047 var px = getPixelBySlag(slug); if (px) px.fireEvent(pysEvent.name, pysEvent); 1048 }); 1049 } 1050 }); 1051 } 1052 1053 // Trigger event if Vimeo video mark was reached 1054 function checkVimeoCompletion(player) { 1055 1056 player.getCurrentTime().then(function (seconds) { 1057 1058 for (var key in player.pysMarks) { 1059 1060 if (player.pysMarks[key] <= seconds && !player.pysCompletedMarks[key]) { 1061 1062 player.pysCompletedMarks[key] = true; 1063 1064 if (key === '0%') { 1065 key = 'play'; 1066 } 1067 1068 var params = { 1069 video_type: 'vimeo', 1070 video_id: player.pysVideoId, 1071 video_title: player.pysVideoTitle, 1072 }; 1073 1074 let disable_watch_video = []; 1075 1076 //Custom 1077 if ( options.triggerEventTypes.hasOwnProperty( "video_view" ) ) { 1078 Object.entries( options.triggerEventTypes.video_view ).forEach( function ( [ trigger_id, triggers ] ) { 1079 triggers.forEach( function ( trigger ) { 1080 if ( trigger.type === 'vimeo' && trigger.rule == player.pysVideoId ) { 1081 let pixels = Object.keys( options.triggerEvents[ trigger_id ] ); 1082 for ( let i = 0; i < pixels.length; i++ ) { 1083 let event = Utils.clone( options.triggerEvents[ trigger_id ][ pixels[ i ] ] ); 1084 1085 if ( trigger.disable_watch_video ) { 1086 disable_watch_video.push( pixels[ i ] ); 1087 } 1088 1089 if ( seconds >= player.pysMarks[ trigger.value ] ) { 1090 if ( pixels[ i ] !== 'reddit' ) { 1091 event.params[ "progress" ]
1091= key; 1092 Utils.copyProperties( params, event.params ); 1093 } 1094 1095 if ( event.fired !== true ) { 1096 if ( Utils.isEventInTimeWindow( event.name, event, 'dyn_' + pixels[ i ] + '_' + trigger_id ) ) { 1097 event = Utils.getFormFilledData( event ); 1098 getPixelBySlag( pixels[ i ] ).fireEvent( event.name, event ); 1099 options.triggerEvents[ trigger_id ][ pixels[ i ] ].fired = true; 1100 } 1101 } 1102 } 1103 } 1104 } 1105 } ) 1106 } ) 1107 } 1108 1109 // Auto 1110 if(options.automatic.enable_video 1111 && options.automatic.enable_vimeo 1112 && options.dynamicEvents.hasOwnProperty("automatic_event_video") 1113 ) { 1114 var pixels = Object.keys(options.dynamicEvents.automatic_event_video); 1115 1116 for (var i = 0; i < pixels.length; i++) { 1117 if ( disable_watch_video.includes( pixels[ i ] ) ) { 1118 continue; 1119 } 1120 1121 var event = Utils.clone(options.dynamicEvents.automatic_event_video[pixels[i]]); 1122 1123 if ( pixels[i] !== 'reddit' ) { 1124 event.params["progress"] = key; 1125 Utils.copyProperties(params, event.params); 1126 } 1127 1128 if ( pixels[i] === 'tiktok' || pixels[i] === 'reddit' ) { 1129 var time_trigger = event.automatic_event_video_trigger; 1130 if ( seconds >= player.pysMarks[time_trigger] && event.fired !== true ) { 1131 getPixelBySlag(pixels[i]).onWatchVideo(event); 1132 1133 //tiktok and reddit watch video fire once 1134 options.dynamicEvents.automatic_event_video[pixels[i]].fired = true; 1135 } 1136 } else { 1137 Utils.copyProperties(Utils.getRequestParam
1137s(), event.params); 1138 getPixelBySlag(pixels[i]).onWatchVideo(event); 1139 } 1140 } 1141 } 1142 1143 if(key == "play") { 1144 $.each(options.triggerEventTypes, function (triggerType, events) { 1145 $.each(events, function (eventId, triggers) { 1146 switch (triggerType) { 1147 case 'video_play': 1148 Utils.handleTriggerFired(eventId, triggerType); 1149 break; 1150 } 1151 1152 }); 1153 }); 1154 } 1155 } 1156 1157 } 1158 1159 }); 1160 1161 } 1162 1163 // ââ HTML5 <video> WatchVideo tracking âââââââââââââââââââââââââââââââââââ 1164 function initHtml5VideoTracking() { 1165 1166 if ( !options.automatic.enable_video 1167 || !options.dynamicEvents.hasOwnProperty('automatic_event_video') 1168 ) { 1169 return; 1170 } 1171 1172 // play event â fires once per element when playback starts 1173 document.addEventListener('play', function (e) { 1174 var el = e.target; 1175 if ( !el || el.tagName !== 'VIDEO' ) return; 1176 1177 el.pysCompletedMarks = el.pysCompletedMarks || {}; 1178 if ( el.pysCompletedMarks['play'] ) return; 1179 1180 var url = el.src || el.currentSrc || ''; 1181 el.pysCompletedMarks['play'] = true; 1182 1183 fireHtml5VideoMilestone( el, url, 'play' ); 1184 1185 }, true ); 1186 1187 // timeupdate â check percentage milestones 1188 document.addEventListener('timeupdate', function (e) { 1189 var el = e.target; 1190 if ( !el || el.tagName !== 'VIDEO' ) return; 1191 1192 var duration = el.duration; 1193 if ( !duration || isNaN(duration) || !isFinite(duration) ) return; 1194 1195 var currentTime = el.currentTime; 1196 var marks = getVideoCompletionMarks( duration ); 1197 var url = el.src || el.currentSrc || ''; 1198 1199 el.pysCompletedMarks = el.pysCompletedMarks || {}; 1200 1201 for ( var key in marks ) { 1202 if ( key === '0%' ) continue; // handled by 'play' listener 1203 1204 if ( marks[ key ] <= currentTime && !el.pysCompletedMarks[ key ] ) { 1205 el.pysCompletedMarks[ key ] = true; 1206 fireHtml5VideoMilestone( el, url, key ); 1207 } 1208 } 1209 1210 }, true ); 1211 } 1212 1213 function fireHtml5VideoMilestone( el, url, key ) { 1214 1215 var progressLabel = ( key === '0%' ) ? 'play' : key; 1216 1217 var params = { 1218 video_type: 'html5', 1219 video_id: el.id || url, 1220 video_title: Utils.getHtml5VideoTitle( url ), 1221 }; 1222 1223 var pixels = Object.keys( options.dynamicEvents.automatic_event_video ); 1224 1225 for ( var i = 0; i < pixels.length; i++ ) { 1226 var event = Utils.clone( options.dynamicEvents.automatic_event_video[ pixels[ i ] ] ); 1227 1228 if ( pixels[ i ] !== 'reddit' ) { 1229 event.params[ 'progress' ] = progressLabel; 1230 Utils.copyProperties( params, event.params ); 1231 } 1232 1233 if ( pixels[ i ] === 'tiktok' || pixels[ i ] === 'reddit' ) { 1234 var time_trigger = event.automatic_event_video_trigger; 1235 var marks = getVideoCompletionMarks( el.duration || 0 ); 1236 if ( el.currentTime >= marks[ time_trigger ] && event.fired !== true ) { 1237 getPixelBySlag( pixels[ i ] ).onWatchVideo( event ); 1238 options.dynamicEvents.automatic_event_video[ pixels[ i ] ].fired = true; 1239 } 1240 } else {
1241 Utils.copyProperties( Utils.getRequestParams(), event.params ); 1242 getPixelBySlag( pixels[ i ] ).onWatchVideo( event ); 1243 } 1244 } 1245 1246 // handle custom video_view triggers (mirrors YouTube/Vimeo pattern) 1247 if ( key === 'play' ) { 1248 $.each( options.triggerEventTypes, function ( triggerType, events ) { 1249 $.each( events, function ( eventId ) { 1250 if ( triggerType === 'video_play' ) { 1251 Utils.handleTriggerFired( eventId, triggerType ); 1252 } 1253 } ); 1254 } ); 1255 } 1256 } 1257 1258 /** 1259 * COOKIES UTILS 1260 */ 1261 1262 var utmTerms = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content' ,'utm_term']; 1263 var utmId = ['fbadid', 'gadid', 'padid', 'bingid']; 1264 var requestParams = []; 1265 1266 1267 function getDomain(url) { 1268 1269 url = url.replace(/(https?:\/\/)?(www.)?/i, ''); 1270 1271 if (url.indexOf('/') !== -1) { 1272 return url.split('/')[0]; 1273 } 1274 1275 return url; 1276 } 1277 1278 function checkSession() { 1279 1280 if( Cookies.get('pys_start_session') === undefined || 1281 Cookies.get('pys_session_limit') === undefined) { 1282 firstVisit = true; 1283 return true 1284 } 1285 return false 1286 1287 } 1288 1289 function getTrafficSource() { 1290 1291 try { 1292 1293 let referrer = stripUrlQuery(document.referrer.toString()), 1294 source; 1295 1296 let direct = referrer.length === 0; 1297 let internal = direct ? false : referrer.indexOf(options.siteUrl) === 0; 1298 let external = !direct && !internal; 1299 1300 if (external === false) { 1301 source = 'direct'; 1302 } else { 1303 source = referrer; 1304 } 1305 1306 if (source !== 'direct') { 1307 // leave only domain (Issue #70) 1308 return getDomain(source); 1309 } else { 1310 return source; 1311 } 1312 1313 } catch (e) { 1314 console.error(e); 1315 return 'direct'; 1316 } 1317 1318 } 1319 1320 /** 1321 * Return query variables object with where property name is query variable 1322 * and property value is query variable value. 1323 */ 1324 function getQueryVars() { 1325 1326 try { 1327 1328 var result = {}, 1329 tmp = []; 1330 1331 window.location.search 1332 .substr(1) 1333 .split("&") 1334 .forEach(function (item) { 1335 1336 tmp = item.split('='); 1337 1338 if (tmp.length > 1) { 1339 result[tmp[0]] = tmp[1]; 1340 } 1341 1342 }); 1343 1344 return result; 1345 1346 } catch (e) { 1347 console.error(e); 1348 return {}; 1349 } 1350 1351 } 1352 1353 function getLandingPageValue() { 1354 let name = "pys_landing_page" 1355 if(options.visit_data_model === "last_visit") { 1356 name = "last_pys_landing_page" 1357 } 1358 if(Cookies.get(name) && Cookies.get(name) !== "undefined") { 1359 return Cookies.get(name); 1360 } 1361 // No cookie of our own. The dynamic-options endpoint may still know: 1362 // it answers per visitor and is never cached, so it is allowed to carry 1363 // the PHP session copy â which is all a visitor whose cookies a consent 1364 // manager has blocked ever has. In the HTML this value is always empty, 1365 // and an empty value means "nothing to say", so this branch is simply 1366 // skipped unless the endpoint filled it in. 1367 if ( options.hasOwnProperty( "tracking_analytics" ) 1368 && options.tracking_analytics 1369 && options.tracking_analytics.TrafficLanding ) { 1370 return options.tracking_analytics.TrafficLanding; 1371 } 1372 1373 // Nobody knows better than the current URL: it is what the cookie would 1374 // have been set to a moment ago. 1375 return window.location.href; 1376 } 1377 function getTrafficSourceValue() { 1378 let name = "pysTrafficSource" 1379 if(options.visit_data_model === "last_visit") { 1380 name = "last_pysTrafficSource" 1381 } 1382 if(Cookies.get(name) && Cookies.get(name) !== "undefined") { 1383 return Cookies.get(name); 1384 } 1385 // See getLandingPageValue(): filled by the dynamic-options endpoint only. 1386 if ( options.hasOwnProperty( "tracking_analytics" ) 1387 && options.tracking_analytics 1388 && options.tracking_analytics.TrafficSource ) { 1389 return options.tracking_analytics.TrafficSource; 1390 } 1391 return ""; 1392 } 1393 1394 /** 1395 * Handle alternative GCLID parameter for Safari compatibility 1396 * Creates _gcl_aw cookie when alternative parameter is detected and _gcl_aw doesn't exist 1397 */ 1398 function handleAlternativeGclid() { 1399 // Check if alternative GCLID is enabled 1400 if (!options.GATags || !options.GATags.gclid_alternative_enabled || !options.GATags.gclid_alternative_param) { 1401 return; 1402 } 1403 1404 // GDPR consent checks - stop if any consent filter blocks it 1405 if (options.gdpr.all_disabled_by_api) { 1406 return; 1407 } 1408 if (options.gdpr.analytics_disabled_by_api) { 1409 return; 1410 } 1411 if (options.cookie.disabled_all_cookie) { 1412 return; 1413 } 1414 if (options.cookie.disabled_google_alternative_id) { 1415 return; 1416 } 1417 1418 const alternativeParam = options.GATags.gclid_alternative_param; 1419 const queryVars = getQueryVars(); 1420 1421 // Check if alternative parameter exists in URL 1422 if (queryVars[alternativeParam]) { 1423 const gclidValue = queryVars[alternativeParam]; 1424 1425 // Only create _gcl_aw cookie if it doesn't exist 1426 if (!Cookies.get('_gcl_aw')) { 1427 // Create _gcl_aw cookie with the alternative parameter value in Google's format 1428 // Format: GCL.{timestamp}.{gclid_value} 1429 const timestamp = Math.floor(Date.now() / 1000); 1430 const cookieValue = 'GCL.' + timestamp + '.' + gclidValue; 1431 1432 // Set cookie for 90 days (Google's standard) 1433 const expires = new Date(); 1434 expires.setTime(expires.getTime() + (90 * 24 * 60 * 60 * 1000)); 1435 1436 Cookies.set('_gcl_aw', cookieValue, { 1437 expires: expires, 1438 path: '/', 1439 domain: cookieDomain() 1440 }); 1441 1442 if (options.debug) { 1443 console.log('PYS: Created _gcl_aw cookie from alternative parameter:', alternativeParam, '=', gclidValue); 1444 } 1445 } 1446 } 1447 } 1448 1449 function getUTMId(useLast = false) { 1450 try { 1451 let cookiePrefix = 'pys_' 1452 let terms = []; 1453 if (useLast) { 1454 cookiePrefix = 'last_pys_' 1455 } 1456 // Fall back to this request's own query string, not to the server 1457 // options: cached HTML carries whatever the visitor who generated 1458 // the cache entry arrived with. 1459 let queryVars = getQueryVars(); 1460 $.each(utmId, function (index, name) { 1461 if (Cookies.get(cookiePrefix + name)) { 1462 terms[name] = Cookies.get(cookiePrefix + name) 1463 } 1464 else if(options.hasOwnProperty("tracking_analytics") 1465 && options.tracking_analytics 1466 && options.tracking_analytics.TrafficUtmsId 1467 && options.tracking_analytics.TrafficUtmsId[name]) { 1468 terms[name] = filterEmails(options.tracking_analytics.TrafficUtmsId[name]) 1469 } 1470 else if(queryVars.hasOwnProperty(name)) { 1471 terms[name] = filterEmails(queryVars[name]) 1472 } 1473 }); 1474 return terms; 1475 } catch (e) { 1476 console.error(e); 1477 return []; 1478 } 1479 } 1480 /** 1481 * Return UTM terms from request query variables or from cookies. 1482 */ 1483 function getUTMs(useLast = false) { 1484 1485 try { 1486 let cookiePrefix = 'pys_' 1487 if(useLast) { 1488 cookiePrefix = 'last_pys_' 1489 } 1490 let terms = []; 1491 // See getUTMId(): the query string, never the server options. 1492 let queryVars = getQueryVars(); 1493 $.each(utmTerms, function (index, name) { 1494 if (Cookies.get(cookiePrefix + name)) { 1495 let value = Cookies.get(cookiePrefix + name); 1496 terms[name] = filterEmails(value); // do not allow email in request params (Issue #70) 1497 } 1498 else if(options.hasOwnProperty("tracking_analytics") 1499 && options.tracking_analytics 1500 && options.tracking_analytics.TrafficUtms 1501 && options.tracking_analytics.TrafficUtms[name]) { 1502 terms[name] = filterEmails(options.tracking_analytics.TrafficUtms[name]) 1503 } 1504 else if(queryVars.hasOwnProperty(name)) { 1505 terms[name] = filterEmails(queryVars[name]) 1506 } 1507 }); 1508 1509 return terms; 1510 1511 } catch (e) { 1512 console.error(e); 1513 return []; 1514 } 1515 1516 } 1517 1518 function getDateTime() { 1519 var dateTime = new Array(); 1520 var date = new Date(), 1521 days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 1522 months = ['January', 'February', 'March', 'April', 'May', 'June', 1523 'July', 'August', 'September', 'October', 'November', 'December' 1524 ], 1525 hours = ['00-01', '01-02', '02-03', '03-04', '04-05', '05-06', '06-07', '07-08', 1526 '08-09', '09-10', '10-11', '11-12', '12-13', '13-14', '14-15', '15-16', '16-17', 1527 '17-18', '18-19', '19-20', '20-21', '21-22', '22-23', '23-24' 1528 ]; 1529 dateTime.push(hours[date.getHours()]); 1530 dateTime.push(days[date.getDay()]); 1531 dateTime.push(months[date.getMonth()]); 1532 return dateTime; 1533 } 1534 1535 function filterEmails(value) { 1536 return Utils.validateEmail(value) ? undefined : value; 1537 } 1538 1539 /** 1540 * PUBLIC API 1541 */ 1542 return { 1543 1544 PRODUCT_SIMPLE : 0, 1545 PRODUCT_VARIABLE : 1, 1546 PRODUCT_BUNDLE : 2, 1547 PRODUCT_GROUPED : 3, 1548 utmTerms : utmTerms, 1549 utmId : utmId, 1550 isNewSession: checkSession(), 1551 setHidePixelCookie: function(){ 1552 let refresh_after_consent = false; 1553 if ( !firstVisit && options.gdpr.consent_magic_integration_enabled && window.CS_Data !== undefined && window.CS_Data.cs_refresh_after_consent) { 1554 refresh_after_consent = true; 1555 } 1556 if( !refresh_after_consent && !firstVisit) return false; 1557 1558 const url_parts = window.location.href; 1559 const url_params = new URLSearchParams(window.location.search); 1560 const matchingPixels = ["facebook", "ga", "gtm", "google_ads", "bing", "pinterest", "tiktok", "reddit", "openai"]; 1561 $.each(matchingPixels, function (index, slug) { 1562 var module = getPixelBySlag(slug); 1563 if (module && module.isEnabled()) { 1564 const fullUrl = window.location.href; 1565 $.each(module.getHidePixel(), function (index, hide_info) { 1566 for (const item of hide_info.hide_tag_contain) { 1567 if (item && fullUrl.includes(item)) { 1568 let hideTagTimeInMilliseconds = hide_info.hide_tag_time * 60 * 60 * 1000; 1569 let expiresTime = new Date().getTime() + hideTagTimeInMilliseconds; 1570 Cookies.set('hide_tag_' + hide_info.pixel, true, { expires: new Date(expiresTime), path: '/' });
1571 break; 1572 } 1573 } 1574 }); 1575 } 1576 1577 }); 1578 }, 1579 /** 1580 * Is this Meta pixel opted into Meta-Enabled Conversion API? 1581 */ 1582 isMetaEnabledCapiPixel: function (pixelId) { 1583 var pixels = options.facebook && options.facebook.metaEnabledCapiPixels; 1584 return Array.isArray(pixels) && pixels.indexOf(pixelId) !== -1; 1585 }, 1586 hideMatchingPixel: function(pixelValue, slug) { 1587 if(Cookies.get('hide_tag_'+pixelValue)) 1588 { 1589 return true; 1590 } 1591 return false; 1592 }, 1593 /** 1594 * Whether a send_to value actually points at a destination. 1595 * 1596 * gtag('event', name, { send_to: [] }) has no destination, so gtag.js 1597 * parks the hit until a matching config command appears. Tag Assistant 1598 * reports that as "Some hits will only be sent after a config command 1599 * is issued via gtag('config')". Callers must skip the event instead. 1600 */ 1601 hasSendToTarget: function (target) { 1602 if (Array.isArray(target)) { 1603 return target.length > 0; 1604 } 1605 return typeof target === 'string' && target !== ''; 1606 }, 1607 validateEmail: function (email) { 1608 var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 1609 return re.test(email); 1610 }, 1611 fireEventForAllPixel:function(functionName,events){ 1612 Utils.refreshAddonPixels(); 1613 1614 if (events.hasOwnProperty(Facebook.tag())) 1615 Facebook[functionName](events[Facebook.tag()]); 1616 if (events.hasOwnProperty(Analytics.tag())) 1617 Analytics[functionName](events[Analytics.tag()]); 1618 if (events.hasOwnProperty(GAds.tag())) 1619 GAds[functionName](events[GAds.tag()]); 1620 if (events.hasOwnProperty(Pinterest.tag())) 1621 Pinterest[functionName](events[Pinterest.tag()]); 1622 if (events.hasOwnProperty(Bing.tag())) 1623 Bing[functionName](events[Bing.tag()]); 1624 if (events.hasOwnProperty(TikTok.tag())) 1625 TikTok[functionName](events[TikTok.tag()]); 1626 if (events.hasOwnProperty(Reddit.tag())) 1627 Reddit[functionName](events[Reddit.tag()]); 1628 1629 if (events.hasOwnProperty(GTM.tag())) 1630 GTM[functionName](events[GTM.tag()]); 1631 }, 1632 1633 getQueryValue:function (name){ 1634 return getQueryVars()[name]; 1635 }, 1636 1637 filterEmails: function (value) { 1638 return filterEmails(value); 1639 }, 1640 1641 /** 1642 * Re-read the pixels that live in separate plugins. 1643 */ 1644 refreshAddonPixels: function () { 1645 Pinterest = Utils.setupPinterestObject(); 1646 Bing = Utils.setupBingObject(); 1647 Reddit = Utils.setupRedditObject(); 1648 }, 1649 1650 /** 1651 * Is one of ConsentMagic's switches actually on? 1652 */ 1653 csModeEnabled: function ( value ) { 1654 return Number( value ) === 1; 1655 }, 1656 1657 setupPinterestObject: function () { 1658 Pinterest = window.pys.Pinterest || Pinterest; 1659 return Pinterest; 1660 }, 1661 1662 setupBingObject: function () { 1663 Bing = window.pys.Bing || Bing; 1664 return Bing; 1665 }, 1666 1667 setupRedditObject: function () { 1668 Reddit = window.pys.Reddit || Reddit; 1669 return Reddit; 1670 }, 1671 1672 // Clone all object members to another and return it 1673 copyProperties: function (from, to) { 1674 for (var key in from) { 1675 if("function" == typeof from[key]) { 1676 continue; 1677 } 1678 to[key] = from[key]; 1679 } 1680 return to; 1681 }, 1682 1683 getVideoTypeFromUrl: function (url) { 1684 if (!url) return 'unknown'; 1685 if (url.indexOf('youtube.com') !== -1 || url.indexOf('youtu.be') !== -1) return 'youtube'; 1686 if (url.indexOf('vimeo.com') !== -1) return 'vimeo'; 1687 return 'html5'; 1688 }, 1689 1690 getHtml5VideoTitle: function (url) { 1691 if (!url) return 'Unknown Title'; 1692 var path = url.split('?')[0].split('#')[0]; 1693 var filename = path.split('/').pop(); 1694 if (!filename) return 'Unknown Title'; 1695 var ext = filename.lastIndexOf('.'); 1696 return ext > 0 ? filename.slice(0, ext) : filename; 1697 }, 1698 1699 /** 1700 * Generate unique ID 1701 * 1702 * For purchase events (woo_purchase, edd_purchase): 1703 * - Server eventID has priority (for server-browser deduplication) 1704 * - If server didn't provide eventID, generate one keyed by order_id 1705 * 1706 * For other events: 1707 * - Use existing logic with event type as key 1708 */ 1709 generateUniqueId : function (event) { 1710 var eventsWithHook = [ 1711 'woo_purchase', 1712 'woo_start_trial', 1713 'woo_subscription_created', 1714 'woo_subscription_renewal', 1715 'edd_purchase', 1716 'edd_start_trial', 1717 'edd_subscription_created', 1718 'edd_subscription_renewal', 1719 'edd_license_created', 1720 'edd_license_upgrade', 1721 'edd_license_renewal' 1722 ]; 1723 1724 // For events with server hooks: prioritize server eventID 1725 if ( eventsWithHook.includes( event.e_id ) ) { 1726 // If server provided eventID, use it (for deduplication) 1727 if ( event.eventID && event.eventID.length > 0 ) { 1728 var orderKey = event.e_id; 1729 if ( !uniqueId.hasOwnProperty( orderKey ) ) { 1730 uniqueId[orderKey] = event.eventID; 1731 } 1732 return uniqueId[orderKey]; 1733 } 1734 } 1735 1736 if ( 1737 event.eventID.length == 0 || 1738 ( !event.isVariationTrigger && 1739 ( event.type == "static" && options.ajaxForServerStaticEvent || 1740 event.type !== "static" && options.ajaxForServerEvent ) 1741 ) 1742 ) { 1743 let idKey = event.hasOwnProperty('custom_event_post_id') ? event.custom_event_post_id : event.e_id; 1744 if ( !uniqueId.hasOwnProperty( idKey ) ) { 1745 uniqueId[idKey] = pys_generate_token(); 1746 } 1747 return uniqueId[idKey]; 1748 } 1749 else if ( event.eventID.length !== 0 ) { 1750 return event.eventID; 1751 } 1752 }, 1753 1754 // Flatten object for sendBeacon compatibility 1755 flattenObject: function (obj, prefix = '', res = {}) { 1756 for (const [key, value] of Object.entries(obj)) { 1757 const prefixedKey = prefix 1758 ? (Array.isArray(obj) ? `${prefix}[${key}]` : `${prefix}[${key}]`)
1759 : key; 1760 if (value !== null && typeof value === 'object' && !(value instanceof File)) { 1761 this.flattenObject(value, prefixedKey, res); 1762 } else { 1763 res[prefixedKey] = value; 1764 } 1765 } 1766 return res; 1767 }, 1768 1769 sendServerAjaxRequest: function ( url, data ) { 1770 // Check if we should use REST API for TikTok or Facebook 1771 if (data.action === 'pys_tiktok_api_event' && window.pysTikTokRest) { 1772 // Use TikTok REST API 1773 this.sendRestAPIRequest(data, 'tiktok'); 1774 return; 1775 } 1776 1777 if (data.action === 'pys_openai_api_event' && window.pysOpenAIRest) { 1778 this.sendRestAPIRequest(data, 'openai'); 1779 return; 1780 } 1781 1782 if (data.action === 'pys_api_event' && data.pixel === 'facebook' && window.pysFacebookRest) { 1783 // Use Facebook REST API 1784 this.sendRestAPIRequest(data, 'facebook'); 1785 return; 1786 } 1787 1788 // Check if sendBeacon is enabled and supported 1789 if (options.useSendBeacon && navigator.sendBeacon) { 1790 try { 1791 // Flatten the data object for sendBeacon compatibility 1792 const flattenedData = this.flattenObject(data); 1793 const formData = new URLSearchParams(); 1794 1795 // Convert flattened data to URLSearchParams 1796 for (const [key, value] of Object.entries(flattenedData)) { 1797 if (value !== null && value !== undefined) { 1798 formData.append(key, value); 1799 } 1800 } 1801 1802 // Try to send using sendBeacon 1803 const success = navigator.sendBeacon(url, formData); 1804 if (success) { 1805 return; // Successfully sent via sendBeacon 1806 } 1807 } catch (e) { 1808 // If sendBeacon fails, fall back to jQuery.ajax 1809 if (options.debug) { 1810 console.log('PYS: sendBeacon failed, falling back to jQuery.ajax:', e); 1811 } 1812 } 1813 } 1814 1815 // Fallback to jQuery.ajax 1816 jQuery.ajax( { 1817 type: 'POST', 1818 url: url, 1819 data: data, 1820 headers: { 1821 'Cache-Control': 'no-cache' 1822 }, 1823 success: function () { 1824 }, 1825 } ); 1826 }, 1827 1828 // Send event via REST API (unified function for TikTok, Facebook, and other platforms) 1829 // Replaces the old sendTikTokRestAPIRequest and sendFacebookRestAPIRequest functions 1830 sendRestAPIRequest: function (data, platform) { 1831 let restApiUrl; 1832 1833 // Get platform-specific REST API configuration 1834 switch (platform) { 1835 case 'tiktok': 1836 restApiUrl = window.pysTikTokRest ? window.pysTikTokRest.restApiUrl : '/wp-json/pys-tiktok/v1/event'; 1837 break; 1838 case 'facebook': 1839 restApiUrl = window.pysFacebookRest ? window.pysFacebookRest.restApiUrl : '/wp-json/pys-facebook/v1/event'; 1840 break; 1841 case 'pinterest': 1842 restApiUrl = window.pysPinterestRest ? window.pysPinterestRest.restApiUrl : '/wp-json/pys-pinterest/v1/event'; 1843 break; 1844 case 'openai': 1845 restApiUrl = window.pysOpenAIRest ? window.pysOpenAIRest.restApiUrl : '/wp-json/pys-openai/v1/event'; 1846 break; 1847 default: 1848 console.error('PYS: Unknown platform for REST API:', platform); 1849 this.sendAjaxFallback(data); 1850 return; 1851 } 1852 1853 // Prepare data for REST API 1854 const restApiData = { 1855 event: data.event, 1856 event_slug: data.event_slug, 1857 data: JSON.stringify(data.data || {}), 1858 ids: JSON.stringify(data.ids || []), 1859 eventID: data.event_id || data.eventID || '',
1860 woo_order: data.woo_order || '0', 1861 edd_order: data.edd_order || '0', 1862 order_key: data.order_key || '' 1863 }; 1864 1865 // Facebook-only: forward the per-page referrer so the CAPI 1866 // helper can attach it to custom_data.referrer_url. 1867 if (platform === 'facebook') { 1868 const forwardedReferrer = data.referrer_url || pageReferrer; 1869 if (forwardedReferrer) { 1870 restApiData.referrer_url = forwardedReferrer; 1871 } 1872 } 1873 1874 // Variant A: send the actual page URL where the event happened 1875 // so the server sets event_source_url to this page, not the 1876 // REST endpoint. PHP applies enable_remove_source_url_params. 1877 restApiData.event_source_url = data.url || window.location.href; 1878 1879 // Try to send using sendBeacon first (if enabled) 1880 if (options.useSendBeacon && navigator.sendBeacon) { 1881 try { 1882 const formData = new URLSearchParams(); 1883 for (const [key, value] of Object.entries(restApiData)) { 1884 formData.append(key, value); 1885 } 1886 1887 if (navigator.sendBeacon(restApiUrl, formData)) { 1888 return; 1889 } 1890 } catch (e) { 1891 // sendBeacon failed, continue to fetch 1892 } 1893 } 1894 1895 // Try to send using fetch with REST API 1896 if (window.fetch) { 1897 const headers = { 1898 'Content-Type': 'application/json' 1899 }; 1900 1901 fetch(restApiUrl, { 1902 method: 'POST', 1903 headers: headers, 1904 body: JSON.stringify(restApiData) 1905 }) 1906 .then(response => { 1907 if (!response.ok) { 1908 throw new Error(platform + ' REST API request failed: ' + response.status); 1909 } 1910 }) 1911 .catch(error => { 1912 // Fallback to AJAX if REST API fails 1913 if (options.debug) { 1914 console.log('PYS: ' + platform + ' REST API failed, falling back to AJAX:', error); 1915 } 1916 this.sendAjaxFallback(data); 1917 }); 1918 } else { 1919 // Fallback to AJAX if fetch is not supported 1920 this.sendAjaxFallback(data); 1921 } 1922 }, 1923 1924 1925 1926 // Fallback AJAX method 1927 sendAjaxFallback: function (data) { 1928 jQuery.ajax({ 1929 type: 'POST', 1930 url: options.ajaxUrl, 1931 data: data, 1932 headers: { 1933 'Cache-Control': 'no-cache' 1934 }, 1935 success: function () { 1936 }, 1937 }); 1938 }, 1939 1940 clone: function(obj) { 1941 var copy; 1942 1943 // Handle the 3 simple types, and null or undefined 1944 if (null == obj || "object" != typeof obj) return obj; 1945 1946 // Handle Date 1947 if (obj instanceof Date) { 1948 copy = new Date(); 1949 copy.setTime(obj.getTime()); 1950 return copy; 1951 } 1952 1953 // Handle Array 1954 if (obj instanceof Array) { 1955 copy = []; 1956 for (var i = 0, len = obj.length; i < len; i++) { 1957 if("function" == typeof obj[i]) { 1958 continue; 1959 } 1960 copy[i] = Utils.clone(obj[i]); 1961 } 1962 return copy; 1963 } 1964 1965 // Handle Object 1966 if (obj instanceof Object) { 1967 copy = {}; 1968 for (var attr in obj) { 1969 if (obj.hasOwnProperty(attr)) { 1970 if("function" == typeof obj[attr]) { 1971 continue; 1972 } 1973 copy[attr] = Utils.clone(obj[attr]); 1974 } 1975 } 1976 return copy; 1977 } 1978 1979 return obj;
1980 }, 1981 1982 // Returns array of elements with given tag name 1983 getTagsAsArray: function (tag) { 1984 return [].slice.call(document.getElementsByTagName(tag)); 1985 }, 1986 1987 1988 initializeVideoAPIs: function (options) { 1989 let trigger_has_video_YT, trigger_has_video_Vimeo = false; 1990 if (options.hasOwnProperty('automatic') && options.automatic.enable_video) { 1991 if ( options.triggerEventTypes.hasOwnProperty( "video_view" ) ) { 1992 Object.entries( options.triggerEventTypes.video_view ).forEach( function ( [ trigger_id, triggers ] ) { 1993 triggers.forEach( function ( trigger ) { 1994 if (trigger.type === 'youtube') { 1995 trigger_has_video_YT = true; 1996 } 1997 if(trigger.type === 'vimeo'){ 1998 trigger_has_video_Vimeo = true; 1999 } 2000 }); 2001 }); 2002 } 2003 if ( 2004 options.hasOwnProperty('enable_automatic_events') && 2005 options.enable_automatic_events 2006 ) { 2007 if (options.automatic.enable_youtube || trigger_has_video_YT) { 2008 Utils.initYouTubeAPI(); 2009 } 2010 if (options.automatic.enable_vimeo || trigger_has_video_Vimeo) { 2011 Utils.initVimeoAPI(); 2012 } 2013 initHtml5VideoTracking(); 2014 } 2015 } 2016 2017 // Also init YT/Vimeo APIs for video_speed custom triggers and global VideoSpeed event 2018 var has_video_speed_trigger = options.triggerEventTypes && options.triggerEventTypes.hasOwnProperty('video_speed') && 2019 Object.keys(options.triggerEventTypes.video_speed).length > 0; 2020 var has_global_video_speed = options.dynamicEvents && options.dynamicEvents.hasOwnProperty('automatic_event_video_speed'); 2021 if (has_video_speed_trigger || has_global_video_speed) { 2022 Utils.initYouTubeAPI(); 2023 Utils.initVimeoAPI(); 2024 } 2025 }, 2026 /** 2027 * Load and initialize YouTube API 2028 * 2029 * @link: https://developers.google.com/youtube/iframe_api_reference 2030 */ 2031 initYouTubeAPI: function () { 2032 2033 // maybe load YouTube JS API 2034 if (typeof window.YT === 'undefined') { 2035 var tag = document.createElement('script'); 2036 tag.src = '//www.youtube.com/iframe_api'; 2037 var firstScriptTag = document.getElementsByTagName('script')[0]; 2038 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 2039 } 2040 2041 // initialize when API is ready 2042 if ( typeof window.onYouTubeIframeAPIReady !== 'function' ) { 2043 2044 window.onYouTubeIframeAPIReady = function () { 2045 2046 // collect all possible YouTube tags 2047 var potentialVideos = Utils.getTagsAsArray( 'iframe' ).concat(
2047Utils.getTagsAsArray( 'embed' ) ); 2048 2049 // turn videos into trackable videos with events 2050 for ( var i = 0; i < potentialVideos.length; i++ ) { 2051 var video = potentialVideos[ i ]; 2052 if ( tagIsYouTubeVideo( video ) ) { 2053 var iframe = normalizeYouTubeIframe( video ); 2054 addYouTubeEvents( iframe ); 2055 } else if ( tagIsYouTubeAsyncVideo( video ) ){ 2056 video.addEventListener( "load", function ( evt ) { 2057 var iframe = normalizeYouTubeIframe( evt.currentTarget ); 2058 addYouTubeEvents( iframe ); 2059 } ); 2060 } 2061 } 2062 2063 var targets = document.querySelectorAll( '.elementor-widget-video .elementor-wrapper' ); 2064 2065 const config = { 2066 attributes: false, 2067 childList: true, 2068 subtree: true 2069 }; 2070 2071 const callback = function ( mutationsList, observer ) { 2072 for ( let mutation of mutationsList ) { 2073 if ( mutation.type === 'childList' ) { 2074 for ( var m = 0; m < mutation.addedNodes.length; m++ ) { 2075 addDynYouTubeVideos( mutation.addedNodes[ m ] ); 2076 } 2077 } 2078 } 2079 }; 2080 // observe elementator widget-video and add event when it add iframe 2081 for ( var i = 0; i < targets.length; i++ ) { 2082 const observer = new MutationObserver( callback ); 2083 observer.observe( targets[ i ], config );//maybe remove before add 2084 } 2085 } 2086 } 2087 }, 2088 2089 /** 2090 * Load and initialize Vimeo API 2091 * 2092 * @link: https://github.com/vimeo/player.js 2093 */ 2094 initVimeoAPI: function () { 2095 2096 2097 $(document).ready(function () { 2098 2099 var potentialVideos = Utils.getTagsAsArray('iframe').concat(Utils.getTagsAsArray('embed')); 2100 2101 for (var i = 0; i < potentialVideos.length; i++) { 2102 var tag = potentialVideos[i]; 2103 if (tagIsVimeoVideo(tag)) { 2104 var nextElementHasClass = $(tag).closest('.elementor-widget-video').find('.elementor-custom-embed-image-overlay'); 2105 if(nextElementHasClass.length > 0) { 2106 tag.addEventListener("load", function() { 2107 attachVimeoPlayerToTag(this); 2108 }); 2109 } 2110 else { 2111 attachVimeoPlayerToTag(tag); 2112 } 2113 2114 2115 } else { 2116 if (tagIsAsincVimeoVideo(tag)) { 2117 tag.addEventListener("load", function(evt) { 2118 attachVimeoPlayerToTag(evt.currentTarget); 2119 }); 2120 } 2121 } 2122 } 2123 2124 }); 2125 2126 }, 2127 2128 /** 2129 * Ask PHP for this browser's external_id (single-flight). 2130 * 2131 * The cookie keeps the id across a network change, so it is 2132 * requested whenever external_id is on -- not only when server 2133 * events go over AJAX. Returns the in-flight jqXHR, or null when no 2134 * request is warranted (feature off, cookie already there, no 2135 * Meta pixel). 2136 */ 2137 requestPbid: function () { 2138 if (!options.send_external_id || Cookies.get('pbid') || !Facebook.isEnabled()) { 2139 return null; 2140 } 2141 2142 if (!pbidRequest) { 2143 pbidRequest = jQuery.ajax({ 2144 url: options.ajaxUrl, 2145 dataType: 'json', 2146 data: { 2147 action: 'pys_get_pbid' 2148 }, 2149 success: function (res) { 2150 if (res.data && res.data.pbid != false) { 2151 if(!(options.cookie.disabled_all_cookie || options.cookie.externalID_disabled_by_api)){ 2152 var expires = parseInt(options.external_id_expire || 180); 2153 Cookies.set('pbid', res.data.pbid, { expires: expires,path: '/',domain: cookieDomain() }); 2154 } 2155 2156 if(options.hasOwnProperty('facebook')) { 2157 options.facebook.advancedMatching = { 2158 ...options.facebook.advancedMatching, // spread current advancedMatching values 2159 external_id: res.data.pbid 2160 }; 2161 } 2162 } 2163 } 2164 }).always(function () { 2165 pbidRequest = null; 2166 }); 2167 } 2168 2169 return pbidRequest; 2170 }, 2171 2172 /** 2173 * Defer `fn` until this browser's external_id is known, or until a 2174 * short ceiling passes. Returns true when `fn` was deferred (it will 2175 * be called back), false when the caller may go ahead right now. 2176 * 2177 * Pixels read external_id when they initialise. On a first visit the 2178 * cookie does not exist yet, and the inline value in the page may 2179 * belong to whoever rendered a cached copy of it; the reply to 2180 * pys_get_pbid is never cached and names this browser, so the first 2181 * PageView waits for it. Sites that fetch identity over REST already 2182 * have the right value by now and do not wait again. 2183 */ 2184 holdForIdentity: function (fn) { 2185 if (pbidSettled || !options.send_external_id || Cookies.get('pbid')) { 2186 return false; 2187 } 2188 2189 if (options.dynamicDataUrl && options.facebook && options.facebook.advancedMatching 2190 && options.facebook.advancedMatching.external_id) { 2191 return false; 2192 } 2193 2194 var request = Utils.requestPbid(); 2195 if (!request) { 2196 return false; 2197 } 2198 2199 var done = false; 2200 var finish = function () { 2201 if (done) { 2202 return; 2203 } 2204 done = true; 2205 pbidSettled = true; 2206 fn(); 2207 }; 2208 2209 request.always(finish); 2210 setTimeout(finish, PBID_WAIT_MS); 2211 2212 return true; 2213 }, 2214 2215 manageCookies: function () { 2216 if ( typeof options.cookie === 'undefined' ) { 2217 return; 2218 } 2219 2220 // Handle alternative GCLID parameter for Safari compatibility 2221 handleAlternativeGclid(); 2222 2223 if (options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined') { 2224 if (Cookiebot.consented === false && !Cookiebot.consent['marketing'] && !Cookiebot.consent['statistics']) { 2225 return; 2226 } 2227 } 2228 2229 let cm_consent_not_expressed = false; 2230 if ( options.gdpr.consent_magic_integration_enabled && window.CS_Data !== undefined && window.CS_Data.cs_refresh_after_consent == 1 ) { 2231 if ( Cookies.get( 'cs_viewed_cookie_policy' ) === undefined ) { 2232 cm_consent_not_expressed = true; 2233 } 2234 } 2235 2236 // Hand PHP the browser-verified cookie domain, under the same 2237 // consent gates as every cookie written below it. 2238 if( !cm_consent_not_expressed && !options.cookie.disabled_all_cookie ) { 2239 reportCookieDomain(cookieDomain()); 2240 } 2241 2242 if( !cm_consent_not_expressed && isNewSession && !options.cookie.disabled_all_cookie && !options.cookie.disabled_start_session_cookie) { 2243 let duration = options.last_visit_duration * 60000 2244 var now = new Date(); 2245 now.setTime(now.getTime() + duration); 2246 Cookies.set('pys_session_limit', true,{ expires: now, path: '/',domain: cookieDomain() }) 2247 Cookies.set('pys_start_session', true,{ path: '/',domain: cookieDomain() }); 2248 Utils.setHidePixelCookie(); 2249 } 2250 2251 if (!Cookies.get('pbid')) { 2252 Utils.requestPbid(); 2253 } else if (Facebook.isEnabled()){ 2254 if(options.hasOwnProperty('facebook')) { 2255 options.facebook.advancedMatching = { 2256 ...options.facebook.advancedMatching, 2257 external_id: Cookies.get('pbid') 2258 }; 2259 } 2260 } 2261 let expires = parseInt(options.cookie_duration); // days 2262 let queryVars = getQueryVars(); 2263 let landing = window.location.href; 2264 try { 2265 // save data for first visit 2266 if(Cookies.get('pys_first_visit') === undefined && (!options.cookie.disabled_all_cookie)) { 2267 2268 if(!options.cookie.disabled_first_visit_cookie) 2269 { 2270 Cookies.set('pys_first_visit', true, { expires: expires, path: '/',domain: cookieDomain() }); 2271 } 2272 else { 2273 Cookies.remove('pys_first_visit', { path: '/',domain: cookieDomain() } ) 2274 } 2275 2276 if(!options.cookie.disabled_trafficsource_cookie) 2277 { 2278 Cookies.set('pysTrafficSource', getTrafficSource(), { expires: expires,path: '/',domain: cookieDomain() }); 2279 } 2280 else { 2281 Cookies.remove('pysTrafficSource', { path: '/',domain: cookieDomain() }) 2282 } 2283 2284 if(!options.cookie.disabled_landing_page_cookie && options.enable_lading_page_param) 2285 { 2286 Cookies.set('pys_landing_page',landing,{ expires: expires,path: '/',domain: cookieDomain() }); 2287 } 2288 else { 2289 Cookies.remove('pys_landing_page', { path: '/',domain: cookieDomain() }) 2290 } 2291 2292 if(!options.cookie.disabled_utmTerms_cookie) 2293 { 2294 $.each(utmTerms, function (index, name) { 2295 if (queryVars.hasOwnProperty(name)) { 2296 Cookies.set('pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() }); 2297 } else { 2298 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 2299 } 2300 }); 2301 } 2302 else { 2303 $.each(utmTerms, function (index, name) { 2304 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 2305 }); 2306 } 2307 2308 if(!options.cookie.disabled_utmId_cookie) 2309 { 2310 $.each(utmId,function(index,name) { 2311 if (queryVars.hasOwnProperty(name)) { 2312 Cookies.set('pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() }); 2313 } else { 2314 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 2315 } 2316 }) 2317 } 2318 else { 2319 $.each(utmId, function (index, name) { 2320 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 2321 }); 2322 } 2323 2324 } 2325 // save data for last visit if it new session 2326 if(isNewSession && (!options.cookie.disabled_all_cookie)) { 2327 if(!options.cookie.disabled_trafficsource_cookie) 2328 { 2329 Cookies.set('last_pysTrafficSource', getTrafficSource(), { expires: expires,path: '/',domain: cookieDomain() }); 2330 } 2331 else { 2332 Cookies.remove('last_pysTrafficSource', { path: '/',domain: cookieDomain() }) 2333 } 2334 2335 if(!options.cookie.disabled_landing_page_cookie && options.enable_lading_page_param) 2336 { 2337 Cookies.set('last_pys_landing_page',landing,{ expires: expires,path: '/',domain: cookieDomain() }); 2338 } 2339 else { 2340 Cookies.remove('last_pys_landing_page', { path: '/',domain: cookieDomain() }) 2341 } 2342 2343 if(!options.cookie.disabled_utmTerms_cookie) 2344 { 2345 $.each(utmTerms, function (index, name) { 2346 if (queryVars.hasOwnProperty(name)) { 2347 Cookies.set('last_pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() }); 2348 } 2349 }); 2350 } 2351 else { 2352 $.each(utmTerms, function (index, name) { 2353 Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() }) 2354 }); 2355 } 2356 2357 if(!options.cookie.disabled_utmId_cookie) 2358 { 2359 $.each(utmId,function(index,name) { 2360 if (queryVars.hasOwnProperty(name)) { 2361 Cookies.set('last_pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() }); 2362 } 2363 }) 2364 } 2365 else { 2366 $.each(utmId, function (index, name) { 2367 Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() }) 2368 }); 2369 } 2370 2371 } 2372 if(options.cookie.disabled_start_session_cookie) { 2373 Cookies.remove('pys_start_session', { path: '/',domain: cookieDomain() }) 2374 Cookies.remove('pys_session_limit', { path: '/',domain: cookieDomain() }) 2375 } 2376 if(options.cookie.disabled_all_cookie) 2377 { 2378 Cookies.remove('pys_first_visit', { path: '/',domain: cookieDomain() }) 2379
2379 Cookies.remove('pysTrafficSource', { path: '/',domain: cookieDomain() }) 2380 Cookies.remove('pys_landing_page', { path: '/',domain: cookieDomain() }) 2381 Cookies.remove('last_pys_landing_page', { path: '/',domain: cookieDomain() }) 2382 Cookies.remove('last_pysTrafficSource', { path: '/',domain: cookieDomain() }) 2383 Cookies.remove('pys_start_session', { path: '/',domain: cookieDomain() }) 2384 Cookies.remove('pys_session_limit', { path: '/',domain: cookieDomain() }) 2385 $.each(Utils.utmTerms, function (index, name) { 2386 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 2387 }); 2388 $.each(Utils.utmId,function(index,name) { 2389 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 2390 }) 2391 $.each(Utils.utmTerms, function (index, name) { 2392 Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() }) 2393 }); 2394 $.each(Utils.utmId,function(index,name) { 2395 Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() }) 2396 }); 2397 } 2398 } catch (e) { 2399 console.error(e); 2400 } 2401 }, 2402
2403 initializeRequestParams: function () { 2404 2405 if (options.trackTrafficSource) { 2406 requestParams.traffic_source = getTrafficSourceValue(); 2407 } 2408 2409 if (options.trackUTMs) { 2410 2411 var utms = getUTMs(options.visit_data_model === "last_visit"); 2412 2413 $.each(utmTerms, function (index, term) { 2414 if (term in utms) { 2415 requestParams[term] = utms[term]; 2416 } 2417 }); 2418 2419 } 2420 2421 2422 var dateTime = getDateTime(); 2423 if(options.enable_event_time_param) { 2424 requestParams.event_hour = dateTime[0]; 2425 } 2426 2427 if(options.enable_event_day_param) { 2428 requestParams.event_day = dateTime[1]; 2429 } 2430 if(options.enable_event_month_param) { 2431 requestParams.event_month = dateTime[2]; 2432 } 2433 2434 if(options.enable_lading_page_param){ 2435 requestParams.landing_page = stripUrlQuery(getLandingPageValue()); 2436 } 2437 }, 2438
2439 getRequestParams: function () { 2440 return requestParams; 2441 }, 2442 2443 /** 2444 * DOWNLOAD DOCS 2445 */ 2446 2447 getLinkExtension: function (link) { 2448 2449 // Remove anchor, query string and everything before last slash 2450 link = link.substring(0, (link.indexOf("#") === -1) ? link.length : link.indexOf("#")); 2451 link = link.substring(0, (link.indexOf("?") === -1) ? link.length : link.indexOf("?")); 2452 link = link.substring(link.lastIndexOf("/") + 1, link.length); 2453 2454 // If there's a period left in the URL, then there's a extension 2455 if (link.length > 0 && link.indexOf('.') !== -1) { 2456 link = link.substring(link.lastIndexOf(".") + 1); // Remove everything but what's after the first period 2457 return link; 2458 } else { 2459 return ""; 2460 } 2461 }, 2462 2463 getLinkFilename: function (link) { 2464 2465 // Remove anchor, query string and everything before last slash 2466 link = link.substring(0, (link.indexOf("#") === -1) ? link.length : link.indexOf("#")); 2467 link = link.substring(0, (link.indexOf("?") === -1) ? link.length : link.indexOf("?")); 2468 link = link.substring(link.lastIndexOf("/") + 1, link.length); 2469 2470 // If there's a period left in the URL, then there's a extension 2471 if (link.length > 0 && link.indexOf('.') !== -1) { 2472 return link; 2473 } else { 2474 return ""; 2475 } 2476 }, 2477 2478 /** 2479 * CUSTOM EVENTS 2480 */ 2481 2482 setupMouseOverClickEvents: function (eventId, triggers, triggerType) { 2483 2484 // Non-default binding used to avoid situations when some code in external js 2485 // stopping events propagation, eg. returns false, and our handler will never called 2486 document.addEventListener('mouseover', function(event) { 2487 var matchedElements = Array.from(document.querySelectorAll(triggers)); 2488 var clickedElement = event.target; 2489 var closestMatch = clickedElement.closest(triggers); 2490 if (matchedElements.includes(clickedElement) || closestMatch){ 2491 if (event.target.classList.contains('pys-mouse-over-' + eventId)) { 2492 return true; 2493 } else { 2494 event.target.classList.add('pys-mouse-over-' + eventId); 2495 } 2496 2497 Utils.handleTriggerFired(eventId, triggerType || 'css_mouseover'); 2498 } 2499 }); 2500 2501 2502 }, 2503 2504 setupFormFieldEvents: function (eventId, triggers, triggerType) { 2505 triggers.forEach(function(trigger) { 2506 let eventType = 'input'; 2507 2508 if (['checkbox', 'radio', 'select'].includes(trigger.type)) { 2509 eventType = 'change'; 2510 } else if (['text', 'email', 'tel'].includes(trigger.type)) { 2511 eventType = 'blur'; 2512 } 2513 2514 const form = document.querySelector(trigger.form); 2515 if (!form) return; 2516 2517 const field = form.querySelector(trigger.field); // search for the field ONLY within the form 2518 if (!field) return; 2519 2520 field.addEventListener(eventType, function (event) { 2521 Utils.handleTriggerFired(eventId, triggerType || 'form_field'); 2522 }, true); 2523 }); 2524 }, 2525 2526 setupCSSClickEvents: function (eventId, triggers, triggerType, clickCount, clickTimeLimit) { 2527 // Non-default binding used to avoid situations when some code in external js 2528 // stopping events propagation, eg. returns false, and our handler will never called
2529 // add event to document to support dyn class 2530 var requiredClicks = (clickCount && clickCount > 1) ? clickCount : 1; 2531 var timeWindow = (clickTimeLimit && clickTimeLimit > 0) ? clickTimeLimit : 0; 2532 var clickCounters = typeof WeakMap !== 'undefined' ? new WeakMap() : null; 2533 2534 document.addEventListener('click', function(event) { 2535 let matchedElements = Array.from(document.querySelectorAll(triggers)), 2536 clickedElement = event.target, 2537 closestMatch = clickedElement.closest(triggers); 2538 2539 if (matchedElements.includes(clickedElement) || closestMatch) { 2540 var target = closestMatch || clickedElement; 2541 2542 if (requiredClicks <= 1) { 2543 Utils.handleTriggerFired(eventId, triggerType || 'css_click'); 2544 return; 2545 } 2546 2547 if (!clickCounters) { 2548 Utils.handleTriggerFired(eventId, triggerType || 'css_click'); 2549 return; 2550 } 2551 2552 var now = Date.now(); 2553 var state = clickCounters.get(target) || { count: 0, last: 0, timer: null }; 2554 2555 // When a time window is set, reset the counter if the gap exceeded it. 2556 if (timeWindow > 0 && now - state.last > timeWindow) { 2557 state.count = 0; 2558 } 2559 state.count++; 2560 state.last = now; 2561 2562 if (state.timer) clearTimeout(state.timer); 2563 // Schedule cleanup to avoid memory leaks.
2564 // With time window: delete entry after window expires. 2565 // Without: quietly clear the timer reference after a long idle period. 2566 state.timer = setTimeout(function() { 2567 if (clickCounters) { 2568 if (timeWindow > 0) { 2569 clickCounters.delete(target); 2570 } else { 2571 var s = clickCounters.get(target); 2572 if (s) { s.timer = null; clickCounters.set(target, s); } 2573 } 2574 } 2575 }, timeWindow > 0 ? timeWindow : 60000); 2576 2577 clickCounters.set(target, state); 2578 2579 if (state.count >= requiredClicks) { 2580 clickCounters.delete(target); 2581 Utils.handleTriggerFired(eventId, triggerType || 'css_click'); 2582 } 2583 } 2584 }, true); 2585 }, 2586 2587 setupCopyElementEvents: function (eventId, selectors) { 2588 var selectorList = Array.isArray(selectors) ? selectors.join(',') : selectors; 2589 if (!selectorList) return; 2590 2591 document.addEventListener('copy', function() { 2592 var selection = window.getSelection(); 2593 if (!selection || selection.isCollapsed) return; 2594 2595 var node = selection.focusNode; 2596 var el = node ? (node.nodeType === 3 ? node.parentElement : node) : null; 2597 if (!el) return; 2598 2599 try { 2600 if (el.closest(selectorList)) { 2601 Utils.handleTriggerFired(eventId, 'copy_element'); 2602 } 2603 } catch (e) {} 2604 }); 2605 }, 2606 2607 setupVideoSpeedEvents: function (eventId, config) { 2608 var urls = Array.isArray(config) ? config : (config.urls || []); 2609 var speedRate = Array.isArray(config) ? 'any' : (config.speed_rate || 'any'); 2610 var triggers = Array.isArray(config) ? [] : (config.triggers || []); 2611 2612 var matchesPage = function() { 2613 if (!urls || urls.length === 0) return false; 2614 var href = window.location.href; 2615 return urls.some(function(u) { 2616 return u === '*' || u === '' || href.indexOf(u) !== -1; 2617 }); 2618 }; 2619 2620 var matchesRate = function(prev, curr) { 2621 if (speedRate === 'any') return curr !== prev; 2622 if (speedRate === 'increase') return curr > prev; 2623 if (speedRate === 'decrease') return curr < prev; 2624 return false; 2625 }; 2626 2627 2628 var matchesVideoId = function(videoId) { 2629 if (!triggers || triggers.length === 0) return true; 2630 var decId; 2631 try { decId = decodeURIComponent(videoId); } catch(e) { decId = videoId; } 2632 return triggers.some(function(t) { 2633 if (t.rule === videoId || t.rule === String(videoId)) return true; 2634 var decRule; 2635 try { decRule = decodeURIComponent(t.rule); } catch(e) { decRule = t.rule; } 2636 return decRule === decId; 2637 }); 2638 }; 2639 2640 if (!matchesPage()) return; 2641 2642 // HTML5 <video> speed tracking 2643 document.addEventListener('ratechange', function(event) { 2644 var video = event.target; 2645 if (!video || video.tagName !== 'VIDEO') return; 2646 var prev = video._pysSpeedRate !== undefined ? video._pysSpeedRate : 1; 2647 var curr = video.playbackRate; 2648 video._pysSpeedRate = curr; 2649 2650 if (matchesRate(prev, curr)) { 2651 var videoId = video.id || video.src || video.currentSrc || ''; 2652 if (matchesVideoId(videoId)) { 2653 Utils.handleTriggerFired(eventId, 'video_speed'); 2654 } 2655 } 2656 }, true); 2657 }, 2658 2659 setupURLClickEvents: function () { 2660 2661 if( !options.triggerEventTypes.hasOwnProperty('url_click') ) { 2662 return; 2663 } 2664 // Non-default binding used to avoid situations when some code in external js 2665 // stopping events propagation, eg. returns false, and our handler will never called 2666 document.addEventListener('click', function(event) { 2667 let anchor = event.target.closest('a'); 2668 2669 if (anchor) { 2670 var url = anchor.getAttribute('href'); 2671 if (url) { 2672 Object.entries(options.triggerEventTypes.url_click).forEac
2672h(function ([eventId, triggers]) { 2673 triggers.forEach(function (trigger) { 2674 if (Utils.compareUrl(url, trigger.value, trigger.rule)) { 2675 Utils.handleTriggerFired(eventId, 'url_click'); 2676 } 2677 }); 2678 }); 2679 } 2680 } 2681 }, true); 2682 2683 }, 2684 2685 removeUrlDomain(url) { 2686 if(url.indexOf("/#") > -1) { 2687 url = url.substring(0, url.indexOf("/#")); 2688 } 2689 return url.replace('http://','') 2690 .replace('https://','') 2691 .replace('www.','') 2692 .trim() 2693 .replace(/^\/+/g, '') 2694 2695 }, 2696 2697 compareUrl: function(base,url,rule){ 2698 2699 if(url == "*" || url == '') return true; 2700 2701 base = Utils.removeUrlDomain(base) 2702 url = Utils.removeUrlDomain(url) 2703 2704 if(rule == 'match') { 2705 return url == base; 2706 } else { 2707 return base.indexOf(url) !== -1 2708 } 2709 2710 }, 2711 2712 /** 2713 * Evaluate the custom-event conditions the server refused to decide. 2714 * 2715 * A condition on the landing page or the traffic source depends on the 2716 * individual visitor, so deciding it in PHP would bake one visitor's 2717 * answer into HTML that a page cache serves to everybody. PHP therefore 2718 * ships the condition itself in the event payload and we answer it here, 2719 * from this visitor's own cookies. See ConditionalEvent:: 2720 * isDeferredToFrontEnd() and CustomEvent::resolveConditions(). 2721 * 2722 * PHP only attaches pendingConditions when the answer can still change 2723 * the outcome, so an event carrying them and failing here must not fire. 2724 * 2725 * @param event one pixel's copy of the event; the payload is the same in all of them 2726 * @returns {boolean} 2727 */ 2728 conditionsPass: function (event) { 2729 2730 if (!event || !event.pendingConditions) return true; 2731 2732 var pending = event.pendingConditions, 2733 conditions = pending.conditions || []; 2734 2735 if (!conditions.length) return true; 2736 2737 var results = []; 2738 2739 for (var i = 0; i < conditions.length; i++) { 2740 results.push(Utils.checkPendingCondition(conditions[i])); 2741 } 2742 2743 if (pending.logic === 'AND') { 2744 return results.indexOf(false) === -1; 2745 } 2746 return results.indexOf(true) !== -1; 2747 }, 2748 2749 /** 2750 * @returns {boolean} 2751 */ 2752 checkPendingCondition: function (condition) { 2753 2754 var rule = condition.rule, 2755 value = condition.value; 2756 2757 // The PHP rule compares rtrim($base, '/') with rtrim($url, '/'), while 2758 // Utils.compareUrl() below compares exactly. That helper is shared with 2759 // trigger matching, so normalise here instead of changing it. Skipped 2760 // when trimming would empty the value, because compareUrl() reads an 2761 // empty needle as "anything matches" and PHP does not. 2762 var matchExact = function (base) { 2763 var needle = String(value == null ? '' : value), 2764 trimmed = needle.replace(/\/+$/, ''); 2765 if (trimmed === '') { 2766 return Utils.compareUrl(base, needle, 'match'); 2767 } 2768 return Utils.compareUrl(String(base == null ? '' : base).replace(/\/+$/, ''), trimmed, 'match'); 2769 }; 2770 2771 switch (condition.type) { 2772 2773 case 'landing_page': 2774 var landing = getLandingPageValue(); 2775 if (rule === 'param_contains' || rule === 'param_match') { 2776 return Utils.compareUrlParams(landing, value, rule); 2777 } 2778 landing = stripUrlQuery(landing); 2779 return rule === 'match' ? matchExact(landing) : Utils.compareUrl(landing, value, rule); 2780 2781 case 'source': 2782 var source = getTrafficSourceValue(); 2783 return rule === 'match' ? matchExact(source) :
2783Utils.compareUrl(source, value, rule); 2784 2785 case 'url_parameters': 2786 // The stored rule is contains/match; PHP prefixes it before 2787 // comparing â compareURLs( $value, '', 'param_' . $rule ) â so 2788 // the comparison always runs against the query string, never 2789 // the path. The query string of *this* request is the point: 2790 // the cached HTML was built for whoever arrived first. 2791 return Utils.compareUrlParams(window.location.href, value, 'param_' + rule); 2792 2793 default: 2794 // A condition type this build cannot judge. PHP deferred it, 2795 // so nobody has decided it â fire rather than swallow the 2796 // event: a missing event is invisible in the reports, a 2797 // superfluous one is not. 2798 return true; 2799 } 2800 }, 2801 2802 /** 2803 * The param_match / param_contains rules of the PHP side, which apply to 2804 * the query string of the landing page rather than to its path. 2805 * 2806 * param_match: any "key=value" pair in the condition matches the URL. 2807 * param_contains: the condition text appears in any parameter name or 2808 * value, case-insensitively. 2809 * 2810 * @returns {boolean} 2811 */ 2812 compareUrlParams: function (url, value, rule) { 2813 2814 value = String(value == null ? '' : value).replace(/\*+$/, ''); 2815 2816 if (value === '' || value === '*') return true; 2817 2818 // PHP parses both sides with parse_str(), which rewrites spaces and 2819 // dots in parameter *names* to underscores. Mirror that, or a 2820 // condition on "a.b" would never meet the "a_b" the server sees. 2821 var normaliseKey = function (raw) { 2822 return decodeURIComponent(String(raw).replace(/\+/g, ' ')).replace(/[ .]/g, '_'); 2823 }; 2824 2825 var query = String(url == null ? '' : url).split('#')[0].split('?')[1] || '', 2826 params = {}; 2827 2828 query.replace(/&/g, '&').split('&').forEach(function (pair) { 2829 if (!pair) return; 2830 var parts = pair.split('='); 2831 params[normaliseKey(parts[0])] = 2832 decodeURIComponent((parts.slice(1).join('=') || '').replace(/\+/g, ' ')); 2833 }); 2834 2835 if (rule === 'param_match') { 2836 var wanted = value.split('&'); 2837 for (var i = 0; i < wanted.length; i++) { 2838 var parts = wanted[i].split('='), 2839 key = normaliseKey(parts[0]), 2840 expected = decodeURIComponent((parts.slice(1).join('=') || '').replace(/\+/g, ' ')); 2841 if (params.hasOwnProperty(key) && params[key] === expected) { 2842 return true; 2843 } 2844 } 2845 return false; 2846 } 2847 2848 var needle = value.toLowerCase(); 2849 for (var name in params) { 2850 if (!params.hasOwnProperty(name)) continue; 2851 if (name.toLowerCase().indexOf(needle) !== -1) return true; 2852 if (String(params[name]).toLowerCase().indexOf(needle) !== -1) return true; 2853 } 2854 return false; 2855 }, 2856 2857 setupScrollPosEvents: function (eventId, triggers, triggerType) { 2858 2859 var scrollPosThresholds = {}, 2860 docHeight = $(document).height() - $(window).height(); 2861 2862 // convert % to absolute positions 2863 $.each(triggers, function (index, scrollPos) { 2864 2865 // convert % to pixels 2866 scrollPos = docHeight * scrollPos / 100; 2867 scrollPos = Math.round(scrollPos); 2868 2869 scrollPosThresholds[scrollPos] = eventId; 2870 2871 }); 2872 2873 $(document).on("scroll",function () { 2874 2875 var scrollPos = $(window).scrollTop(); 2876 2877 $.each(scrollPosThresholds, function (threshold, eventId) { 2878 2879 // position has not reached yes 2880 if (scrollPos < threshold - 0.5) { 2881 return true; 2882 } 2883 2884 // fire event only once 2885 if (eventId === null) { 2886 return true; 2887 } else { 2888 scrollPosThresholds[threshold] = null; 2889 } 2890 2891 Utils.handleTriggerFired(eventId, triggerType || 'scroll_pos'); 2892 }); 2893 2894 }); 2895 2896 }, 2897 setupCommentEvents : function (eventId,triggers) { 2898 $('form.comment-form').on("submit",function () { 2899 Utils.fireTriggerEvent(eventId); 2900 }); 2901 }, 2902 2903 setupEmailLinkEvents: function ( events ) { 2904 2905 $( document ).on( 'click', 'a', function ( event ) { 2906 let anchor = event.target.closest('a'); 2907 2908 if (anchor) { 2909 let url = anchor.getAttribute('href'); 2910 if (url && (url.startsWith('mailto:') || url.startsWith('tel:'))) { 2911 2912 let sendEventIds = [], 2913 disabled_for_pixels = [], 2914 disabled_email_action = false; 2915 2916 $.each(events, function (eventId, trigger_group) { 2917 url = url.replace('mailto:', '').replace('tel:', ''); 2918 trigger_group.forEach(function (triggers) {
2919 triggers.rules.forEach(function (trigger) { 2920 if (Utils.compareUrl(url, trigger.value, trigger.rule)) { 2921 sendEventIds.push(eventId); 2922 if (triggers.hasOwnProperty('disabled_email_link') && triggers.disabled_email_link) { 2923 disabled_email_action = true; 2924 if (options.triggerEvents.hasOwnProperty(eventId)) { 2925 disabled_for_pixels.push(...Object.keys(options.triggerEvents[eventId])); 2926 } 2927 } 2928 } 2929 }); 2930 }) 2931 }) 2932 2933 if (sendEventIds.length > 0) { 2934 sendEventIds.forEach(function (sendEventId) { 2935 Utils.handleTriggerFired(sendEventId, 'email_link'); 2936 }) 2937 } 2938 setupEmailLinks(disabled_for_pixels); 2939 } 2940 } 2941 } ) 2942 }, 2943 2944 /** 2945 * Events 2946 */ 2947 2948 isEventInTimeWindow: function (eventName, event, prefix) { 2949 2950 if(event.hasOwnProperty("hasTimeWindow") && event.hasTimeWindow) { 2951 var cookieName = prefix+"_"+eventName; 2952 var now = new Date().getTime(); 2953 2954 if(Cookies.get(cookieName) !== undefined) { 2955 2956 var lastTimeFire = Cookies.get(cookieName); 2957 var fireTime = event.timeWindow * 60*60*1000; 2958 2959 if( now - lastTimeFire > fireTime) { 2960 Cookies.set(cookieName,now, { expires: event.timeWindow / 24.0, path: '/'} ); 2961 } else { 2962 return false; 2963 } 2964 } else { 2965 Cookies.set(cookieName,now, { expires: event.timeWindow / 24.0, path: '/'} ); 2966 } 2967 } 2968 return true 2969 }, 2970 2971 handleTriggerFired: function (eventId, triggerType) { 2972 if (options.triggerLogic && options.triggerLogic[eventId] === 'AND') { 2973 var state = pysCustomEventsState[eventId]; 2974 if (!state || state.fired) return; 2975 state.triggered[triggerType] = true; 2976 if (Object.keys(state.triggered).length >= state.total) { 2977 state.fired = true; 2978 Utils.fireTriggerEvent(eventId); 2979 } 2980 } else { 2981 Utils.fireTriggerEvent(eventId); 2982 } 2983 }, 2984 2985 fireTriggerEvent: function (eventId) { 2986 2987 if (!options.triggerEvents.hasOwnProperty(eventId)) { 2988 return; 2989 } 2990 2991 if (options.triggerFrequency && options.triggerFrequency[eventId] === 'once') { 2992 if (pysEventFiredOnce[eventId]) return; 2993 pysEventFiredOnce[eventId] = true; 2994 } 2995 2996 var event = {}; 2997 var events = options.triggerEvents[eventId]; 2998 2999 // One gate for every pixel: they all carry the same payload. 3000 for (var pixelSlug in events) { 3001 if (!events.hasOwnProperty(pixelSlug)) continue; 3002 if (!Utils.conditionsPass(events[pixelSlug])) return; 3003 break; 3004 } 3005 3006 if (events.hasOwnProperty('facebook')) { 3007 event = events.facebook; 3008 if(Utils.isEventInTimeWindow(event.name,event,"dyn_facebook_"+eventId)) { 3009 event = Utils.getFormFilledData(event); 3010 Facebook.fireEvent(event.name, event); 3011 } 3012 } 3013 3014 if (events.hasOwnProperty('ga')) { 3015 event = events.ga;
3016 if(Utils.isEventInTimeWindow(event.name,event,"dyn_ga_"+eventId)) { 3017 event = Utils.getFormFilledData(event); 3018 Analytics.fireEvent(event.name, event); 3019 } 3020 } 3021 3022 if (events.hasOwnProperty('google_ads')) { 3023 event = events.google_ads; 3024 if(Utils.isEventInTimeWindow(event.name,event,"dyn_google_ads_"+eventId)) { 3025 event = Utils.getFormFilledData(event); 3026 GAds.fireEvent(event.name, event); 3027 } 3028 } 3029 3030 if (events.hasOwnProperty('pinterest')) { 3031 event = events.pinterest; 3032 if(Utils.isEventInTimeWindow(event.name,event,"dyn_pinterest_"+eventId)) { 3033 event = Utils.getFormFilledData(event); 3034 Pinterest.fireEvent(event.name, event);; 3035 } 3036 } 3037 3038 if (events.hasOwnProperty('bing')) { 3039 event = events.bing; 3040 if(Utils.isEventInTimeWindow(event.name,event,"dyn_bing_"+eventId)) { 3041 event = Utils.getFormFilledData(event); 3042 Bing.fireEvent(event.name, event);; 3043 } 3044 } 3045 if (events.hasOwnProperty('tiktok')) { 3046 event = events.tiktok; 3047 if(Utils.isEventInTimeWindow(event.name,event,"dyn_tiktok_"+eventId)) { 3048 event = Utils.getFormFilledData(event); 3049 TikTok.fireEvent(event.name, event); 3050 } 3051 } 3052 if (events.hasOwnProperty('reddit')) { 3053 event = events.reddit; 3054 if(Utils.isEventInTimeWindow(event.name,event,"dyn_reddit_"+eventId)) { 3055 event = Utils.getFormFilledData(event); 3056 Reddit.fireEvent(event.name, event); 3057 } 3058 } 3059 3060 3061 if (events.hasOwnProperty('openai')) { 3062 event = events.openai; 3063 if(Utils.isEventInTimeWindow(event.name,event,"dyn_openai_"+eventId)) { 3064 event = Utils.getFormFilledData(event); 3065 OpenAI.fireEvent(event.name, event); 3066 } 3067 } 3068 3069 if (events.hasOwnProperty('gtm')) { 3070 event = events.gtm; 3071 if(Utils.isEventInTimeWindow(event.name,event,"dyn_gtm_"+eventId)) { 3072 event = Utils.getFormFilledData(event); 3073 GTM.fireEvent(event.name, event); 3074 } 3075 } 3076 3077 3078 }, 3079 3080 isFirstPurchaseFire: function ($eventName,orderId,pixel) { 3081 3082 if(Cookies.get("pys_"+$eventName+"_order_id_"+pixel) == orderId) { 3083 return false; 3084 } else { 3085 Cookies.set("pys_"+$eventName+"_order_id_"+pixel, orderId, { expires: 1,path: '/' }); 3086 } 3087 return true; 3088 }, 3089 3090 fireStaticEvents: function (pixel, timeout = 0) { 3091 3092 Utils.refreshAddonPixels(); 3093 3094 if (options.staticEvents.hasOwnProperty(pixel)) { 3095 3096 $.each(options.staticEvents[pixel], function (eventId, events) { 3097 3098 //skip purchase event if this order was fired 3099 if( options.woo.hasOwnProperty('woo_purchase_on_transaction') && 3100 options.woo.woo_purchase_on_transaction && 3101 (eventId === "woo_purchase" || eventId === "woo_purchase_category") ) { 3102 if(!Utils.isFirstPurchaseFire(eventId,events[0].woo_order,pixel)) { 3103 return; 3104 } 3105 } 3106 3107 if( options.edd.hasOwnProperty('edd_purchase_on_transaction') && 3108 options.edd.edd_purchase_on_transaction && 3109 (eventId === "edd_purchase" || eventId === "edd_purchase_category") ) { 3110 if(!Utils.isFirstPurchaseFire(eventId,events[0].edd_order,pixel)) { 3111 return; 3112 } 3113 } 3114 3115 3116 $.each(events, function (index, event) { 3117 3118 event.fired = event.fired || false; 3119 3120 if (!event.fired && Utils.conditionsPass(event) && Utils.isEventInTimeWindow(event.
3120name,event,'static_' + pixel+"_")) { 3121 event = Utils.getFormFilledData(event); 3122 3123 var fired = false; 3124 3125 // fire event 3126 if ( pixel === 'tiktok' ) { 3127 setTimeout(function() { 3128 getPixelBySlag(pixel).fireEvent(event.name, event); 3129 }, timeout * 500); 3130 timeout++; 3131 } else { 3132 getPixelBySlag(pixel).fireEvent(event.name, event); 3133 } 3134 3135 // prevent event double event firing 3136 event.fired = fired; 3137 } 3138 }); 3139 }); 3140 } 3141 }, 3142 3143 /** 3144 * Load tag's JS 3145 * 3146 * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/ 3147 * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/custom-dims-mets 3148 */ 3149 loadGoogleTag: function (id) { 3150 3151 if (!gtag_loaded) { 3152 let dataLayerName = this.resolveGaDataLayerName(); 3153 this.dataLayerName = dataLayerName; 3154 (function (window, document, src) { 3155 var a = document.createElement('script'), 3156 m = document.getElementsByTagName('script')[0]; 3157 a.async = 1; 3158 a.src = src; 3159 m.parentNode.insertBefore(a, m); 3160 })(window, document, '//www.googletagmanager.com/gtag/js?id=' + id+'&l='+this.dataLayerName); 3161 3162 window[dataLayerName] = window[dataLayerName] || []; 3163 // Published only so custom client code and third-party snippets 3164 // have a gtag() to call. PYS itself must never route through it: 3165 // the `||` means an earlier definition (Site Kit, a theme, the 3166 // GTM bootstrap) keeps ownership and would point at its own data 3167 // layer. Use Utils.gtagPush() instead. 3168 window.gtag = window.gtag || function gtag() { 3169 window[dataLayerName].push(arguments); 3170 }; 3171 3172 if ( options.google_consent_mode ) { 3173 let data = this.resolveConsentValues();
3174 3175 // Normally this data layer is ours alone and still empty, so a 3176 // `default` is what belongs here. But the GA and GTM scopes 3177 // collapse into one layer when their names resolve to the same 3178 // value (gtm_dataLayer_name left at 'dataLayerPYS', or the GA 3179 // layer set to "Disable name transformation"), and then the GTM 3180 // <head> snippet has already written a strict `denied` default 3181 // here. Repeating `default` would leave GA denied; `update` is 3182 // the documented transition once a default exists. 3183 this.pushConsent( dataLayerName, this.layerHasConsentDefault( dataLayerName ) ? 'update' : 'default', data ); 3184 } 3185 3186 Utils.gtagPush('js', new Date()); 3187 gtag_loaded = true; 3188 3189 } 3190 3191 }, 3192 3193 /** 3194 * Name of the data layer gtag.js is loaded with, see loadGoogleTag(). 3195 * Pure, so it can be resolved before the tag is loaded. 3196 */ 3197 resolveGaDataLayerName: function () { 3198 if ( options.hasOwnProperty( 'GATags' ) ) { 3199 switch ( options.GATags.ga_datalayer_type ) { 3200 case 'default': 3201 return 'dataLayerPYS'; 3202 case 'custom': 3203 return options.GATags.ga_datalayer_name; 3204 default: 3205 return 'dataLayer'; 3206 } 3207 } 3208 return this.dataLayerName || 'dataLayer'; 3209 }, 3210 3211 /** 3212 * Name of the GTM container's data layer. Mirrors GTM::pys_wp_header_top() 3213 * in modules/google_gtm/gtm.php â the two must agree, or the <head> consent 3214 * default and the footer consent update land in different layers. `??` 3215 * alone is not enough: the option can be saved as an empty string, and 3216 * window[''] is a silent dead end. 3217 */ 3218 resolveGtmDataLayerName: function () { 3219 var name = options.hasOwnProperty( 'gtm' ) ? options.gtm.gtm_dataLayer_name : ''; 3220 return ( typeof name === 'string' && name.length ) ? name : 'dataLayer'; 3221 }, 3222 3223 /** 3224 * Sends a gtag command (js/config/set/event/consent) to the GA data layer. 3225 * 3226 * Deliberately does NOT call window.gtag. That global is created with 3227 * `window.gtag = window.gtag || ...`, so whoever defined it first owns it: 3228 * if a theme, Site Kit or the GTM bootstrap got there first, our config 3229 * and event commands would silently land in their data layer while the 3230 * gtag.js instance we loaded (&l=<GA layer>) never receives a config â 3231 * Tag Assistant then reports "Some hits will only be sent after a config 3232 * command is issued". Pushing the arguments object is exactly what gtag() 3233 * does internally, so the payload is identical. 3234 */ 3235 gtagPush: function () { 3236 var name = this.dataLayerName || this.resolveGaDataLayerName(); 3237 window[ name ] = window[ name ] || []; 3238 window[ name ].push( arguments ); 3239 }, 3240 3241 /** 3242 * Back-compat alias of the old GA-scope consent sender. Exposed through 3243 * window.pys.Utils, so custom client code may call it. New code uses 3244 * pushConsent() with an explicit data layer name. 3245 */ 3246 loadDefaultConsent: function() { 3247 this.gtagPush.apply( this, arguments ); 3248 }, 3249 3250 /** 3251 * Sends one Consent Mode command ('default' or 'update') to a named data 3252 * layer. The single place that writes consent commands. 3253 * 3254 * The IIFE is not decoration: gtag() pushes its own `arguments` object, 3255 * and gtag.js / GTM read the command by index off that object. Pushing a 3256 * plain array works today but is not what the tag emits, so we reproduce 3257 * the exact shape â same reason gtm.php builds its <head> command this way. 3258 */ 3259 pushConsent: function ( layerName, command, values ) { 3260 if ( !layerName ) { 3261 return; 3262 } 3263 window[ layerName ] = window[ layerName ] || []; 3264 ( function () { 3265 window[ layerName ].push( arguments ); 3266 } )( 'consent', command, values ); 3267 }, 3268 3269 /** 3270 * Whether a Consent Mode `default` has already been written to this data 3271 * layer â by our own <head> snippet, by a consent plugin, or by a third 3272 * party (Site Kit, a theme). Decides `default` vs `update`: Consent Mode 3273 * documents exactly one `default` per layer, and a repeated one is not a 3274 * defined transition. Consent Magic reads the layer the same way 3275 * (cs-public.js, CS_Consent_Mode.setDefaultConsent). 3276 */ 3277 layerHasConsentDefault: function ( layerName ) { 3278 var layer = window[ layerName ]; 3279 if ( !layer || typeof layer.length !== 'number' ) { 3280 return false; 3281 } 3282 for ( var i = 0; i < layer.length; i++ ) { 3283 var item = layer[ i ];
3284 // Entries pushed by other integrations are plain objects; indexing 3285 // them just yields undefined. 3286 if ( item && item[ 0 ] === 'consent' && item[ 1 ] === 'default' ) { 3287 return true; 3288 } 3289 } 3290 return false; 3291 }, 3292 3293 /** 3294 * The four Consent Mode signals as PYS currently resolves them. `enabled` 3295 * is false when consent mode is off for that signal, in which case Google's 3296 * absence of a value means granted anyway â say so explicitly. 3297 */ 3298 resolveConsentValues: function () { 3299 return { 3300 'analytics_storage': options.gdpr.analytics_storage.enabled ? options.gdpr.analytics_storage.value : 'granted', 3301 'ad_storage': options.gdpr.ad_storage.enabled ? options.gdpr.ad_storage.value : 'granted', 3302 'ad_user_data': options.gdpr.ad_user_data.enabled ? options.gdpr.ad_user_data.value : 'granted', 3303 'ad_personalization': options.gdpr.ad_personalization.enabled ? options.gdpr.ad_personalization.value : 'granted', 3304 }; 3305 }, 3306 3307 loadGTMScript: function (id = '') { 3308 // Strip trailing slash to allow both "domain.com" and "domain.com/" inputs. 3309 const domain = ( options.gtm.gtm_container_domain ?? 'www.googletagmanager.com' ).replace( /\/$/, '' ); 3310 const loader = options.gtm.gtm_container_identifier ?? 'gtm'; 3311 const gtm_auth = options.gtm.gtm_auth ?? ''; 3312 const gtm_preview = options.gtm.gtm_preview ?? ''; 3313 const datalayer_name = this.resolveGtmDataLayerName(); 3314 3315 window[ datalayer_name ] = window[ datalayer_name ] || []; 3316 window.gtag = window.gtag || function gtag() { 3317 window[ datalayer_name ].push( arguments ); 3318 }; 3319 3320 // Send the consent state synchronously before the GTM script tag is 3321 // inserted, so it is in the dataLayer before GTM processes any events. 3322 this.pushGTMConsentState( datalayer_name ); 3323 3324 (function(w, d, s, l, i) { 3325 w[l] = w[l] || []; 3326 w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); 3327 const f = d.getElementsByTagName(s)[0]; 3328 const j = d.createElement(s); 3329 const dl = l !== 'dataLayer' ? '&l=' + l : ''; 3330 j.async = true; 3331 3332 // Detect domain format: bare hostname (legacy) vs full URL / relative path (new). 3333 // Legacy "www.googletagmanager.com" â prepend https:// and append .js. 3334 // New format "/metrics" or "https://custom.com" â use as-is. 3335 // Identifier with extension (e.g. "gtm.js") â file: domain/gtm.js?id= 3336 // Identifier without extension (e.g. "g4h25o") â path: domain/g4h25o/?id= 3337 const isNewFormat = domain.startsWith( 'http://' ) 3338 || domain.startsWith( 'https://' ) 3339 || domain.startsWith( '/' ); 3340 3341 if ( !isNewFormat ) { 3342 j.src = 'https://' + domain + '/' + loader + '.js?id=' + i + dl; 3343 } else { 3344 const hasExtension = loader.includes( '.' ); 3345 j.src = domain + '/' + loader + ( hasExtension ? '?' : '/?' ) + 'id=' + i + dl; 3346 } 3347 3348 if (gtm_auth && gtm_preview) { 3349 j.src += '>m_auth=' + gtm_auth + '>m_preview=' + gtm_preview + '>m_cookies_win=x'; 3350 } 3351 f.parentNode.insertBefore(j, f); 3352 })(window, document, 'script', datalayer_name, id); 3353 3354 }, 3355 3356 /** 3357 * Back-compat aliases. Both are reachable as window.pys.Utils.* and may be 3358 * called by custom client code, so they keep working; internally everything 3359 * goes through pushConsent() / pushGTMConsentState() with an explicit layer 3360 * name instead of the implicit this.GTMdataLayerName state. 3361 */ 3362 loadDefaultGTMConsent: function() { 3363 window[ this.GTMdataLayerName ] = window[ this.GTMdataLayerName ] || []; 3364 window[ this.GTMdataLayerName ].push( arguments ); 3365 }, 3366 pushGTMConsentDefaults: function( datalayer_name ) { 3367 this.pushGTMConsentState( datalayer_name ); 3368 }, 3369 3370 /** 3371 * Sends the GTM container's consent state, once per page load. 3372 * 3373 * Called from loadGTMScript() (PYS loads the container itself) and from 3374 * GTM.loadPixel() when gtm_just_data_layer is on, so the state is sent 3375 * whichever path runs â and only once when both do. 3376 * 3377 * Emits `update`, not a second `default`, whenever a default already sits 3378 * in the layer. modules/google_gtm/gtm.php writes a strict all-denied 3379 * default with wait_for_update:500 in <head>, before the container boots; 3380 * that is correct Consent Mode practice, and it is what makes PYS's own 3381 * prior-consent gate work â this function runs downstream of 3382 * Utils.consentGiven('analytics'), so nothing is granted until consent 3383 * actually exists. What was missing is the other half of the contract: 3384 * the update that resolves the wait_for_update window. A repeated 3385 * `default` is not a defined transition and left GTM-managed tags denied. 3386 */ 3387 pushGTMConsentState: function( datalayer_name ) { 3388 if ( !options.google_consent_mode || gtm_consent_state_pushed ) { 3389 return; 3390 } 3391 3392 // A consent management plugin owns the transition: it issues its own 3393 // update from the user's live choice, while ours carries whatever the 3394 // cookies said when the page was rendered. Consent Magic diffs against 3395 // the last consent values in the layer, so a late push from us either 3396 // suppresses its update or overwrites the choice the user just made. 3397 // The <head> default already carries the CMP's resolved values. 3398 if ( options.consent_mode_cmp_active ) { 3399 return; 3400 } 3401 3402 gtm_consent_state_pushed = true; 3403 // Kept in sync for the loadDefaultGTMConsent() back-compat alias. 3404 this.GTMdataLayerName = datalayer_name; 3405 this.pushConsent( 3406 datalayer_name, 3407 this.layerHasConsentDefault( datalayer_name ) ? 'update' : 'default', 3408 this.resolveConsentValues() 3409 ); 3410 }, 3411 3412 /** 3413 * GDPR 3414 */ 3415 3416 loadPixels: function () { 3417 3418 if (options.gdpr.ajax_enabled && !options.gdpr.consent_magic_integration_enabled) { 3419 3420 // retrieves actual PYS GDPR filters values which allow to avoid cache issues 3421 $.get({ 3422 url: options.ajaxUrl, 3423 dataType: 'json', 3424 data: { 3425 action: 'pys_get_gdpr_filters_values' 3426 }, 3427 success: function (res) { 3428 3429 if (res.success) { 3430 3431 options.gdpr.all_disabled_by_api = res.data.all_disabled_by_api; 3432 options.gdpr.facebook_disabled_by_api = res.data.facebook_disabled_by_api; 3433 options.gdpr.tiktok_disabled_by_api = res.data.tiktok_disabled_by_api; 3434 options.gdpr.openai_disabled_by_api = res.data.openai_disabled_by_api; 3435 options.gdpr.analytics_disabled_by_api = res.data.analytics_disabled_by_api; 3436 options.gdpr.google_ads_disabled_by_api = res.data.google_ads_disabled_by_api; 3437 options.gdpr.pinterest_disabled_by_api = res.data.pinterest_disabled_by_api; 3438 options.gdpr.bing_disabled_by_api = res.data.bing_disabled_by_api; 3439 options.gdpr.reddit_disabled_by_api = res.data.reddit_disabled_by_api; 3440 3441 options.cookie.externalID_disabled_by_api = res.data.externalID_disabled_by_api; 3442 options.cookie.disabled_all_cookie = res.data.disabled_all_cookie; 3443 options.cookie.disabled_advanced_form_data_cookie = res.data.disabled_advanced_form_data_cookie; 3444 options.cookie.disabled_landing_page_cookie = res.data.disabled_landing_page_cookie || !options.enable_lading_page_param; 3445 options.cookie.disabled_first_visit_cookie = res.data.disabled_first_visit_cookie; 3446 options.cookie.disabled_trafficsource_cookie = res.data.disabled_trafficsource_cookie; 3447 options.cookie.disabled_utmTerms_cookie = res.data.disabled_utmTerms_cookie; 3448 options.cookie.disabled_utmId_cookie = res.data.disabled_utmId_cookie; 3449 } 3450 loadPixels(); 3451 3452 } 3453 }); 3454 3455 } else { 3456 loadPixels(); 3457 } 3458 3459 }, 3460 3461 consentGiven: function (pixel) { 3462 3463 /** 3464 * Cookiebot 3465 */ 3466 if (options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined') { 3467 3468 var cookiebot_consent_category = options.gdpr['cookiebot_' + pixel + '_consent_category']; 3469 3470 if (options.gdpr[pixel + '_prior_consent_enabled']) { 3471 if (Cookiebot.consented === true || Cookiebot.consent[cookiebot_consent_category]) { 3472 return true; 3473 } 3474 } else { 3475 if (Cookiebot.consent[cookiebot_consent_category]) { 3476 return true; 3477 } 3478 } 3479 return false; 3480 3481 } 3482 3483 /** 3484 * Cookie Notice 3485 */ 3486 if (options.gdpr.cookie_notice_integration_enabled && typeof cnArgs !== 'undefined') { 3487 3488 var cn_cookie = Cookies.get(cnArgs.cookieName); 3489 3490 if (options.gdpr[pixel + '_prior_consent_enabled']) { 3491 if (typeof cn_cookie === 'undefined' || cn_cookie === 'true') { 3492 return true; 3493 } 3494 } else { 3495 if (cn_cookie === 'true') { 3496 return true; 3497 } 3498 } 3499 3500 return false; 3501 3502 } 3503 3504 /** 3505 * Cookie Law Info 3506 */ 3507 3508 if (options.gdpr.cookie_law_info_integration_enabled) { 3509 var cli_cookie = Cookies.get('cookieyes-consent') ?? Cookies.get('wt_consent') ?? Cookies.get('viewed_cookie_policy'); 3510 if (options.gdpr[pixel + '_prior_consent_enabled']) { 3511 if (typeof cli_cookie === 'undefined') return true; 3512 if ( 3513 cli_cookie && 3514 (cli_cookie === Cookies.get('cookieyes-consent') || cli_cookie === Cookies.get('wt_consent')) 3515 ) { 3516 if (getCookieYes('analytics') === 'yes') { 3517 return true; 3518 } 3519 } else if (cli_cookie && cli_cookie === Cookies.get('viewed_cookie_policy')) { 3520 if (Cookies.get('viewed_cookie_policy') === 'yes') { 3521 return true; 3522 } 3523 } 3524 } else { 3525 if ( 3526 cli_cookie && 3527 (cli_cookie === Cookies.get('cookieyes-consent') || cli_cookie === Cookies.get('wt_consent')) 3528 ) { 3529 if (getCookieYes('analytics') === 'yes') { 3530 return true; 3531 } 3532 } else if (cli_cookie && cli_cookie === Cookies.get('viewed_cookie_policy')) { 3533 if (Cookies.get('viewed_cookie_policy') === 'yes') { 3534 return true; 3535 } 3536 } 3537 } 3538 return false; 3539 } 3540 3541 /** 3542 * ConsentMagic 3543 */ 3544 if (options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined" ) { 3545 3546 let test_prefix = CS_Data.test_prefix; 3547 if ( 3548 ( Utils.csModeEnabled( CS_Data.cs_google_consent_mode_enabled ) && ( pixel == 'analytics' || pixel == 'google_ads' ) ) 3549 || ( Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) && pixel == 'facebook' ) 3550 || ( Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) && pixel == 'reddit' ) 3551 || ( Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) && pixel == 'openai' ) 3552 || ( Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) && pixel == 'bing' ) 3553 ) { 3554 if ( Number( CS_Data.cs_cache_enabled ) === 0 3555 || ( Utils.csModeEnabled( CS_Data.cs_cache_enabled ) && window.CS_Cache && window.CS_Cache.check_status ) ) { 3556 return true; 3557 } else { 3558 return false; 3559 } 3560 } 3561 3562 if( pixel == 'facebook' && ( CS_Data.cs_script_cat.facebook == 0 || CS_Data.cs_script_cat.facebook == CS_Data.cs_necessary_cat_id ) ) { 3563 return true; 3564 } else if( pixel == 'bing' && ( CS_Data.cs_script_cat.bing == 0 || CS_Data.cs_script_cat.bing == CS_Data.cs_necessary_cat_id ) ) { 3565 return true; 3566 } else if( pixel == 'analytics' && ( CS_Data.cs_script_cat.analytics == 0 || CS_Data.cs_script_cat.analytics == CS_Data.cs_necessary_cat_id ) ) { 3567 return true; 3568 } else if( pixel == 'google_ads' && ( CS_Data.cs_script_cat.gads == 0 || CS_Data.cs_script_cat.gads == CS_Data.cs_necessary_cat_id ) ) { 3569 return true; 3570 } else if( pixel == 'pinterest' && ( CS_Data.cs_script_cat.pinterest == 0 || CS_Data.cs_script_cat.pinterest == CS_Data.cs_necessary_cat_id ) ) { 3571 return true; 3572 } else if( pixel == 'tiktok' && ( CS_Data.cs_script_cat.tiktok == 0 || CS_Data.cs_script_cat.tiktok == CS_Data.cs_necessary_cat_id ) ) { 3573 return true; 3574 } else if( pixel == 'reddit' && ( CS_Data.cs_script_cat?.reddit == 0 || CS_Data.cs_script_cat?.reddit == CS_Data.cs_necessary_cat_id ) ) { 3575 return true; 3576 } else if( pixel == 'openai' && ( CS_Data.cs_script_cat?.openai == 0 || CS_Data.cs_script_cat?.openai == CS_Data.cs_necessary_cat_id ) ) { 3577 return true; 3578 } 3579 3580 let substring = "cs_enabled_cookie_
3580term", 3581 theCookies = document.cookie.split( ';' ); 3582 3583 for ( let i = 1; i <= theCookies.length; i++ ) { 3584 if ( theCookies[ i - 1 ].indexOf( substring ) !== -1 ) { 3585 let categoryCookie = theCookies[ i - 1 ].replace( 'cs_enabled_cookie_term' + test_prefix + '_', '' ); 3586 categoryCookie = Number( categoryCookie.replace( /\D+/g, "" ) ); 3587 let cs_cookie_val = Cookies.get( 'cs_enabled_cookie_term' + test_prefix + '_' + categoryCookie ); 3588 3589 if ( categoryCookie === CS_Data.cs_script_cat.facebook && pixel == 'facebook' ) { 3590 return cs_cookie_val == 'yes'; 3591 } else if ( categoryCookie === CS_Data.cs_script_cat.bing && pixel == 'bing' ) { 3592 return cs_cookie_val == 'yes'; 3593 } else if ( categoryCookie === CS_Data.cs_script_cat.analytics && pixel == 'analytics' ) { 3594 return cs_cookie_val == 'yes'; 3595 } else if ( categoryCookie === CS_Data.cs_script_cat.gads && pixel == 'google_ads' ) { 3596 return cs_cookie_val == 'yes'; 3597 } else if ( categoryCookie === CS_Data.cs_script_cat.pinterest && pixel == 'pinterest' ) { 3598 return cs_cookie_val == 'yes'; 3599 } else if ( categoryCookie === CS_Data.cs_script_cat.tiktok && pixel == 'tiktok' ) { 3600 return cs_cookie_val == 'yes'; 3601 } else if ( categoryCookie === CS_Data.cs_script_cat?.reddit && pixel == 'reddit' ) { 3602 return cs_cookie_val == 'yes'; 3603 } else if ( categoryCookie === CS_Data.cs_script_cat?.openai && pixel == 'openai' ) { 3604 return cs_cookie_val == 'yes'; 3605 } 3606 } 3607 } 3608 3609 return false; 3610 } 3611 3612 3613 /** 3614 * Real Cookie Banner 3615 */ 3616 if (options.gdpr.real_cookie_banner_integration_enabled) { 3617 var consentApi = window.consentApi; 3618 if (consentApi) { 3619 switch (pixel) { 3620 case "analytics": 3621 return consentApi.consentSync("http", "_ga", "*").cookieOptIn; 3622 case "facebook": 3623 return consentApi.consentSync("http", "_fbp", "*").cookieOptIn; 3624 case "pinterest": 3625 return consentApi.consentSync("http", "_pinterest_sess", ".pinterest.com").cookieOptIn; 3626 case "bing": 3627 return consentApi.consentSync("http", "_uetsid", "*").cookieOptIn; 3628 case "google_ads": 3629 return consentApi.consentSync("http", "1P_JAR", ".google.com").cookieOptIn; 3630 case 'tiktok': 3631 return consentApi.consentSync("http", "tt_webid_v2", ".tiktok.com").cookieOptIn; 3632 case 'openai': 3633 return consentApi.consentSync("http", "__oppref", "*").cookieOptIn; 3634 default: 3635 return true; 3636 } 3637 } 3638 } 3639 3640 return true; 3641 3642 }, 3643 3644 setupGdprCallbacks: function () { 3645 3646 let isConcent = false; 3647 3648 /** 3649 * Cookiebot 3650 */ 3651 if ( options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined' ) { 3652 isConcent = true; 3653 window.addEventListener( "CookiebotOnConsentReady", function () { 3654 3655 let consent = { 3656 facebook: true, 3657 ga: true, 3658 google_ads: true, 3659 tiktok: true, 3660 openai: true, 3661 bing: true, 3662 pinterest: true, 3663 gtm: true, 3664 }; 3665 Utils.initializeVideoAPIs( options ); 3666 3667 Utils.manageCookies(); 3668 if ( Cookiebot.consent.marketing ) { 3669 Facebook.loadPixel(); 3670 Bing.loadPixel(); 3671 Pinterest.loadPixel(); 3672 GAds.loadPixel(); 3673 TikTok.loadPixel(); 3674 OpenAI.loadPixel(); 3675 3676 consent.facebook = true; 3677 consent.bing = true; 3678 consent.google_ads = true; 3679 consent.pinterest = true; 3680 consent.tiktok = true; 3681 } 3682 if ( Cookiebot.consent.statistics ) { 3683 Analytics.loadPixel(); 3684 3685 consent.ga = true; 3686 consent.gtm = true; 3687 } 3688 if ( !Cookiebot.consent.marketing ) { 3689 Facebook.disable(); 3690 Pinterest.disable(); 3691 Bing.disable() 3692 GAds.disable(); 3693 TikTok.disable(); 3694 OpenAI.disable(); 3695 3696 consent.facebook = false; 3697 consent.bing = false; 3698 consent.google_ads = false; 3699 consent.pinterest = false; 3700 consent.tiktok = false; 3701 } 3702 if ( !Cookiebot.consent.statistics ) { 3703 Analytics.disable(); 3704 3705 consent.ga = false; 3706 consent.gtm = false; 3707 } 3708 3709 Utils.setupGDPRData( consent ); 3710 } ); 3711 } 3712 3713 /** 3714 * Cookie Notice 3715 */ 3716 if ( options.gdpr.cookie_notice_integration_enabled ) { 3717 isConcent = true; 3718 $( document ).onFirst( 'click', '.cn-set-cookie', function () { 3719 3720 let consent = {}; 3721 3722 if ( $( this ).data( 'cookie-set' ) === 'accept' ) { 3723 3724 Utils.initializeVideoAPIs( options ); 3725 3726 Facebook.loadPixel(); 3727 Analytics.loadPixel(); 3728 GAds.loadPixel(); 3729 Pinterest.loadPixel(); 3730 Bing.loadPixel(); 3731 TikTok.loadPixel(); 3732 OpenAI.loadPixel(); 3733 3734 consent = { 3735 facebook: true, 3736 ga: true, 3737 google_ads: true, 3738 tiktok: true, 3739 openai: true, 3740 bing: true, 3741 pinterest: true, 3742 gtm: true, 3743 }; 3744 } else { 3745 Facebook.disable(); 3746 Analytics.disable(); 3747 GAds.disable(); 3748 Pinterest.disable(); 3749 Bing.disable(); 3750 TikTok.disable(); 3751 OpenAI.disable(); 3752 3753 consent = {
3754 facebook: false, 3755 ga: false, 3756 google_ads: false, 3757 tiktok: false, 3758 openai: false, 3759 bing: false, 3760 pinterest: false, 3761 gtm: false, 3762 }; 3763 } 3764 Utils.setupGDPRData( consent ); 3765 3766 } ); 3767 3768 $( document ).onFirst( 'click', '.cn-revoke-cookie', function () { 3769 Facebook.disable(); 3770 Analytics.disable(); 3771 GAds.disable(); 3772 Pinterest.disable(); 3773 Bing.disable(); 3774 TikTok.disable(); 3775 OpenAI.disable(); 3776 3777 let consent = { 3778 facebook: false, 3779 ga: false, 3780 google_ads: false, 3781 tiktok: false, 3782 openai: false, 3783 bing: false, 3784 pinterest: false, 3785 gtm: false, 3786 }; 3787 Utils.setupGDPRData( consent ); 3788 3789 } ); 3790 } 3791 3792 /** 3793 * Cookie Law Info 3794 */ 3795 if ( options.gdpr.cookie_law_info_integration_enabled ) { 3796 isConcent = true; 3797 $( document ).onFirst( 'click', '#wt-cli-accept-all-btn,#cookie_action_close_header, .cky-btn-accept', function () { 3798 Utils.initializeVideoAPIs( options ); 3799 setTimeout( function () { 3800 let cli_cookie = Cookies.get( 'cookieyes-consent' ) ?? Cookies.get( 'wt_consent' ) ?? Cookies.get( 'viewed_cookie_policy' ); 3801 if ( typeof cli_cookie !== 'undefined' ) { 3802 if ( 3803 cli_cookie && 3804 (cli_cookie === Cookies.get('cookieyes-consent') || cli_cookie === Cookies.get('wt_consent')) 3805 ) { 3806 if (getCookieYes('analytics') === 'yes') { 3807 Utils.manageCookies(); 3808 } 3809 } else if ( cli_cookie === Cookies.get( 'viewed_cookie_policy' ) && cli_cookie == 'yes' ) { 3810 Utils.manageCookies(); 3811 } 3812 } 3813 }, 1000 ) 3814 Facebook.loadPixel(); 3815 Analytics.loadPixel(); 3816 GAds.loadPixel(); 3817 Pinterest.loadPixel(); 3818 Bing.loadPixel(); 3819 TikTok.loadPixel(); 3820 OpenAI.loadPixel(); 3821 3822 let consent = { 3823 facebook: true, 3824 ga: true, 3825 google_ads: true, 3826 tiktok: true, 3827 openai: true, 3828 bing: true, 3829 pinterest: true, 3830 gtm: true, 3831 }; 3832 Utils.setupGDPRData( consent ); 3833 } ); 3834 3835 $( document ).onFirst( 'click', '#cookie_action_close_header_reject, .cky-btn-reject', function () { 3836 Facebook.disable(); 3837 Analytics.disable(); 3838 GAds.disable(); 3839 Pinterest.disable(); 3840 Bing.disable(); 3841 TikTok.disable(); 3842 OpenAI.disable(); 3843 3844 let consent = {
3845 facebook: false, 3846 ga: false, 3847 google_ads: false, 3848 tiktok: false, 3849 openai: false, 3850 bing: false, 3851 pinterest: false, 3852 gtm: false, 3853 }; 3854 Utils.setupGDPRData( consent ); 3855 } ); 3856 } 3857 3858 /** 3859 * ConsentMagic 3860 */ 3861 if ( options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined" ) { 3862 isConcent = true; 3863 let test_prefix = CS_Data.test_prefix, 3864 cs_refresh_after_consent = false, 3865 substring = "cs_enabled_cookie_term"; 3866 3867 if ( CS_Data.cs_refresh_after_consent == 1 ) { 3868 cs_refresh_after_consent = CS_Data.cs_refresh_after_consent; 3869 } 3870 3871 let consent_actions = function () { 3872 let theCookies = document.cookie.split( ';' ); 3873 3874 let consent = { 3875 facebook: true, 3876 ga: true, 3877 google_ads: true, 3878 tiktok: true, 3879 openai: true, 3880 bing: true, 3881 pinterest: true, 3882 gtm: true, 3883 reddit: true, 3884 }; 3885 3886 for ( let i = 1; i <= theCookies.length; i++ ) { 3887 if ( theCookies[ i - 1 ].indexOf( substring ) !== -1 ) { 3888 let categoryCookie = theCookies[ i - 1 ].replace( 'cs_enabled_cookie_term' + test_prefix + '_', '' ); 3889 categoryCookie = Number( categoryCookie.replace( /\D+/g, "" ) ); 3890 let cs_cookie_val = Cookies.get( 'cs_enabled_cookie_term' + test_prefix + '_' + categoryCookie ); 3891 if ( cs_cookie_val == 'yes' ) { 3892 3893 Utils.initializeVideoAPIs( options ); 3894 3895 if ( ( categoryCookie === CS_Data.cs_script_cat.facebook ) || Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) ) { 3896 Facebook.loadPixel(); 3897 } 3898 3899 if ( categoryCookie === CS_Data.cs_script_cat.bing || Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) ) { 3900 Bing.loadPixel(); 3901 } 3902 3903 if ( categoryCookie === CS_Data.cs_script_cat.analytics || ( typeof CS_Data.cs_google_analytics_consent_mode !== "undefined" && CS_Data.cs_google_analytics_consent_mode == 1 ) ) { 3904 Analytics.loadPixel(); 3905 } 3906 if ( categoryCookie === CS_Data.cs_script_cat.gads || ( typeof CS_Data.cs_google_ads_consent_mode !== "undefined" && CS_Data.cs_google_ads_consent_mode == 1 ) ) { 3907 GAds.loadPixel(); 3908 } 3909 3910 if ( categoryCookie === CS_Data.cs_script_cat.pinterest ) { 3911 Pinterest.loadPixel(); 3912 } 3913 3914 if ( categoryCookie === CS_Data.cs_script_cat.tiktok ) { 3915 TikTok.loadPixel(); 3916 } 3917 3918 if ( ( categoryCookie === CS_Data.cs_script_cat.reddit ) || Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) ) { 3919 Reddit.loadPixel(); 3920 } 3921 3922 if ( ( categoryCookie === CS_Data.cs_script_cat.openai ) || Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) ) { 3923 OpenAI.loadPixel(); 3924 } 3925 } else { 3926 if ( ( categoryCookie === CS_Data.cs_script_cat.facebook ) && ! Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) ) { 3927 Facebook.disable(); 3928 consent.facebook = false; 3929 } 3930 3931 if ( categoryCookie === CS_Data.cs_script_cat.bing && ! Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) ) { 3932 Bing.disable(); 3933 consent.bing = false; 3934 } 3935 3936 if ( categoryCookie === CS_Data.cs_script_cat.analytics && ( typeof CS_Data.cs_google_analytics_consent_mode == "undefined" || CS_Data.cs_google_analytics_consent_mode == 0 ) ) { 3937 Analytics.disable(); 3938 consent.ga = false; 3939 consent.gtm = false; 3940 } 3941 3942 if ( categoryCookie === CS_Data.cs_script_cat.gads && ( typeof CS_Data.cs_google_ads_consent_mode == "undefined" || CS_Data.cs_google_ads_consent_mode == 0 ) ) { 3943 GAds.disable(); 3944 consent.google_ads = false; 3945 } 3946 3947 if ( categoryCookie === CS_Data.cs_script_cat.pinterest ) { 3948 Pinterest.disable(); 3949 consent.pinterest = false; 3950 } 3951 3952 if ( categoryCookie === CS_Data.cs_script_cat.tiktok ) { 3953 TikTok.disable(); 3954 consent.tiktok = false; 3955 } 3956 3957 if ( ( categoryCookie === CS_Data.cs_script_cat.reddit ) && ! Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) ) { 3958 Reddit.disable(); 3959 consent.reddit = false; 3960 } 3961 3962 if ( ( categoryCookie === CS_Data.cs_script_cat.openai ) && ! Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) ) { 3963 OpenAI.disable(); 3964 consent.openai = false; 3965 } 3966 } 3967 if ( Cookies.get( 'cs_enabled_advanced_matching' ) == 'yes' ) { 3968 Facebook.loadPixel(); 3969 } 3970 } 3971 } 3972 3973 Utils.setupGDPRData( consent ); 3974 } 3975 3976 if ( !cs_refresh_after_consent ) { 3977 consent_actions(); 3978 3979 $( document ).on( 'click', '.cs_action_btn', function ( e ) { 3980 e.preventDefault(); 3981 3982 let consent = { 3983 facebook: true, 3984 ga: true, 3985 google_ads: true, 3986 tiktok: true, 3987 openai: true, 3988 bing: true, 3989 pinterest: true, 3990 gtm: true, 3991 reddit: true, 3992 }; 3993 3994 let elm = $( this ), 3995 button_action = elm.attr( 'data-cs_action' ); 3996 3997 if ( button_action === 'allow_all' ) { 3998 3999 Utils.initializeVideoAPIs( options ); 4000 4001 Facebook.loadPixel(); 4002 Bing.loadPixel(); 4003 Analytics.loadPixel(); 4004 GAds.loadPixel(); 4005 Pinterest.loadPixel(); 4006 TikTok.loadPixel(); 4007 OpenAI.loadPixel(); 4008 Reddit.loadPixel(); 4009 4010 consent.facebook = true; 4011 consent.bing = true; 4012 consent.ga = true; 4013 consent.google_ads = true; 4014 consent.pinterest = true; 4015 consent.tiktok = true; 4016 consent.reddit = true; 4017 consent.gtm = true; 4018 4019 Utils.setupGDPRData( consent ); 4020 4021 } else if ( button_action === 'disable_all' ) { 4022 4023 if ( ! Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) ) { 4024 Facebook.disable(); 4025 consent.facebook = false; 4026 } 4027 4028 if ( ! Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) ) { 4029 Bing.disable(); 4030 consent.bing = false; 4031 } 4032 4033 if ( typeof CS_Data.cs_google_analytics_consent_mode == "undefined" || CS_Data.cs_google_analytics_consent_mode == 0 ) { 4034 Analytics.disable(); 4035 consent.ga = false; 4036 consent.gtm = false; 4037 } 4038 4039 if ( typeof CS_Data.cs_google_ads_consent_mode == "undefined" || CS_Data.cs_google_ads_consent_mode == 0 ) { 4040 GAds.disable(); 4041 consent.google_ads = false; 4042 } 4043 4044 if ( ! Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) ) { 4045 Reddit.disable(); 4046 consent.reddit = false; 4047 } 4048 4049 Pinterest.disable(); 4050 TikTok.disable(); 4051 OpenAI.disable(); 4052 4053 consent.pinterest = false; 4054 consent.tiktok = false; 4055 consent.openai = false; 4056 4057 Utils.setupGDPRData( consent ); 4058 } else if ( button_action === 'cs_confirm' ) { 4059 consent_actions(); 4060 } 4061 } ); 4062 } 4063 } 4064 4065 /** 4066 * Real Cookie Banner 4067 */ 4068 if ( options.gdpr.real_cookie_banner_integration_enabled ) { 4069 isConcent = true; 4070 let consentApi = window.consentApi; 4071 if ( consentApi ) { 4072 consentApi.consent( "http", "_ga", "*" ) 4073 .then( Analytics.loadPixel.bind( Analytics ), Analytics.disable.bind( Analytics ) ); 4074 consentApi.consent( "http", "1P_JAR", ".google.com" ) 4075 .then( GAds.loadPixel.bind( GAds ), GAds.disable.bind( GAds ) ); 4076 consentApi.consent( "http", "_fbp", "*" ) 4077 .then( Facebook.loadPixel.bind( Facebook ), Facebook.disable.bind( Facebook ) ); 4078 consentApi.consent( "http", "_pinterest_sess", ".pinterest.com" ) 4079 .then( Pinterest.loadPixel.bind( Pinterest ), Pinterest.disable.bind( Pinterest ) ); 4080 consentApi.consent( "http", "_uetsid", "*" ) 4081 .then( Bing.loadPixel.bind( Bing ), Bing.disable.bind( Bing ) ); 4082 consentApi.consent( "http", "tt_webid_v2", ".tiktok.com" ) 4083 .then( TikTok.loadPixel.bind( TikTok ), TikTok.disable.bind( TikTok ) ); 4084 consentApi.consent( "http", "__oppref", "*" ) 4085 .then( OpenAI.loadPixel.bind( OpenAI ), OpenAI.disable.bind( OpenAI ) ); 4086 4087 let consent = { 4088 facebook: true, 4089 ga: true, 4090 google_ads: true, 4091 tiktok: true, 4092 openai: true, 4093 bing: true, 4094 pinterest: true, 4095 gtm: true, 4096 }; 4097 4098 if (!consentApi.consentSync("http", "_ga", "*").cookieOptIn) { 4099 consent.ga = false; 4100 consent.gtm = false; 4101 } 4102 if (!consentApi.consentSync("http", "_fbp", "*").cookieOptIn) { 4103 consent.facebook = false; 4104 } 4105 if (!consentApi.consentSync("http", "_pinterest_sess", ".pinterest.com").cookieOptIn) { 4106 consent.pinterest = false; 4107 } 4108 if (!consentApi.consentSync("http", "_uetsid", "*").cookieOptIn) { 4109 consent.bing = false; 4110 } 4111 if (!consentApi.consentSync("http", "1P_JAR", ".google.com").cookieOptIn) { 4112 consent.google_ads = false; 4113 } 4114 if (!consentApi.consentSync("http", "tt_webid_v2", ".tiktok.com").cookieOptIn) { 4115 consent.tiktok = false; 4116 } 4117 if (!consentApi.consentSync("http", "__oppref", "*").cookieOptIn) { 4118 consent.openai = false; 4119 } 4120 Utils.setupGDPRData( consent ); 4121 4122 // load WatchVideo event APIs for Auto 4123 /** 4124 * Real Cookie Banner. 4125 */ 4126 if ( options.hasOwnProperty( 'automatic' ) && options.automatic.enable_video && options.automatic.enable_youtube ) { 4127 consentApi.consent( "http", "CONSENT", ".youtube.com" ).then( Utils.initYouTubeAPI ); 4128 } 4129 4130 if ( options.hasOwnProperty( 'automatic' ) && options.automatic.enable_video && options.automatic.enable_vimeo ) { 4131 consentApi.consent( "http", "player", ".vimeo.com" ).then( Utils.initVimeoAPI ); 4132 } 4133 } 4134 } 4135 4136 if ( !isConcent ) { 4137 Utils.initializeVideoAPIs( options ); 4138 } 4139 }, 4140 4141 setupGDPRData: function ( consent ) { 4142 consent = window.btoa( JSON.stringify( consent ) ); 4143 Cookies.set( 'pys_consent', consent, { expires: 365, path: '/', domain: cookieDomain() } ); 4144 }, 4145 4146 /** 4147 * Enrich 4148 */ 4149 isCheckoutPage: function () { 4150 return $('body').hasClass('woocommerce-checkout') || document.querySelector('.woocommerce-checkout') || 4151 $('body').hasClass('edd-checkout'); 4152 }, 4153 addCheckoutFields : function() { 4154 var utm = ""; 4155 var utms
4155 = getUTMs() 4156 $.each(utmTerms, function (index, name) { 4157 if(index > 0) { 4158 utm+="|"; 4159 } 4160 utm+=name+":"+utms[name]; 4161 }); 4162 var utmIdList = ""; 4163 var utmsIds = getUTMId() 4164 $.each(utmId, function (index, name) { 4165 if(index > 0) { 4166 utmIdList+="|"; 4167 } 4168 utmIdList+=name+":"+utmsIds[name]; 4169 }); 4170 var utmIdListLast = ""; 4171 var utmsIdsLast = getUTMId(true) 4172 $.each(utmId, function (index, name) { 4173 if(index > 0) { 4174 utmIdListLast+="|"; 4175 } 4176 utmIdListLast+=name+":"+utmsIdsLast[name]; 4177 }); 4178 4179 4180 var utmLast = ""; 4181 var utmsLast = getUTMs(true) 4182 $.each(utmTerms, function (index, name) { 4183 if(index > 0) { 4184 utmLast+="|"; 4185 } 4186 utmLast+=name+":"+utmsLast[name]; 4187 }); 4188 4189 var dateTime = getDateTime(); 4190 var landing = Cookies.get('pys_landing_page'); 4191 var lastLanding = Cookies.get('last_pys_landing_page'); 4192 var trafic = Cookies.get('pysTrafficSource'); 4193 var lastTrafic = Cookies.get('last_pysTrafficSource'); 4194 4195 var $form = null; 4196 if($('body').hasClass('woocommerce-checkout')) { 4197 $form = $("form.woocommerce-checkout"); 4198 } else { 4199 $form = $("#edd_purchase_form"); 4200 } 4201 var inputs = {'pys_utm':utm, 4202 'pys_utm_id':utmIdList, 4203 'pys_browser_time':dateTime.join("|"), 4204 'pys_landing':landing, 4205 'pys_source':trafic, 4206 'pys_order_type': $(".wcf-optin-form").length > 0 ? "wcf-optin" : "normal", 4207 4208 'last_pys_landing':lastLanding, 4209 'last_pys_source':lastTrafic, 4210 'last_pys_utm':utmLast, 4211 'last_pys_utm_id':utmIdListLast, 4212 } 4213 4214 Object.keys(inputs).forEach(function(key,index) { 4215 $form.append("<input type='hidden' name='"+key+"' value='"+inputs[key]+"' /> "); 4216 }); 4217 4218 4219 }, 4220 4221 /** 4222 * Whether we may capture what the visitor typed into a form. 4223 * 4224 * This mirrors the consent checks that already guard manageCookies() 4225 * further down. It is evaluated per capture rather than once at 4226 * bind time, so granting consent mid-page starts capture straight 4227 * away without having to rebind the listeners. 4228 * 4229 * With no consent-manager integration configured the answer is yes, 4230 * which leaves behaviour on such sites unchanged. 4231 */ 4232 advancedFormDataConsentGiven: function () { 4233 4234 if ( typeof options.gdpr === 'undefined' ) { 4235 return true; 4236 } 4237 4238 // Cookiebot 4239 if ( options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined' ) { 4240 return Cookiebot.consented === true 4241 || !!Cookiebot.consent[ 'marketing' ] 4242 || !!Cookiebot.consent[ 'statistics' ]; 4243 } 4244 4245 // ConsentMagic 4246 if ( options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== 'undefined' ) { 4247 if ( CS_Data.cs_script_cat.pys == CS_Data.cs_necessary_cat_id || CS_Data.cs_script_cat.pys == 0 ) { 4248 return true; 4249 } 4250 return Cookies.get( 'cs_enabled_cookie_term' + CS_Data.test_prefix + '_' + CS_Data.cs_script_cat.pys ) === 'yes'; 4251 } 4252
4253 // Cookie Law Info / CookieYes 4254 if ( options.gdpr.cookie_law_info_integration_enabled ) { 4255 let cli_cookie = Cookies.get( 'cookieyes-consent' ) ?? Cookies.get( 'wt_consent' ) ?? Cookies.get( 'viewed_cookie_policy' ); 4256 if ( typeof cli_cookie === 'undefined' ) { 4257 return false; 4258 } 4259 if ( cli_cookie === Cookies.get( 'cookieyes-consent' ) || cli_cookie === Cookies.get( 'wt_consent' ) ) { 4260 return getCookieYes( 'analytics' ) === 'yes'; 4261 } 4262 return cli_cookie === 'yes'; 4263 } 4264 4265 // Cookie Notice 4266 if ( options.gdpr.cookie_notice_integration_enabled && typeof cnArgs !== 'undefined' ) { 4267 return Cookies.get( cnArgs.cookieName ) === 'true'; 4268 } 4269 4270 return true; 4271 }, 4272 4273 /** 4274 * js-cookie 2.1.3 builds the cookie string from a fixed list of 4275 * attributes - expires, path, domain, secure - so a sameSite passed 4276 * to Cookies.set() is silently dropped. This cookie holds what the 4277 * visitor typed into a form, so it is written by hand to get both 4278 * SameSite and Secure onto it. 4279 * 4280 * HttpOnly is not available here: Meta's and TikTok's browser pixels 4281 * are fed from this value, so the page has to be able to read it. 4282 */ 4283 setAdvancedFormDataCookie: function ( data ) { 4284 4285 let days = parseInt( options.cookie && options.cookie.advanced_form_data_expire ) || 30; 4286 let expires = new Date(); 4287 expires.setTime( expires.getTime() + days * 864e5 ); 4288 // '' means host-only, which is what every other writer here gets too. 4289 let scope = cookieDomain(); 4290 4291 let cookie = 'pys_advanced_form_data=' + encodeURIComponent( JSON.stringify( data ) ) 4292 + '; expires=' + expires.toUTCString() 4293 + '; path=/' 4294 + ( scope ? '; domain=' + scope : '' ) 4295 + '; samesite=lax'; 4296 4297 // Secure would make the cookie unwritable on a plain-HTTP site, 4298 // which would silently break matching on local and dev installs. 4299 if ( window.location.protocol === 'https:' ) { 4300 cookie += '; secure'; 4301 } 4302 4303 document.cookie = cookie; 4304 4305 // A host-only copy written while the domain was still unknown 4306 // would shadow this one: the older cookie is listed first, and 4307 // both js-cookie and PHP read the first match. 4308 if ( scope ) { 4309 document.cookie = 'pys_advanced_form_data=; path=/; max-age=0'; 4310 } 4311 }, 4312 4313 /** 4314 * Advanced From Data 4315 */ 4316 saveAdvancedFormData: function ( email, phone, firstName, lastName, override = true ) { 4317 4318 if ( !Utils.advancedFormDataConsentGiven() ) { 4319 return; 4320 } 4321 4322 if ( !options.cookie.disabled_advanced_form_data_cookie ) { 4323 let data = Utils.getAdvancedFormData(); 4324 // Ensure data["address"] is an object 4325 4326 if ( email != null ) { 4327 if ( !override ) { 4328 if ( typeof data[ "email" ] === 'undefined' || !data[ "email" ] ) { 4329 data[ "email" ] = email; 4330 } 4331 } else { 4332 data[ "email" ] = email; 4333 } 4334 4335 if ( typeof data[ "emails" ] === 'object' ) { 4336 if ( override ) { 4337 data[ "emails" ] = [ email, ...Object.values( data[ "emails" ] ) ]; 4338 } else { 4339 data[ "emails" ] = [ ...Object.values( data[ "emails" ] ), email ]; 4340 } 4341 } else { 4342 data[ "emails" ] = [ email ]; 4343 } 4344 data[ "emails" ] = [ ...new Set( data[ "emails" ] ) ]; 4345 data[ "emails" ] = data[ "emails" ].slice( 0, 3 ); 4346 } 4347 4348 if ( phone != null ) { 4349 if ( !override ) { 4350 if ( typeof data[ "phone" ] === 'undefined' || !data[ "phone" ] ) { 4351 data[ "phone" ] = phone; 4352 } 4353 } else { 4354 data[ "phone" ] = phone; 4355 } 4356 4357 if ( typeof data[ "phones" ] === 'object' ) { 4358 if ( override ) { 4359 data[ "phones" ] = [ phone, ...Object.values( data[ "phones" ] ) ]; 4360 } else { 4361 data[ "phones" ] = [ ...Object.values( data[ "phones" ] ), phone ]; 4362 } 4363 } else { 4364 data[ "phones" ] = [ phone ]; 4365 } 4366 data[ "phones" ] = [ ...new Set( data[ "phones" ] ) ]; 4367 data[ "phones" ] = data[ "phones" ].slice( 0, 3 ); 4368 } 4369 4370 if ( firstName != null ) { 4371 if ( !override ) { 4372 if ( typeof data[ "first_name" ] === 'undefined' || !data[ "first_name" ] ) { 4373 data[ "first_name" ] = firstName; 4374 } 4375 } else { 4376 data[ "first_name" ] = firstName; 4377 } 4378 4379 if ( typeof data[ "fns" ] === 'object' ) { 4380 if ( override ) { 4381 data[ "fns" ] = [ firstName, ...Object.values( data[ "fns" ] ) ]; 4382 } else { 4383 data[ "fns" ] = [ ...Object.values( data[ "fns" ] ), firstName ]; 4384 } 4385 } else { 4386 data[ "fns" ] = [ firstName ]; 4387 } 4388 data[ "fns" ] = [ ...new Set( data[ "fns" ] ) ]; 4389 data[ "fns" ] = data[ "fns" ].slice( 0, 2 ); 4390 } 4391 4392 if ( lastName != null ) { 4393 if ( !override ) { 4394 if ( typeof data[ "last_name" ] === 'undefined' || !data[ "last_name" ] ) { 4395 data[ "last_name" ] = lastName; 4396 } 4397 } else { 4398 data[ "last_name" ] = lastName; 4399 } 4400
4401 if ( typeof data[ "lns" ] === 'object' ) { 4402 if ( override ) { 4403 data[ "lns" ] = [ lastName, ...Object.values( data[ "lns" ] ) ]; 4404 } else { 4405 data[ "lns" ] = [ ...Object.values( data[ "fns" ] ), lastName ]; 4406 } 4407 } else { 4408 data[ "lns" ] = [ lastName ]; 4409 } 4410 data[ "lns" ] = [ ...new Set( data[ "lns" ] ) ]; 4411 data[ "lns" ] = data[ "lns" ].slice( 0, 2 ); 4412 } 4413 4414 Utils.setAdvancedFormDataCookie( data ); 4415 } else { 4416 // Same cookie, same two disagreeing writers, so the same pair of 4417 // deletes -- see the long note at the disabled_advanced_form_data_cookie 4418 // branch in pysNormalInit(). `domain: ''` means host-only. 4419 Cookies.remove( 'pys_advanced_form_data', { path: '/',domain: cookieDomain() } ) 4420 Cookies.remove( 'pys_advanced_form_data', { path: '/',domain: '' } ) 4421 } 4422 if(GTM.isEnabled()){ 4423 GTM.updateEnhancedConversionData(); 4424 } 4425 if(Analytics.isEnabled()){ 4426 Analytics.updateEnhancedConversionData(); 4427 } else if ( GAds.isEnabled() ) { 4428 GAds.updateEnhancedConversionData(); 4429 } 4430 if ( Bing.isEnabled() && typeof Bing.updateEnhancedConversionData === 'function' ) { 4431 Bing.updateEnhancedConversionData(); 4432 } 4433 }, 4434 getAdvancedMergeFormData: function (allways_provided_data = false) { 4435 const advanced = Utils.getAdvancedFormData(); 4436 let mergedData = {}, 4437 limit = options.tracking_analytics.use_multiple_provided_data || allways_provided_data ? 3 : 1, 4438 address_limit = options.tracking_analytics.use_multiple_provided_data || allways_provided_data ? 2 : 1; 4439 4440 // These values come partly from a cookie PHP also writes, so any of them 4441 // can be missing, null or empty. sha256() throws on anything but a 4442 // string, and hashing '' would send Google the hash of nothing. 4443 const hash = ( value ) => ( typeof value === 'string' && value !== '' ) || typeof value === 'number' 4444 ? sha256( String( value ) ) 4445 : ''; 4446 4447 if ( options.tracking_analytics.use_encoding_provided_data || allways_provided_data) { 4448 mergedData.sha256_email_address = []; 4449 mergedData.sha256_phone_number = []; 4450 mergedData.address = []; 4451 if ( options.tracking_analytics.hasOwnProperty( "userData" ) ) { 4452 if ( options.tracking_analytics.userData.hasOwnProperty( "emails" ) && Object.keys( options.tracking_analytics.userData.emails ).length > 0 ) { 4453 Object.keys( options.tracking_analytics.userData.emails ).forEach( ( key ) => { 4454 mergedData.sha256_email_address.push( hash( options.tracking_analytics.userData.emails[ key ] ) ); 4455 } ) 4456 } 4457 if ( options.tracking_analytics.userData.hasOwnProperty( "phones" ) && Object.keys( options.tracking_analytics.userData.phones ).length > 0 ) { 4458 Object.keys( options.tracking_analytics.userData.phones ).forEach( ( key ) => { 4459 mergedData.sha256_phone_number.push( hash( options.tracking_analytics.userData.phones[ key ] ) ); 4460 } ) 4461 } 4462 if ( options.tracking_analytics.userData.hasOwnProperty( "addresses" ) && Object.keys( options.tracking_analytics.userData.addresses ).length > 0 ) { 4463 Object.keys( options.tracking_analytics.userData.addresses ).forEach( ( ke
4463y ) => { 4464 let address = Object.assign( {}, options.tracking_analytics.userData.addresses[ key ] ); 4465 4466 if ( address.first_name ) { 4467 address.sha256_first_name = hash( address.first_name ); 4468 delete address.first_name; 4469 } 4470 if ( address.last_name ) { 4471 address.sha256_last_name = hash( address.last_name ); 4472 delete address.last_name; 4473 } 4474 mergedData.address.push( address ); 4475 } ) 4476 } 4477 } 4478 4479 let data_persistency = options.data_persistency; 4480 4481 //emails 4482 if ( advanced.emails && Object.keys( advanced.emails ).length > 0 ) { 4483 Object.keys( advanced.emails ).forEach( ( key ) => { 4484 if ( data_persistency === 'recent_data' ) { 4485 mergedData.sha256_email_address.push( hash( advanced.emails[ key ] ) ); 4486 } else { 4487 mergedData.sha256_email_address.unshift( hash( advanced.emails[ key ] ) ); 4488 } 4489 } ) 4490 } 4491 if ( advanced.email && advanced.email !== '' ) { 4492 if ( data_persistency === 'recent_data' ) { 4493 mergedData.sha256_email_address.push( hash( advanced.email ) ); 4494 } else { 4495 mergedData.sha256_email_address.unshift( hash( advanced.email ) ); 4496 } 4497 } 4498 mergedData.sha256_email_address = [ ...new Set( mergedData.sha256_email_address.filter( Boolean ) ) ]; 4499 mergedData.sha256_email_address = mergedData.sha256_email_address.slice( 0, limit ); 4500 4501 //phones 4502 if ( advanced.phones && Object.keys( advanced.phones ).length > 0 ) { 4503 Object.keys( advanced.phones ).forEach( ( key ) => { 4504 if ( data_persistency === 'recent_data' ) { 4505 mergedData.sha256_phone_number.push( hash( advanced.phones[ key ] ) ); 4506 } else { 4507 mergedData.sha256_phone_number.unshift( hash( advanced.phones[ key ] ) ); 4508 } 4509 } ) 4510 } 4511 if ( advanced.phone && advanced.phone !== '' ) { 4512 if ( data_persistency === 'recent_data' ) { 4513 mergedData.sha256_phone_number.push( hash( advanced.phone ) ); 4514 } else { 4515 mergedData.sha256_phone_number.unshift( hash( advanced.phone ) ); 4516 } 4517 } 4518 mergedData.sha256_phone_number = [ ...new Set( mergedData.sha256_phone_number.filter( Boolean ) ) ]; 4519 mergedData.sha256_phone_number = mergedData.sha256_phone_number.slice( 0, limit ); 4520 4521 //first and last names 4522 if ( advanced.fns && Object.keys( advanced.fns ).length > 0 ) { 4523 Object.entries( advanced.fns ).forEach( ( item ) => { 4524 if ( mergedData.address.length < address_limit ) { 4525 let first_name = item[ 1 ]; 4526 let last_name = ( advanced.lns && Object.keys( advanced.lns ).length > 0 ) ? ( advanced.lns[ item[ 0 ] ] ? advanced.lns[ item[ 0 ] ] : advanced.lns[ 0 ] ) : ''; 4527 let address = { 4528 sha256_first_name: hash( first_name ), 4529 sha256_last_name: hash( last_name ), 4530 // Add other address properties here if they exist in advanced 4531 }; 4532 if ( address.sha256_first_name || address.sha256_last_name ) { 4533 mergedData.address.push( address ); 4534 } 4535 } 4536 } ) 4537 } 4538 4539 if ( advanced.first_name || advanced.last_name ) { 4540 if ( mergedData.address.length < address_limit ) { 4541 let address = { 4542 sha256_first_name: hash( advanced.first_name ), 4543 sha256_last_name: hash( advanced.last_name ), 4544 // Add other address properties here if they exist in advanced 4545 }; 4546 if ( address.sha256_first_name || address.sha256_last_name ) { 4547 mergedData.address.push( address ); 4548 } 4549 } 4550 } 4551 } else { 4552 mergedData.email = []; 4553 mergedData.phone_number = []; 4554 mergedData.address = []; 4555 if ( options.tracking_analytics.hasOwnProperty( "userData" ) ) { 4556 if ( options.tracking_analytics.userData.hasOwnProperty( "emails" ) && Object.keys( options.tracking_analytics.userData.emails ).length > 0 ) { 4557 Object.keys( options.tracking_analytics.userData.emails ).forEach( ( ke
4557y ) => { 4558 mergedData.email.push( options.tracking_analytics.userData.emails[ key ] ); 4559 } ) 4560 } 4561 if ( options.tracking_analytics.userData.hasOwnProperty( "phones" ) && Object.keys( options.tracking_analytics.userData.phones ).length > 0 ) { 4562 Object.keys( options.tracking_analytics.userData.phones ).forEach( ( key ) => { 4563 mergedData.phone_number.push( options.tracking_analytics.userData.phones[ key ] ); 4564 } ) 4565 } 4566 if ( options.tracking_analytics.userData.hasOwnProperty( "addresses" ) && Object.keys( options.tracking_analytics.userData.addresses ).length > 0 ) { 4567 Object.keys( options.tracking_analytics.userData.addresses ).forEach( ( key ) => { 4568 mergedData.address.push( options.tracking_analytics.userData.addresses[ key ] ); 4569 } ) 4570 } 4571 } 4572 4573 let data_persistency = options.data_persistency; 4574 4575 //emails 4576 if ( advanced.emails && Object.keys( advanced.emails ).length > 0 ) { 4577 Object.keys( advanced.emails ).forEach( ( key ) => { 4578 if ( data_persistency === 'recent_data' ) { 4579 mergedData.email.push( advanced.emails[ key ] ); 4580 } else { 4581 mergedData.email.unshift( advanced.emails[ key ] ); 4582 } 4583 } ) 4584 } 4585 if ( advanced.email && advanced.email !== '' ) { 4586 if ( data_persistency === 'recent_data' ) { 4587 mergedData.email.push( advanced.email ); 4588 } else { 4589 mergedData.email.unshift( advanced.email ); 4590 } 4591 } 4592 mergedData.email = [ ...new Set( mergedData.email ) ]; 4593 mergedData.email = mergedData.email.slice( 0, limit ); 4594 4595 //phones 4596 if ( advanced.phones && Object.keys( advanced.phones ).length > 0 ) { 4597 Object.keys( advanced.phones ).forEach( ( key ) => { 4598 if ( data_persistency === 'recent_data' ) { 4599 mergedData.phone_number.push( advanced.phones[ key ] ); 4600 } else { 4601 mergedData.phone_number.unshift( advanced.phones[ key ] ); 4602 } 4603 } ) 4604 } 4605 if ( advanced.phone && advanced.phone !== '' ) { 4606 if ( data_persistency === 'recent_data' ) { 4607 mergedData.phone_number.push( advanced.phone ); 4608 } else { 4609 mergedData.phone_number.unshift( advanced.phone ); 4610 } 4611 } 4612 mergedData.phone_number = [ ...new Set( mergedData.phone_number ) ]; 4613 mergedData.phone_number = mergedData.phone_number.slice( 0, limit ); 4614 4615 //first and last names 4616 if ( advanced.fns && Object.keys( advanced.fns ).length > 0 ) { 4617 Object.entries( advanced.fns ).forEach( ( item ) => { 4618 if ( mergedData.address.length < address_limit ) { 4619 let first_name = item[ 1 ]; 4620 let last_name = ( advanced.lns && Object.keys( advanced.lns ).length > 0 ) ? ( advanced.lns[ item[ 0 ] ] ? advanced.lns[ item[ 0 ] ] : advanced.lns[ 0 ] ) : ''; 4621 let address = { 4622 first_name: first_name || '', 4623 last_name: last_name || '', 4624 // Add other address properties here if they exist in advanced 4625 }; 4626 mergedData.address.push( address ); 4627 } 4628 } ) 4629 } 4630 4631 if ( advanced.first_name || advanced.last_name ) { 4632 if ( mergedData.address.length < address_limit ) { 4633 let address = { 4634 first_name: advanced.first_name || '', 4635 last_name: advanced.last_name || '', 4636 // Add other address properties here if they exist in advanced 4637 }; 4638 mergedData.address.push( address ); 4639 } 4640 } 4641 } 4642 4643 Utils.removeEmptyProperties( mergedData ); 4644 return mergedData; 4645 }, 4646 removeEmptyProperties : function(obj) { 4647 4648 if (obj.hasOwnProperty('address')) { 4649 // Iterate over each array in 'address' 4650 for (let i = 0; i < obj['address'].length; i++) { 4651 let address1 = obj['address'][i]; 4652 4653 // Compare with every other array in 'address' 4654 for (let j = i + 1; j < obj['address'].length; j++) { 4655 let address2 = obj['address'][j]; 4656 4657 // If duplicate properties are found, remove them from address2 4658 for (let prop in address1) { 4659 if (address1[prop] === address2[prop]) { 4660 delete address2[prop]; 4661 } 4662 } 4663 } 4664 } 4665 } 4666 4667 for (let key in obj) { 4668 if (obj[key] === null || obj[key] === undefined || obj[key] === '') { 4669 delete obj[key]; 4670 } else if (Array.isArray(obj[key])) { 4671 obj[key] = [...new Set(obj[key])]; // Remove duplicates from the array 4672 obj[key] = obj[key].filter(item => item !== null && item !== undefined && item !== ''); 4673 // Process objects inside arrays 4674 obj[key] = obj[key].map(item => { 4675 if (item === null) { 4676 return; // Skip null elements 4677 } else if (Array.isArray(item)) { 4678 return this.removeEmptyProperties(item); 4679 } else if (typeof item === 'object') { 4680 let cleanedItem = this.removeEmptyProperties(item); 4681 if (Object.keys(cleanedItem).length > 0) { 4682 return cleanedItem; 4683 } 4684 } else { 4685 return item; 4686 } 4687 }); 4688 // Remove empty objects and null values from the array 4689 obj[key] = obj[key].filter(item => item !== undefined && !(typeof item === 'object' && Object.keys(item).length === 0)); 4690 if (obj[key].length === 0) { 4691 delete obj[key]; 4692 } 4693 } else if (typeof obj[key] === 'object') { 4694 this.removeEmptyProperties(obj[key]); 4695 if (Object.keys(obj[key]).length === 0) { 4696 delete obj[key]; 4697 } 4698 } 4699 } 4700 return obj;
4701 }, 4702 getAdvancedFormData: function () { 4703 let dataStr = Cookies.get("pys_advanced_form_data"); 4704 if(dataStr === undefined) { 4705 return {'first_name':"",'last_name':"",'email':"",'phone':"",'fns':[],'lns':[],'emails':[],'phones':[]}; 4706 } else { 4707 return JSON.parse(dataStr); 4708 } 4709 }, 4710 4711 /** 4712 * Advanced Matching that the server is no longer allowed to render. 4713 * 4714 * Page HTML can be served out of a shared cache, so the server 4715 * leaves the visitor's own email, phone and name out of pysOptions. 4716 * The values are on this device in pys_advanced_form_data, so the 4717 * browser puts them back just before each pixel initialises. 4718 * Normalisation mirrors the PHP side field for field -- if one 4719 * moves, both move. 4720 */ 4721 AdvancedMatching: { 4722 4723 normalizeEmail: function (value) { 4724 if (typeof value !== 'string') { 4725 return ''; 4726 } 4727 var email = value.trim().toLowerCase(); 4728 if (email.length === 0 || email.length > 254) { 4729 return ''; 4730 } 4731 return /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/.test(email) ? email : ''; 4732 }, 4733 4734 normalizePhone: function (value) { 4735 if (typeof value !== 'string') { 4736 return ''; 4737 } 4738 var digits = value.replace(/[^0-9]/g, ''); 4739 return digits.length > 0 ? digits : ''; 4740 }, 4741 4742 normalizeName: function (value) { 4743 if (typeof value !== 'string' || value.length > 1024) { 4744 return ''; 4745 } 4746 return value.trim().toLowerCase(); 4747 }, 4748 4749 /** 4750 * The identity this device carries, normalised and possibly 4751 * all-empty. Never throws when the cookie is absent or corrupt. 4752 */ 4753 cookieIdentity: function () { 4754 var data; 4755 try { 4756 data = Utils.getAdvancedFormData() || {}; 4757 } catch (e) { 4758 data = {}; 4759 } 4760 return { 4761 email: this.normalizeEmail(data.email || ''), 4762 phone: this.normalizePhone(data.phone || ''), 4763 first_name: this.normalizeName(data.first_name || ''), 4764 last_name: this.normalizeName(data.last_name || '') 4765 }; 4766 }, 4767 4768 /** 4769 * @param {object} target The pixel's advanced matching object, edited in place. 4770 * @param {object} mapping {targetKey: {field, hash}} 4771 * @param {object} identity Optional; defaults to cookieIdentity(). 4772 * @param {string} persistency Optional; defaults to options.data_persistency. 4773 */ 4774 merge: function (target, mapping, identity, persistency) { 4775 4776 if (!target || !mapping) { 4777 return target; 4778 } 4779 4780 identity = identity || this.cookieIdentity(); 4781 4782 if (typeof persistency === 'undefined') { 4783 persistency = typeof options !== 'undefined' ? options.data_persistency : ''; 4784 } 4785 4786 var keepData = persistency === 'keep_data'; 4787 4788 Object.keys(mapping).forEach(function (targetKey) { 4789 4790 var rule = mapping[targetKey]; 4791 var value = identity[rule.field]; 4792 4793 if (typeof value !== 'string' || value.length === 0) { 4794 return; 4795 } 4796 4797 var hasServerValue = typeof target[targetKey] === 'string' 4798 && target[targetKey].length > 0;
4799 4800 if (hasServerValue && !keepData) { 4801 return; 4802 } 4803 4804 if (rule.hash) { 4805 if (typeof sha256 !== 'function') { 4806 return; 4807 } 4808 target[targetKey] = sha256(value); 4809 } else { 4810 target[targetKey] = value; 4811 } 4812 }); 4813 4814 return target; 4815 } 4816 }, 4817 4818 getFormFilledData: function ( event ) { 4819 // First, resolve static/dynamic parameters 4820 if (event.params && Object.keys(event.params).length > 0) { 4821 Object.entries(event.params).forEach(([key, value]) => { 4822 // Resolve parameter value (static or dynamic) 4823 const resolvedValue = resolveParamValue(value, key); 4824 if (resolvedValue !== null) { 4825 event.params[key] = resolvedValue; 4826 } 4827 else { 4828 delete event.params[key]; 4829 } 4830 }); 4831 } 4832 4833 // Then, handle dynamic fields from cookies (existing logic) 4834 4835 if ( Object.keys(options.track_dynamic_fields).length > 0 && Object.keys(event.params).length > 0 ) { 4836 Object.entries(event.params).forEach((item) => { 4837 4838 if ( options.track_dynamic_fields.hasOwnProperty(item[0]) ) { 4839 let fieldData = Cookies.get('pys_dyn_field_' + item[1] ); 4840 4841 if(fieldData !== undefined && fieldData !== '') { 4842 event.params[item[0]] = fieldData; 4843 } else { 4844 delete event.params[item[0]]; 4845 } 4846 } 4847 }) 4848 } 4849 return event; 4850 }, 4851 4852 registerGadsPhoneConversion( conversion_id ) { 4853 // Ads phone call conversion 4854 if ( options.triggerEventTypes.hasOwnProperty( 'gads_phone_conversion' ) ) { 4855 Object.entries( options.triggerEventTypes.gads_phone_conversion ).forEach( function ( [ trigger_id, data ] ) { 4856 if ( data.enabled && Object.entries( data.numbers ).length > 0 ) { 4857 4858 let allowed_pixels = []; 4859 Object.entries( options.triggerEvents[ trigger_id ] ).forEach( function ( [ pixel, pixel_data ] ) { 4860 if ( pixel !== 'google_ads' ) { 4861 return; 4862 } 4863 4864 allowed_pixels.push( ...pixel_data.conversion_ids ); 4865 } ) 4866 4867 if ( !allowed_pixels.includes( conversion_id ) ) { 4868 return; 4869 } 4870 4871 data.numbers.forEach( function ( pair ) { 4872 if ( !pair.number || !pair.label ) { 4873 return; 4874 } 4875 4876 let phoneCb = function () { 4877 Utils.fireTriggerEvent( trigger_id ) 4878 }; 4879 4880 Utils.gtagPush( 'config', conversion_id + '/' + pair.label, { 4881 phone_conversion_number: pair.number, 4882 phone_conversion_callback: phoneCb 4883 } ); 4884 } ) 4885 } 4886 } ); 4887 } 4888 } 4889 } 4890 4891 4892 }(options); 4893 4894 var TikTok = function (options) { 4895 4896 var initialized = false; 4897 4898 // TikTok appends its click id to the ad landing page URL. Unlike Meta's 4899 // fbc it is sent to the Events API raw, with no 'tt.1.<timestamp>.' 4900 // style prefix, so no formatting is applied here. 4901 var TTCLID_COOKIE = 'pys_ttclid'; 4902 4903 // Click id seen on this page load, kept so a brand new ad click beats a 4904 // cookie left over from an earlier one. Mirrors the same memo in 4905 // TikTokServer, and means the URL is only ever read from storeTtclid(), 4906 // behind the consent check in loadPixel(). 4907 var freshTtclid = ''; 4908 4909 // Persist the click id so it is still around at checkout, which is where 4910 // it matters most for match rate. Deliberately writes our own cookie and 4911 // never TikTok's: the format of theirs is theirs to change. 4912 var storeTtclid = function () { 4913 var ttclid = getUrlParameter('ttclid'); 4914 4915 if (!ttclid) { 4916 return; 4917 } 4918 4919 freshTtclid = ttclid; 4920 4921 if (!options.cookie || options.cookie.disabled_all_cookie) { 4922 return; 4923 } 4924 4925 Cookies.set(TTCLID_COOKIE, ttclid, { 4926 expires: parseInt(options.cookie_duration), 4927 path: '/', 4928 domain: cookieDomain() 4929 }); 4930 }; 4931 4932 // Read the click id the same way PHP does: this page load's value first, 4933 // then the cookie the TikTok SDK writes itself, then ours. 4934 var getTtclid = function () { 4935 return freshTtclid 4936 || Cookies.get('ttclid') 4937 || Cookies.get(TTCLID_COOKIE) 4938 || ''; 4939 }; 4940 4941 function fireEvent(name, event) { 4942 4943 if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name,'tiktok')) { 4944 return; 4945 } 4946 4947 var data = event.params; 4948 4949 var ids = event.pixelIds.filter(function (pixelId) { 4950 return !Utils.hideMatchingPixel(pixelId, 'tiktok'); 4951 }) 4952 var params = {}; 4953 Utils.copyProperties(data, params); 4954 4955 params.eventID = Utils.generateUniqueId(event); 4956 4957 if(ids.length > 0){ 4958 TikTok.fireEventAPI(name, event, params); 4959 } 4960 ids.forEach(function(pixelId){ 4961 if (options.debug) { 4962 console.log('[TikTok] ' + name, params,"pixel_id",pixelId); 4963 } 4964 4965 ttq.instance(pixelId).track(name,params) 4966 4967 }); 4968 4969 var customEvent = new CustomEvent('tiktok_event_sent', { 4970 detail: { 4971 eventName: name, 4972 params: params, 4973 pixel_ids: ids 4974 } 4975 }); 4976 window.dispatchEvent(customEvent); 4977 } 4978 4979 return { 4980 tag: function() { 4981 return "tiktok"; 4982 }, 4983 isEnabled: function () { 4984 return options.hasOwnProperty('tiktok'); 4985 }, 4986 getHidePixel: function(){ 4987 if(this.isEnabled() && options.tiktok.hasOwnProperty('hide_pixels')) 4988 { 4989 return options.tiktok.hide_pixels; 4990 } 4991 return []; 4992 }, 4993 disable: function () { 4994 initialized = false; 4995 }, 4996 4997 loadPixel:function () { 4998 if (initialized || !this.isEnabled() || !Utils.consentGiven('tiktok')) { 4999 return; 5000 } 5001 5002 // Same first-visit hold as Facebook.loadPixel(): TikTok's 5003 // advanced matching carries external_id too. 5004 if (Utils.holdForIdentity(function () { TikTok.loadPixel(); })) { 5005 return; 5006 } 5007 for (var i = 0; i < options.tiktok.pixelIds.length; i++) { 5008 var trackingId = options.tiktok.pixelIds[i]; 5009 if (!Utils.hideMatchingPixel(trackingId, 'tiktok')) { 5010 !function (w, d, t) { 5011 w.TiktokAnalyticsObject=t; 5012 var ttq=w[t]=w[t]||[]; 5013 ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie"]; 5014 ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}; 5015 for(var i=0;i<ttq.methods.length;i++) 5016 ttq.setAndDefer(ttq,ttq.methods[i]); 5017 ttq.instance=function(t){for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e}; 5018 ttq.load=function(e,n){var i="https://analytics.tiktok.com/i18n/pixel/events.js";ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=i,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};var o=document.createElement("script");o.type="text/javascript",o.async=!0,o.src=i+"?sdkid="+e+"&lib="+t;var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(o,a)}; 5019 5020 //ttq.load('C60QSCQRVDG9JAKNPK2G'); 5021 //ttq.page(); 5022 }(window, document, 'ttq'); 5023 break; 5024 } 5025 } 5026 5027 // Consent for TikTok is confirmed by the guard above, so the 5028 // click id may be persisted now. 5029 storeTtclid(); 5030 5031 var ids = options.tiktok.pixelIds.filter(function (pixelId) { 5032 return !Utils.hideMatchingPixel(pixelId, 'tiktok'); 5033 }) 5034 ids.forEach(function (pixelId) { 5035 5036 ttq.load(pixelId); 5037 ttq.page(); 5038 let advancedMatching = {}; 5039 5040 if (options.tiktok.hasOwnProperty('advanced_matching')) { 5041 advancedMatching = Object.assign({}, options.tiktok.advanced_matching || {}); 5042 } 5043 5044 // The server leaves the visitor's own email and ph
5044one out of 5045 // the page so a shared cache cannot replay them. TikTok hashes 5046 // the same way on both sides. 5047 Utils.AdvancedMatching.merge(advancedMatching, { 5048 sha256_email: { field: 'email', hash: true }, 5049 sha256_phone_number: { field: 'phone', hash: true } 5050 }); 5051 5052 if (!advancedMatching.hasOwnProperty("external_id")) { 5053 if (Cookies.get('pbid')) { 5054 advancedMatching["external_id"] = Cookies.get('pbid'); 5055 } 5056 } else if (advancedMatching.external_id != Cookies.get('pbid')) { 5057 advancedMatching["external_id"] = Cookies.get('pbid') ? Cookies.get('pbid') : advancedMatching.external_id; 5058 } 5059 5060 if (Object.keys(advancedMatching).length > 0) { 5061 ttq.instance(pixelId).identify(advancedMatching) 5062 } 5063 }); 5064 initialized = true; 5065 5066 if (options.staticEvents.hasOwnProperty('tiktok')) { 5067 var timeout = 1; 5068 setTimeout(function (timeout) { 5069 Utils.fireStaticEvents('tiktok', timeout); 5070 }, 1500, timeout) 5071 } 5072 }, 5073 5074 fireEvent: function (name, data) { 5075 if (!initialized || !this.isEnabled()) { 5076 return false; 5077 } 5078 data.delay = data.delay || 0; 5079 5080 if (data.delay === 0) { 5081 fireEvent(name, data); 5082 } else { 5083 setTimeout(function (name, params) { 5084 fireEvent(name, params); 5085 }, data.delay * 1000, name, data); 5086 } 5087 return true; 5088 }, 5089 5090 fireEventAPI: function (name, event, params) { 5091 5092 var ids = event.pixelIds.filter(function (pixelId) { 5093 return !Utils.hideMatchingPixel(pixelId, 'tiktok'); 5094 }) 5095 var notCachedEventsIds = new Array(); 5096 var isAddToCartFromJs = options.woo.hasOwnProperty("addToCartCatchMethod") 5097 && options.woo.addToCartCatchMethod === "add_cart_js"; 5098 if(!isAddToCartFromJs) { 5099 notCachedEventsIds.push('woo_add_to_cart_on_button_click') 5100 } 5101 5102 if(options.tiktok.serverApiEnabled) { 5103 if(!notCachedEventsIds.includes(event.e_id)) { 5104 5105 var isApiDisabled = options.gdpr.all_disabled_by_api || 5106 options.gdpr.tiktok_disabled_by_api || 5107 options.gdpr.cookiebot_integration_enabled || 5108 options.gdpr.cookie_notice_integration_enabled || 5109 options.gdpr.consent_magic_integration_enabled || 5110 options.gdpr.cookie_law_info_integration_enabled; 5111 // Update eventID 5112 5113 // send event from server if they were block by gdpr or need send with delay 5114 if( options.ajaxForServerEvent || isApiDisabled || event.delay > 0 || event.type !== "static" ){ 5115 5116 // Fallback for the case where the landing page was 5117 // served from a cache and no cookie ever reached 5118 // PHP. The click id goes into a copy of the params: 5119 // this same object is handed to ttq.track(), and the 5120 // browser pixel reads the click id by itself. 5121 var apiParams = {}; 5122 Utils.copyProperties(params, apiParams); 5123 5124 var ttclid = getTtclid(); 5125 if (ttclid) { 5126 apiParams.ttclid = ttclid; 5127 } 5128 5129 var json = { 5130 action: 'pys_tiktok_api_event', 5131 pixel: TikTok.tag(), 5132 event: name, 5133 event_slug: event.e_id, 5134 ids: ids, 5135 data:apiParams, 5136 url:window.location.href, 5137 event_id:params.eventID, 5138 ajax_event:options.ajax_event 5139 }; 5140 5141 let delay = 0; 5142 try { 5143 if (sessionStorage) { 5144 Object.keys(sessionStorage) 5145 .filter(key => key.startsWith('wc_fragments_')) 5146 .forEach((key) => { 5147 let value = sessionStorage.getItem(key); 5148 if (event.e_id === 'woo_add_to_cart_on_button_click') {
5149 delay = 2000; 5150 } 5151 }); 5152 } 5153 } catch (error) { 5154 console.error('sessionStorage is not accessible:', error); 5155 } 5156 5157 5158 if(event.hasOwnProperty('woo_order')) { 5159 json['woo_order'] = event.woo_order; 5160 } 5161 5162 if(event.hasOwnProperty('edd_order')) { 5163 json['edd_order'] = event.edd_order; 5164 } 5165 5166 // Forward the order key so the server can bind the 5167 // purchase event to its specific order (RestPurchaseGuard). 5168 if(event.hasOwnProperty('order_key') && event.order_key) { 5169 json['order_key'] = event.order_key; 5170 } 5171 if (event.e_id === "automatic_event_internal_link" || event.e_id === "automatic_event_outbound_link" || name == 'PageView') { 5172 delay = delay === 0 ? 500 : delay; 5173 setTimeout(() => Utils.sendServerAjaxRequest(options.ajaxUrl, json), delay); 5174 } else if (event.type != 'static') { 5175 setTimeout(() => Utils.sendServerAjaxRequest(options.ajaxUrl, json), delay); 5176 } 5177 5178 if ( ( event.type == 'static' && options.ajaxForServerStaticEvent ) || ( event.hasOwnProperty( 'ajaxFire' ) && event.ajaxFire ) ) { 5179 setTimeout( () => Utils.sendServerAjaxRequest( options.ajaxUrl, json ), delay ); 5180 } 5181 } 5182 } 5183 } 5184 }, 5185 5186 onClickEvent: function (event) { 5187 this.fireEvent(event.name, event); 5188 }, 5189 5190 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form) { 5191 5192 window.pysWooProductData = window.pysWooProductData || []; 5193 if (!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 5194 return; 5195 5196 if (window.pysWooProductData.hasOwnProperty(product_id)) { 5197 if (window.pysWooProductData[product_id].hasOwnProperty(this.tag())) { 5198 5199 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 5200 5201 Utils.copyProperties(window.pysWooProductData[product_id][this.tag()]['params'], event.params); 5202 5203 // maybe customize value option 5204 if (options.woo.addToCartOnButtonValueEnabled && options.woo.addToCartOnButtonValueOption !== 'global') { 5205 5206 if (product_type === Utils.PRODUCT_BUNDLE) { 5207 var data = $(".bundle_form .bundle_data").data("bundle_form_data"); 5208 var items_sum = getBundlePriceOnSingleProduct(data); 5209 event.params.value = (parseFloat(data.base_price) + items_sum) * qty; 5210 } else { 5211 event.params.value = event.params.value * qty; 5212 } 5213 } 5214 5215 event.params.quantity = qty; 5216 5217 this.fireEvent(event.name, event); 5218 5219 } 5220 } 5221 }, 5222 5223 onWooAddToCartOnButtonEvent: function (product_id) { 5224 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 5225 return; 5226 5227 if (window.pysWooProductData.hasOwnProperty(product_id)) { 5228 if (window.pysWooProductData[product_id].hasOwnProperty(this.tag())) { 5229 var productData = window.pysWooProductData[product_id][this.tag()] 5230 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]) 5231 5232 Utils.copyProperties(productData['params'], event.params) 5233 event.pixelIds = productData['pixelIds']; 5234 this.fireEvent(event.name, event); 5235 } 5236 } 5237 }, 5238 5239 onEddAddToCartOnButtonEvent : function (download_id, price_index, qty) { 5240 if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 5241 return; 5242 var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]); 5243 5244 5245 if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) { 5246 5247 var index; 5248 5249 if (price_index) { 5250 index = download_id + '_' + price_index; 5251 } else { 5252 index = download_id; 5253 } 5254 5255 if (window.pysEddProductData[download_id].hasOwnProperty(index)) { 5256 if (window.pysEddProductData[download_id][index].hasOwnProperty(this.tag())) { 5257 5258 Utils.copyProperties(window.pysEddProductData[download_id][index][this.tag()].params, event.params); 5259 this.fireEvent(event.name,event); 5260 5261 } 5262 } 5263 5264 } 5265 }, 5266 5267 onPageScroll: function (event) { 5268 if (initialized && this.isEnabled()) { 5269 this.fireEvent(event.name, event); 5270 } 5271 }, 5272 5273 onWatchVideo: function (event) { 5274 if (initialized && this.isEnabled() && !event.fired) { 5275 this.fireEvent(event.name, event); 5276 } 5277 }, 5278 5279 onCommentEvent: function (event) { 5280 if (initialized && this.isEnabled()) { 5281 this.fireEvent(event.name, event); 5282 } 5283 }, 5284 5285 onAdSenseEvent: function (event) { 5286 if (initialized && this.isEnabled()) { 5287 this.fireEvent(event.name, event); 5288 } 5289 }, 5290 5291 onTime: function (event) { 5292 if (initialized && this.isEnabled()) { 5293 this.fireEvent(event.name, event); 5294 } 5295 }, 5296 } 5297 }(options); 5298 5299 var OpenAI = function (options) { 5300 5301 let initialized = false, 5302 sentIdentity = null, 5303 identityWaitTimer = null, 5304 identityWaitExpired = false; 5305 5306 function visiblePixelIds(ids) { 5307 return (ids || []).filter(function (pixelId) { 5308 return !Utils.hideMatchingPixel(pixelId, 'openai'); 5309 }); 5310 } 5311 /** 5312 * Is the pixel in OpenAI's restricted mode? 5313 */ 5314 function optOutMode() { 5315 5316 if ( options.openai && options.openai.opt_out ) { 5317 return true; 5318 } 5319 5320 return !! ( options.gdpr 5321 && options.gdpr.consent_magic_integration_enabled 5322 && typeof CS_Data !== 'undefined' 5323 && Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) ); 5324 } 5325 5326 5327 const SDK_DATA_FIELDS = { 5328 contents: ['type', 'amount', 'currency', 'contents'], 5329 custom: ['type', 'plan_id', 'amount', 'currency', 'contents'], 5330 customer_action: ['type', 'amount', 'currency'], 5331 plan_enrollment: ['type', 'plan_id', 'amount', 'currency', 'contents'] 5332 }; 5333 5334 const SDK_ITEM_FIELDS = ['id', 'name', 'content_type', 'quantity', 'amount', 'currency']; 5335 5336 function sdkInteger(value) { 5337 let number = typeof value === 'string' ? Number(value.trim()) : value; 5338 5339 return typeof number === 'number' && isFinite(number) ? Math.round(number) : null; 5340 } 5341 5342 function sdkCurrency(value) { 5343 return typeof value === 'string' && /^[A-Za-z]{3}$/.test(value) ? value : null; 5344 } 5345 5346 function sdkText(value) { 5347 if (typeof value === 'string') { 5348 return value === '' ? null : value; 5349 } 5350 5351 return typeof value === 'number' && isFinite(value) ? String(value) : null; 5352 } 5353 5354 /** 5355 * One contents[] item, with only the keys the SDK documents. 5356 * 5357 * @param item the item as configured or collected 5358 * @param eventHasCurrency true when data.currency is set: an item amount 5359 * needs a currency, its own or the event's 5360 */ 5361 function sdkSafeItem(item, eventHasCurrency) { 5362 5363 if (!item || typeof item !== 'object' || Array.isArray(item)) { 5364 return null; 5365 } 5366 5367 let safe = {}; 5368 5369 SDK_ITEM_FIELDS.forEach(function (field) { 5370 if (item[field] === undefined || item[field] === null || item[field] === '') { 5371 return; 5372 } 5373 5374 if (field === 'quantity' || field === 'amount') { 5375 let number = sdkInteger(item[field]); 5376 5377 if (number !== null) { 5378 safe[field] = number; 5379 } 5380 5381 return; 5382 } 5383 5384 if (field === 'currency') { 5385 let currency = sdkCurrency(item.currency); 5386 5387 if (currency !== null) { 5388 safe.currency = currency; 5389 } 5390 5391 return; 5392 } 5393 5394 let text = sdkText(item[field]); 5395 5396 if (text !== null) { 5397 safe[field] = text; 5398 } 5399 }); 5400 5401 if (safe.hasOwnProperty('amount') && !safe.hasOwnProperty('currency') && !eventHasCurrency) { 5402 delete safe.amount; 5403 } 5404 5405 return Object.keys(safe).length ? safe : null; 5406 } 5407 5408 /** 5409 * The event data, with only the fields the SDK documents for its shape. 5410 */ 5411 function sdkSafeData(data) { 5412 5413 if (!data || typeof data !== 'object') { 5414 return {}; 5415 } 5416 5417 let allowed = SDK_DATA_FIELDS[data.type]; 5418 5419 if (!allowed) { 5420 return data; 5421 } 5422 5423 let safe = {}; 5424 5425 allowed.forEach(function (field) { 5426 if (data[field] !== undefined && data[field] !== null && data[field] !== '') { 5427 safe[field] = data[field]; 5428 } 5429 }); 5430 5431 if (safe.hasOwnProperty('plan_id')) { 5432 let planId = sdkText(safe.plan_id); 5433 5434 if (planId === null) { 5435 delete safe.plan_id; 5436 } else { 5437 safe.plan_id = planId; 5438 } 5439 } 5440 5441 if (safe.hasOwnProperty('currency')) { 5442 let currency = sdkCurrency(safe.currency); 5443 5444 if (currency === null) { 5445 delete safe.currency; 5446 } else { 5447 safe.currency = currency; 5448 } 5449 } 5450 5451 if (safe.hasOwnProperty('amount')) { 5452 let amount = sdkInteger(safe.amount); 5453 5454 // The SDK refuses an amount with no currency beside it. 5455 if (amount === null || !safe.hasOwnProperty('currency')) { 5456 delete safe.amount; 5457 } else { 5458 safe.amount = amount; 5459 } 5460 } 5461 5462 if (safe.hasOwnProperty('contents')) { 5463 let items = Array.isArray(safe.contents) 5464 ? safe.contents 5465 .map(function (item) { return sdkSafeItem(item, safe.hasOwnProperty('currency')); }) 5466 .filter(function (item) { return item !== null; }) 5467 : []; 5468 5469 if (items.length) { 5470 safe.contents = items; 5471 } else { 5472 delete safe.contents; 5473 } 5474 } 5475 5476 return safe; 5477 } 5478 5479 function fireEvent(name, event) { 5480 5481 if (typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name, 'openai')) { 5482 return; 5483 } 5484 5485 if (typeof window.oaiq !== 'function') { 5486 return; 5487 } 5488 5489 let ids = visiblePixelIds(event.pixelIds); 5490 5491 if (ids.length === 0) { 5492 return; 5493 } 5494 5495 let data = (event.params && event.params.data) ? Utils.clone(event.params.data) : {}; 5496 5497 let eventOptions = {}, 5498 eventId = Utils.generateUniqueId(event); 5499 5500 if (eventId) { 5501 eventOptions.event_id = eventId; 5502 } 5503 5504 if (event.params && event.params.custom_event_name) { 5505 eventOptions.custom_event_name = event.params.custom_event_name; 5506 } 5507 5508 OpenAI.fireEventAPI(name, event, data, eventId, ids); 5509 5510 let browserData = sdkSafeData(data); 5511 5512 if (optOutMode()) { 5513 eventOptions.opt_out = true; 5514 } 5515 5516 if (options.debug) { 5517 console.log('[OpenAI] ' + name, browserData, eventOptions, 'pixel_ids', ids); 5518 } 5519 5520 ids.forEach(function (pixelId) { 5521 oaiq('measureSingle', pixelId, name, browserData, eventOptions); 5522 }); 5523 5524 let customEvent = new CustomEvent('openai_event_sent', { 5525 detail: { 5526 eventName: name, 5527 data: data, 5528 options: eventOptions, 5529 pixel_ids: ids 5530 } 5531 }); 5532 window.dispatchEvent(customEvent); 5533 } 5534 5535 /** 5536 * True while the visitor's identity is still in flight. 5537 */ 5538 function identityPending() { 5539 return ! identityWaitExpired 5540 && ! options.dynamicDataApplied 5541 && !! options.dynamicDataUrl; 5542 } 5543 5544 5545 const OPENAI_USER_FIELDS = [ 5546 'email_sha256', 5547 'phone_number_sha256', 5548 'external_id_sha256', 5549 'first_name_sha256', 5550 'last_name_sha256', 5551 'country', 5552 'city', 5553 'region', 5554 'postal_code' 5555 ]; 5556 5557 /** 5558 * OpenAI normalises more aggressively than the other pixels: the phone 5559 * is reduced to its national digit form (leading +, 00 and zeros 5560 * stripped, then length-validated), and names lose every punctuation 5561 * and whitespace character. Mirrors pys_openai_normalize_* in 5562 * modules/openai/function_helpers.php. 5563 */ 5564 function openaiCookieIdentity() { 5565 5566 let raw; 5567 5568 try { 5569 raw = Utils.getAdvancedFormData() || {}; 5570 } catch (e) { 5571 raw = {}; 5572 } 5573 5574 let phone = typeof raw.phone === 'string' 5575 ? raw.phone.trim().replace(/[\s().-]+/g, '') 5576 : ''; 5577 5578 if (!/^\+?[0-9]+$/.test(phone) || phone.indexOf('+0') === 0) { 5579 phone = ''; 5580 } else { 5581 let international = phone.indexOf('+') === 0 || phone.indexOf('00') === 0; 5582 let national = phone.replace(/^\+?0*/, ''); 5583 5584 if (international && national.indexOf('1') === 0 && national.length !== 11) { 5585 national = ''; 5586 } 5587 5588 phone = /^[1-9][0-9]{7,14}$/.test(national) ? national : ''; 5589 } 5590 5591 let normalizeName = function (value) { 5592 if (typeof value !== 'str
5592ing' || value.length > 1024) { 5593 return ''; 5594 } 5595 let stripped = value.replace(/[!-\/:-@\[-`{-~\s]+/g, '').toLowerCase(); 5596 return /\D/u.test(stripped) ? stripped : ''; 5597 }; 5598 5599 return { 5600 email: Utils.AdvancedMatching.normalizeEmail(raw.email || ''), 5601 phone: phone, 5602 first_name: normalizeName(raw.first_name || ''), 5603 last_name: normalizeName(raw.last_name || '') 5604 }; 5605 } 5606 5607 /** 5608 * The identity to hand the SDK, built from whatever is known right now. 5609 */ 5610 function buildAdvancedMatchingUser() { 5611 5612 let user = {}; 5613 5614 if (options.openai.hasOwnProperty('advanced_matching')) { 5615 5616 let matching = options.openai.advanced_matching; 5617 5618 OPENAI_USER_FIELDS.forEach(function (field) { 5619 let value = matching[field]; 5620 5621 if (typeof value === 'string' && value.length > 0) { 5622 user[field] = value; 5623 } 5624 }); 5625 } 5626 5627 Utils.AdvancedMatching.merge(user, { 5628 email_sha256: { field: 'email', hash: true }, 5629 phone_number_sha256: { field: 'phone', hash: true }, 5630 first_name_sha256: { field: 'first_name', hash: true }, 5631 last_name_sha256: { field: 'last_name', hash: true } 5632 }, openaiCookieIdentity()); 5633 5634 let externalIdAllowed = options.send_external_id 5635 && ! ( options.gdpr && options.gdpr.externalID_disabled_by_api ) 5636 && ! ( options.cookie 5637 && ( options.cookie.disabled_all_cookie 5638 || options.cookie.externalID_disabled_by_api ) ); 5639 5640 if (!user.hasOwnProperty('external_id_sha256') 5641 && externalIdAllowed 5642 && typeof sha256 === 'function' 5643 && Cookies.get('pbid')) { 5644 user.external_id_sha256 = sha256(Cookies.get('pbid')); 5645 } 5646 5647 return user; 5648 } 5649 5650 return { 5651 tag: function () { 5652 return "openai"; 5653 }, 5654 5655 fireEventAPI: function (name, event, data, eventId, ids) { 5656 5657 if (!this.isEnabled() || !options.openai.serverApiEnabled) { 5658 return; 5659 } 5660 5661 let notCachedEventsIds = [], 5662 isAddToCartFromJs = options.hasOwnProperty('woo') 5663 && options.woo.hasOwnProperty('addToCartCatchMethod') 5664 && options.woo.addToCartCatchMethod === 'add_cart_js'; 5665 5666 if (!isAddToCartFromJs) { 5667 notCachedEventsIds.push('woo_add_to_cart_on_button_click'); 5668 } 5669 5670 if (notCachedEventsIds.indexOf(event.e_id) !== -1) { 5671 return; 5672 } 5673 5674 let isApiDisabled = options.gdpr.all_disabled_by_api || 5675 options.gdpr.openai_disabled_by_api || 5676 options.gdpr.cookiebot_integration_enabled || 5677 options.gdpr.cookie_notice_integration_enabled || 5678 options.gdpr.consent_magic_integration_enabled || 5679 options.gdpr.cookie_law_info_integration_enabled; 5680 5681 let forcedToBrowser = ( event.hasOwnProperty( 'ajaxFire' ) && event.ajaxFire ) 5682 || isApiDisabled 5683 || event.delay > 0; 5684 5685 if (event.type === "static" && !forcedToBrowser 5686 && !(options.ajaxForServerEvent && options.ajaxForServerStaticEvent)) { 5687 return; 5688 } 5689 5690 if (!(options.ajaxForServerEvent || forcedToBrowser || event.type !== "static")) { 5691 return; 5692 } 5693 5694 let params = {data: data}; 5695 5696 if (event.params && event.params.custom_event_name) { 5697 params.custom_event_name = event.params.custom_event_name; 5698 } 5699 5700 let json = { 5701 action: 'pys_openai_api_event', 5702 pixel: OpenAI.tag(), 5703 event: name, 5704 event_slug: event.e_id, 5705 ids: ids, 5706 data: params, 5707 url: window.location.href, 5708 event_id: eventId, 5709 ajax_event: options.ajax_event 5710 }; 5711 5712 if (event.hasOwnProperty('woo_order')) { 5713 json['woo_order'] = event.woo_order; 5714 } 5715 5716 if (event.hasOwnProperty('edd_order')) { 5717 json['edd_order'] = event.edd_order; 5718 } 5719 5720 if (event.hasOwnProperty('order_key') && event.order_key) { 5721 json['order_key'] = event.order_key; 5722 } 5723
5724 var delay = 0; 5725 5726 if (name === 'page_viewed') { 5727 delay = 500; 5728 } 5729 5730 setTimeout(function () { 5731 Utils.sendServerAjaxRequest(options.ajaxUrl, json); 5732 }, delay); 5733 }, 5734 5735 isEnabled: function () { 5736 return options.hasOwnProperty('openai'); 5737 }, 5738 getHidePixel: function () { 5739 if (this.isEnabled() && options.openai.hasOwnProperty('hide_pixels')) { 5740 return options.openai.hide_pixels; 5741 } 5742 return []; 5743 }, 5744 disable: function () { 5745 initialized = false; 5746 5747 if (typeof window.oaiq === 'function') { 5748 oaiq('consent', false); 5749 } 5750 }, 5751 5752 loadPixel: function () { 5753 5754 if (initialized || !this.isEnabled() || !Utils.consentGiven('openai')) { 5755 return; 5756 } 5757 5758 let ids = visiblePixelIds(options.openai.pixelIds); 5759 5760 if (ids.length === 0) { 5761 return; 5762 } 5763 5764 if (identityPending()) { 5765 5766 if (identityWaitTimer === null) { 5767 identityWaitTimer = setTimeout(function () { 5768 identityWaitExpired = true; 5769 5770 if (window.pys && window.pys.OpenAI) { 5771 window.pys.OpenAI.loadPixel(); 5772 } 5773 }, 3000); 5774 } 5775 5776 return; 5777 } 5778 5779 if (identityWaitTimer !== null) { 5780 clearTimeout(identityWaitTimer); 5781 identityWaitTimer = null; 5782 } 5783 5784 !function (w, d, s, u) { 5785 if (w.oaiq) return; 5786 var q = function () { 5787 q.q.push(arguments); 5788 }; 5789 q.q = []; 5790 w.oaiq = q; 5791 var js = d.createElement(s); 5792 js.async = true; 5793 js.src = u; 5794 var f = d.getElementsByTagName(s)[0]; 5795 f.parentNode.insertBefore(js, f); 5796 }(window, document, 'script', 'https://bzrcdn.openai.com/sdk/oaiq.min.js'); 5797 5798 let user = buildAdvancedMatchingUser(); 5799 5800 if (Object.keys(user).length > 0) { 5801 sentIdentity = JSON.stringify(user); 5802 } 5803 5804 ids.forEach(function (pixelId) { 5805 let initOptions = { 5806 pixelId: pixelId 5807 }; 5808 5809 if (options.openai.debug) { 5810 initOptions.debug = true; 5811 } 5812 5813 if (Object.keys(user).length > 0) { 5814 initOptions.user = user; 5815 } 5816 5817 oaiq('init', initOptions); 5818 }); 5819 5820 oaiq('consent', true); 5821 5822 initialized = true; 5823 5824 if (options.staticEvents.hasOwnProperty('openai')) { 5825 Utils.fireStaticEvents('openai'); 5826 } 5827 }, 5828 5829 refreshAdvancedMatching: function () { 5830 5831 if (!initialized || !this.isEnabled() || typeof window.oaiq !== 'function') { 5832 return; 5833 } 5834 5835 let user = buildAdvancedMatchingUser(); 5836 5837 if (Object.keys(user).length === 0) { 5838 return; 5839 } 5840 5841 let identity = JSON.stringify(user); 5842 5843 if (identity === sentIdentity) { 5844 return; 5845 } 5846 5847 sentIdentity = identity; 5848 5849 (options.openai.pixelIds || []).forEach(function (pixelId) { 5850 oaiq('init', {pixelId: pixelId, user: user}); 5851 }); 5852 }, 5853 5854 fireEvent: function (name, data) { 5855 if (!initialized || !this.isEnabled()) { 5856 return false; 5857 } 5858 data.delay = data.delay || 0; 5859 5860 if (data.delay === 0) { 5861 fireEvent(name, data); 5862 } else { 5863 setTimeout(function (name, params) { 5864 fireEvent(name, params); 5865 }, data.delay * 1000, name, data); 5866 } 5867 return true; 5868 }, 5869 5870 onClickEvent: function (event) { 5871 this.fireEvent(event.name, event); 5872 }, 5873 5874 onWooAddToCartOnButtonEvent: function (product_id) { 5875 if (!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) { 5876 return; 5877 } 5878 if (!window.pysWooProductData || !window.pysWooProductData.hasOwnProperty(product_id)) { 5879 return; 5880 } 5881 if (!window.pysWooProductData[product_id].hasOwnProperty(this.tag())) { 5882 return; 5883 } 5884 5885 var productData = window.pysWooProductData[product_id][this.tag()]; 5886 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 5887 5888 Utils.copyProperties(productData['params'], event.params); 5889 5890 if (productData.hasOwnProperty('pixelIds')) { 5891 event.pixelIds = productData['pixelIds']; 5892 } 5893 5894 this.fireEvent(event.name, event); 5895 }, 5896 5897 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form) { 5898 if (!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) { 5899 return; 5900 } 5901 if (!window.pysWooProductData || !window.pysWooProductData.hasOwnProperty(product_id)) { 5902 return; 5903 } 5904 if (!window.pysWooProductData[product_id].hasOwnProperty(this.tag())) { 5905 return; 5906 } 5907 5908 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 5909 5910 Utils.copyProperties(window.pysWooProductData[product_id][this.tag()]['params'], event.params); 5911 5912 // The server priced one unit; scale the quantity and the money that 5913 // depends on it. Amounts are already integer minor units, so the 5914 // multiplication stays integral. 5915 if (event.params && event.params.data) { 5916 var data = event.params.data; 5917 5918 if (Array.isArray(data.contents)) {
5919 data.contents.forEach(function (item) { 5920 item.quantity = qty; 5921 }); 5922 } 5923 } 5924 5925 this.fireEvent(event.name, event); 5926 }, 5927 5928 onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) { 5929 if (!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag())) { 5930 return; 5931 } 5932 if (!window.pysEddProductData || !window.pysEddProductData.hasOwnProperty(download_id)) { 5933 return; 5934 } 5935 5936 var index = price_index ? download_id + '_' + price_index : download_id; 5937 5938 if (!window.pysEddProductData[download_id].hasOwnProperty(index)) { 5939 return; 5940 } 5941 if (!window.pysEddProductData[download_id][index].hasOwnProperty(this.tag())) { 5942 return; 5943 } 5944 5945 var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]); 5946 5947 Utils.copyProperties(window.pysEddProductData[download_id][index][this.tag()].params, event.params); 5948 5949 if (qty && event.params && event.params.data && Array.isArray(event.params.data.contents)) { 5950 event.params.data.contents.forEach(function (item) { 5951 item.quantity = qty; 5952 }); 5953 } 5954 5955 this.fireEvent(event.name, event); 5956 }, 5957 } 5958 }(options); 5959 5960 var Facebook = function (options) { 5961 5962 var defaultEventTypes = [ 5963 'PageView', 5964 'ViewContent', 5965 'Search', 5966 'AddToCart', 5967 'AddToWishlist', 5968 'InitiateCheckout', 5969 'AddPaymentInfo', 5970 'Purchase', 5971 'Lead', 5972 5973 'Subscribe', 5974 'CustomizeProduct', 5975 'FindLocation', 5976 'StartTrial', 5977 'SubmitApplication', 5978 'Schedule', 5979 'Contact', 5980 'Donate' 5981 ]; 5982 5983 var notCachedEventsIds = new Array(); 5984 var isAddToCartFromJs = options.woo.hasOwnProperty("addToCartCatchMethod") 5985 && options.woo.addToCartCatchMethod === "add_cart_js"; 5986 if(!isAddToCartFromJs) { 5987 notCachedEventsIds.push('woo_add_to_cart_on_button_click') 5988 } 5989 5990 var initialized = false; 5991 5992 /** 5993 * The `version.subdomainIndex.` prefix Meta's format calls for. 5994 * subdomainIndex is how many labels the cookie's domain has below 5995 * the public suffix (com=0, example.com=1, www.example.com=2), so it 5996 * must track cookieDomain()/probeCookieDomain() rather than a 5997 * hardcoded `fb.1.`. Only new values are affected â an existing 5998 * cookie is returned untouched. 5999 */ 6000 var fbValuePrefix = function (){ 6001 var resolved = cookieDomain(); 6002 var scope = resolved ? String(resolved).replace(/^\./, '') : ''; 6003 if (!scope) { 6004 try { scope = String(location.hostname || ''); } catch (e) { scope = ''; } 6005 } 6006 var dots = scope ? scope.split('.').length - 1 : 0; 6007 return 'fb.' + dots + '.' + Date.now() + '.'; 6008 }; 6009 var genereateFbp = function (){ 6010 return !Cookies.get('_fbp') ? fbValuePrefix()+Math.floor(1000000000 + Math.random() * 9000000000) : Cookies.get('_fbp'); 6011 }; 6012 var genereateFbc = function (){ 6013 return getUrlParameter('fbclid') ? fbValuePrefix()+getUrlParameter('fbclid') : '' 6014 }; 6015 6016 var configuredPixels = new Array(); 6017 function fireEvent(name, event) { 6018 6019 6020 if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name,'facebook')) { 6021 return; 6022 } 6023 6024 var data = event.params; 6025 var ids = event.pixelIds.filter(function (pixelId) { 6026 return !Utils.hideMatchingPixel(pixelId, 'facebook'); 6027 }); 6028 6029 var actionType = defaultEventTypes.includes(name) ? 'trackSingle' : 'trackSingleCustom'; 6030 6031 var params = {}; 6032 6033 Utils.copyProperties(data, params);
6034 Utils.copyProperties(Utils.getRequestParams(), params); 6035 Utils.copyProperties(Utils.getRequestParams(), data); 6036 6037 params = Facebook.filterParams(params); 6038 6039 6040 // sendEventId is true when the token-based Conversion API is on OR 6041 // when Meta-Enabled CAPI is on for at least one of the pixels on the 6042 // page: in both cases Meta receives a second, API-side copy of this 6043 // event and dedupes it by eventID. Only the token-based API needs an 6044 // actual request from us, so the hook/AJAX branches below stay gated 6045 // on serverApiEnabled. 6046 if(options.facebook.sendEventId) { 6047 if(options.facebook.serverApiEnabled && event.e_id === "woo_remove_from_cart" ) { 6048 Facebook.updateEventId(event.name); 6049 event.eventID = Facebook.getEventId(event.name); 6050 } else if(!notCachedEventsIds.includes(event.e_id)) { 6051 var isApiDisabled = options.gdpr.all_disabled_by_api || 6052 options.gdpr.facebook_disabled_by_api || 6053 options.gdpr.cookiebot_integration_enabled || 6054 options.gdpr.cookie_notice_integration_enabled || 6055 options.gdpr.consent_magic_integration_enabled || 6056 options.gdpr.cookie_law_info_integration_enabled; 6057 // Update eventID 6058 6059 event.eventID = Utils.generateUniqueId(event); 6060 6061 if(Cookies.get('_fbp')){ 6062 params._fbp = Cookies.get('_fbp'); 6063 } 6064 if(Cookies.get('_fbc')){ 6065 params._fbc = Cookies.get('_fbc'); 6066 } 6067 // send event from server if they was bloc by gdpr or need send with delay 6068 if( options.facebook.serverApiEnabled && ( options.ajaxForServerEvent || isApiDisabled ) ){ 6069 var json = { 6070 action: 'pys_api_event', 6071 pixel: 'facebook', 6072 event: name, 6073 event_slug: event.e_id, 6074 ids: ids, 6075 data:params, 6076 url:window.location.href, 6077 eventID:event.eventID, 6078 ajax_event:options.ajax_event 6079 }; 6080 6081 if(event.hasOwnProperty('woo_order')) { 6082 json['woo_order'] = event.woo_order; 6083 } 6084 6085 if(event.hasOwnProperty('edd_order')) { 6086 json['edd_order'] = event.edd_order; 6087 } 6088 6089 // Forward the order key so the server can bind the 6090 // purchase event to its specific order (RestPurchaseGuard). 6091 if(event.hasOwnProperty('order_key') && event.order_key) { 6092 json['order_key'] = event.order_key; 6093 } 6094 6095 if (pageReferrer) { 6096 json.referrer_url = pageReferrer; 6097 } 6098 6099 let delay = 0; 6100 try { 6101 if (sessionStorage) { 6102 Object.keys(sessionStorage) 6103 .filter(key => key.startsWith('wc_fragments_')) 6104 .forEach((key) => { 6105 let value = sessionStorage.getItem(key); 6106 if (event.e_id === 'woo_add_to_cart_on_button_click') { 6107 delay = 2000; 6108 } 6109 }); 6110 } 6111 } catch (error) { 6112 console.error('sessionStorage is not accessible:', error); 6113 } 6114 6115 if (event.e_id === "automatic_event_internal_link" || event.e_id === "automatic_event_outbound_link") {
6116 delay = delay === 0 ? 500 : delay; 6117 setTimeout(() => Utils.sendServerAjaxRequest(options.ajaxUrl, json), delay); 6118 } else if (event.type != 'static') { 6119 setTimeout(() => Utils.sendServerAjaxRequest(options.ajaxUrl, json), delay); 6120 } 6121 6122 if ( ( event.type == 'static' && options.ajaxForServerStaticEvent ) || ( event.hasOwnProperty( 'ajaxFire' ) && event.ajaxFire ) ) { 6123 setTimeout( () => Utils.sendServerAjaxRequest( options.ajaxUrl, json ), delay ); 6124 } 6125 } 6126 6127 if( options.facebook.serverApiEnabled && event.e_id !== "automatic_event_signup" && name == "CompleteRegistration" && options.facebook.wooCRSendFromServer ) { 6128 return; 6129 } 6130 } 6131 6132 } 6133 delete params._fbp; 6134 delete params._fbc; 6135 if (options.debug) { 6136 console.log('[Facebook] ' + name, params,"pixel_ids",ids,"eventID",event.eventID); 6137 } 6138 // fire event for each pixel id 6139 ids.forEach(function (pixelId) { 6140 // add eventID for deduplicate events @see https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events/ 6141 var args = {}; 6142 if(options.facebook.sendEventId && event.hasOwnProperty('eventID')) { 6143 args.eventID = event.eventID; 6144 } 6145 Facebook.maybeInitPixel(pixelId); 6146 fbq(actionType,pixelId, name, params,args); 6147 }); 6148 var customEvent = new CustomEvent('facebook_event_sent', { 6149 detail: { 6150 eventName: name, 6151 params: params, 6152 pixel_ids: ids 6153 } 6154 }); 6155 window.dispatchEvent(customEvent); 6156 } 6157 6158 /** 6159 * Public API 6160 */ 6161 return { 6162 tag: function() { 6163 return "facebook"; 6164 }, 6165 isEnabled: function () { 6166 return options.hasOwnProperty('facebook'); 6167 }, 6168 getHidePixel: function(){ 6169 if(this.isEnabled() && options.facebook.hasOwnProperty('hide_pixels')) 6170 { 6171 return options.facebook.hide_pixels; 6172 } 6173 return []; 6174 }, 6175 initEventIdCookies: function (key) { 6176 var ids = {}; 6177 ids[key] = pys_generate_token(36) 6178 Cookies.set('pys_fb_event_id', JSON.stringify(ids), {path: '/',domain: cookieDomain()}); 6179 }, 6180 6181 updateEventId:function(key) { 6182 var cooData = Cookies.get("pys_fb_event_id") 6183 if(cooData === undefined) { 6184 this.initEventIdCookies(key); 6185 } else { 6186 var data = JSON.parse(cooData); 6187 data[key] = pys_generate_token(36); 6188 Cookies.set('pys_fb_event_id', JSON.stringify(data), {path: '/',domain: cookieDomain()} ); 6189 } 6190 }, 6191 6192 getEventId:function (key) { 6193 var data = Cookies.get("pys_fb_event_id"); 6194 if(data === undefined) { 6195 this.initEventIdCookies(key); 6196 data = Cookies.get("pys_fb_event_id"); 6197 } 6198 return JSON.parse(data)[key]; 6199 }, 6200 6201 disable: function () { 6202 initialized = false; 6203 }, 6204 6205 filterParams: function (params) { 6206 var filteredParams = {}; 6207 6208 if (options.facebook.hasOwnProperty('enabled_medical') && options.facebook.enabled_medical && options.facebook.hasOwnProperty('do_not_track_medical_param')) { 6209 // Remove empty strings from the array 6210 options.facebook.do_not_track_medical_param = options.facebook.do_not_track_medical_param.filter(Boolean); 6211 for (const key in params) { 6212 if (Array.isArray(options.facebook.do_not_track_medical_param)) { 6213 if (!options.facebook.do_not_track_medical_param.includes(key)) { 6214 filteredParams[key] = params[key]; 6215 } 6216 } 6217 } 6218 } 6219 else { 6220 Utils.copyProperties(params, filteredParams); 6221 } 6222 return filteredParams; 6223 }, 6224 6225 /** 6226 * Load pixel's JS 6227 */ 6228 loadPixel: function () { 6229 if (initialized || !this.isEnabled() || !Utils.consentGiven('facebook')) { 6230 return; 6231 } 6232 6233 // First visit: wait (briefly) for this browser's external_id, so 6234 // the very first PageView is not sent under the inline value. 6235 if (Utils.holdForIdentity(function () { Facebook.loadPixel(); })) { 6236 return; 6237 } 6238 6239 ! function (f, b, e, v, n, t, s) { 6240 if (f.fbq) return; 6241 n = f.fbq = function () { 6242 n.callMethod ? 6243 n.callMethod.apply(n, arguments) : n.queue.push(arguments) 6244 }; 6245 if (!f._fbq) f._fbq = n; 6246 n.push = n; 6247 n.loaded = !0; 6248 n.version = '2.0'; 6249 n.agent = 'dvpixelyoursite'; 6250 n.queue = []; 6251 t = b.createElement(e); 6252 t.async = !0; 6253 t.src = v; 6254 s = b.getElementsByTagName(e)[0]; 6255 s.parentNode.insertBefore(t, s) 6256 }(window, 6257 document, 'script', 'https://connect.facebook.net/en_US/fbevents.js'); 6258 // Report the cookie domain here too, not just from 6259 // manageCookies(): this runs under the `facebook` pixel's own 6260 // consent, which can differ from and outlast the `pys` 6261 // script's consent (e.g. under ConsentMagic's separate 6262 // category assignments), so PHP might otherwise never learn 6263 // the scope for a visitor who only accepted this pixel. 6264 // 6265 // `pys_cd` only ever appears here alongside a cookie we are 6266 // already writing at that scope, so it can never be the first 6267 // cookie a visitor receives. disabled_all_cookie is still 6268 // honoured, matching the call in manageCookies(). 6269 if (!options.cookie || !options.cookie.disabled_all_cookie) { 6270 reportCookieDomain(cookieDomain()); 6271 } 6272 6273 let expires = parseInt(options.cookie_duration); 6274 if(!Cookies.get('_fbp')) { 6275 Cookies.set('_fbp',genereateFbp(), { expires: expires,path: '/',domain: cookieDomain() }); 6276 } 6277 6278 if(getUrlParameter('fbclid')) { 6279 Cookies.set('_fbc',genereateFbc(), { expires: expires,path: '/',domain: cookieDomain() }); 6280 } 6281 6282 // Now that Facebook consent is confirmed, mirror the session 6283 // entry referrer (if any) into the session cookie so PHP can 6284 // use it as a fallback for orphan server-only events. 6285 getOrInitSessionEntryReferrer(true); 6286 6287 // Refresh the per-page event referrer cookie so checkout 6288 // hooks can snapshot the buyer's prior page. 6289 writeEventReferrerCookie(); 6290 6291 var ids = options.facebook.pixelIds.filter(function (pixelId) { 6292 return !Utils.hideMatchingPixel(pixelId, 'facebook'); 6293 }); 6294 // initialize default pixel 6295 ids.forEach(function (pixelId) { 6296 Facebook.maybeInitPixel(pixelId); 6297 }); 6298 initialized = true; 6299 6300 Utils.fireStaticEvents('facebook'); 6301 6302 }, 6303 advancedMatching: function () { 6304 if(options.facebook.advancedMatchingEnabled) { 6305 let advancedMatching = {}; 6306 if(Object.keys(options.facebook.advancedMatching).length > 0) { 6307 advancedMatching = options.facebook.advancedMatching; 6308 } 6309 6310 Utils.AdvancedMatching.merge(advancedMatching, { 6311 em: { field: 'email', hash: false }, 6312 ph: { field: 'phone', hash: false }, 6313 fn: { field: 'first_name', hash: false }, 6314 ln: { field: 'last_name', hash: false } 6315 }); 6316 6317 if(!advancedMatching.hasOwnProperty("external_id")){ 6318 if (Cookies.get('pbid')) { 6319 advancedMatching["external_id"] = Cookies.get('pbid'); 6320 } 6321 } 6322 else if(advancedMatching.hasOwnProperty("external_id") && advancedMatching.external_id != Cookies.get('pbid')) 6323 { 6324 advancedMatching["external_id"] = Cookies.get('pbid') ? Cookies.get('pbid') : advancedMatching.external_id; 6325 } 6326 6327 if(Object.keys(advancedMatching).length > 0) { 6328 return advancedMatching; 6329 } 6330 } 6331 return false 6332 }, 6333 maybeInitPixel: function(pixelId) { 6334 6335 if(configuredPixels.includes(pixelId)) return; 6336 6337 if (options.facebook.removeMetadata || Utils.hideMatchingPixel(pixelId, this.tag())) { 6338 fbq('set', 'autoConfig', false, pixelId); 6339 } 6340 let advancedMatching = Facebook.advancedMatching(); 6341 6342 if ( +options.facebook.meta_ldu === 1 ) { 6343 fbq( 'dataProcessingOptions', [ 'LDU' ], 0, 0 ); 6344 } 6345 6346 // Meta-Enabled Conversion API: let Meta build its own API events 6347 // out of this pixel's data. Must run right before init, and the 6348 // opt-in is per pixel id. 6349 if (Utils.isMetaEnabledCapiPixel(pixelId)) { 6350 fbq('optinMetaEnabledCapi', pixelId); 6351 } 6352 6353 if (options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined") { 6354 if(!advancedMatching) { 6355 fbq('init', pixelId); 6356 } else { 6357 var test_prefix = CS_Data.test_prefix; 6358 var cs_advanced_matching = Cookies.get('cs_enabled_advanced_matching'+test_prefix); 6359 if (jQuery('#cs_enabled_advanced_matching'+test_prefix).length > 0) { 6360 if (cs_advanced_matching == 'yes') { 6361 fbq('init', pixelId, advancedMatching); 6362 } else { 6363 fbq('init', pixelId); 6364 } 6365 } else { 6366 fbq('init', pixelId, advancedMatching); 6367 } 6368 } 6369 } else { 6370 if(!advancedMatching) { 6371 fbq('init', pixelId); 6372 } else { 6373 fbq('init', pixelId, advancedMatching); 6374 } 6375 } 6376 configuredPixels.push(pixelId); 6377 }, 6378 6379 fireEvent: function (name, data) { 6380 6381 if (!initialized || !this.isEnabled()) { 6382 return false; 6383 } 6384 6385 data.delay = data.delay || 0; 6386 data.params = data.params || {}; 6387 6388 if (data.delay === 0) { 6389 6390 fireEvent(name, data); 6391 6392 } else { 6393 6394 setTimeout(function (name, params) { 6395 fireEvent(name, params); 6396 }, data.delay * 1000, name, data); 6397 6398 } 6399 6400 return true; 6401 6402 }, 6403 6404 onAdSenseEvent: function (event) { 6405 this.fireEvent(event.name, event); 6406 }, 6407 6408 onClickEvent: function (event) { 6409 this.fireEvent(event.name, event); 6410 }, 6411 6412 onWatchVideo: function (event) { 6413 this.fireEvent(event.name, event); 6414 }, 6415 6416 onCommentEvent: function (event) { 6417 this.fireEvent(event.name, event); 6418 }, 6419 6420 onFormEvent: function (event) { 6421 this.fireEvent(event.name, event); 6422 }, 6423 6424 onDownloadEvent: function (event) { 6425 this.fireEvent(event.name, event); 6426 6427 }, 6428 6429 6430 onWooAddToCartOnButtonEvent: function (product_id) { 6431 6432 window.pysWooProductData = window.pysWooProductData || []; 6433 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 6434 return; 6435 6436 if (window.pysWooProductData.hasOwnProperty(product_id)) { 6437 if (window.pysWooProductData[product_id].hasOwnProperty('facebook')) { 6438 6439 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]) 6440 6441 Utils.copyProperties(window.pysWooProductData[product_id]['facebook']['params'], event.params) 6442 event.pixelIds = window.pysWooProductData[product_id]['facebook']['pixelIds']; 6443 this.fireEvent(event.name, event); 6444 } 6445 } 6446 }, 6447 6448 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form) { 6449 6450 window.pysWooProductData = window.pysWooProductData || []; 6451 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 6452 return; 6453 6454 if (product_type === Utils.PRODUCT_VARIABLE && !options.facebook.wooVariableAsSimple) { 6455 product_id = parseInt($form.find('input[name="variation_id"]').val()); 6456 } 6457 6458 if (window.pysWooProductData.hasOwnProperty(product_id)) { 6459 if (window.pysWooProductData[product_id].hasOwnProperty('facebook')) { 6460 6461 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click.facebook); 6462 6463 6464 Utils.copyProperties(window.pysWooProductData[product_id]['facebook']['params'], event.params); 6465 6466 var groupValue = 0; 6467 if(product_type === Utils.PRODUCT_GROUPED ) { 6468 $form.find(".woocommerce-grouped-product-list .qty").each(function(index){ 6469 var childId = $(this).attr('name').replaceAll("quantity[","").replaceAll("]",""); 6470 var quantity = parseInt($(this).val()); 6471 if(isNaN(quantity)) { 6472 quantity = 0; 6473 } 6474 var childItem = window.pysWooProductData[product_id]['facebook'].grouped[childId]; 6475 6476 if(quantity == 0) { 6477 event.params.content_ids.forEach(function(el,index,array) { 6478 if(el == childItem.content_id) { 6479 array.splice(index, 1); 6480 } 6481 }); 6482 } 6483 6484 if(event.params
6484.hasOwnProperty('contents')) { 6485 event.params.contents.forEach(function(el,index,array) { 6486 if(el.id == childItem.content_id) { 6487 if(quantity > 0){ 6488 el.quantity = quantity; 6489 } else { 6490 array.splice(index, 1); 6491 } 6492 } 6493 }); 6494 } 6495 6496 6497 groupValue += childItem.price * quantity; 6498 }); 6499 if(groupValue == 0) return; // skip if no items selected 6500 } 6501 6502 6503 6504 // maybe customize value option 6505 if (options.woo.addToCartOnButtonValueEnabled && options.woo.addToCartOnButtonValueOption !== 'global') { 6506 6507 if(product_type === Utils.PRODUCT_GROUPED) { 6508 event.params.value = groupValue; 6509 } else if(product_type === Utils.PRODUCT_BUNDLE) { 6510 var data = $(".bundle_form .bundle_data").data("bundle_form_data"); 6511 var items_sum = getBundlePriceOnSingleProduct(data); 6512 event.params.value = (parseFloat(data.base_price) + items_sum )* qty; 6513 } else { 6514 event.params.value = event.params.value * qty; 6515 } 6516 } 6517 6518 // only when non Facebook for WooCommerce logic used 6519 if (event.params.hasOwnProperty('contents') && product_type !== Utils.PRODUCT_GROUPED) { 6520 event.params.contents[0].quantity = qty; 6521 6522 } 6523 6524 var event_name = is_external ? options.woo.affiliateEventName : event.name; 6525 6526 this.fireEvent(event_name, event); 6527 6528 } 6529 } 6530 6531 }, 6532 6533 onWooRemoveFromCartEvent: function (event) { 6534 this.fireEvent(event.name, event); 6535 }, 6536 6537 onWooAffiliateEvent: function (product_id) { 6538 if(!options.dynamicEvents.woo_affiliate.hasOwnProperty(this.tag())) 6539 return; 6540 var event = Utils.clone(options.dynamicEvents.woo_affiliate[this.tag()]); 6541 6542 6543 if (window.pysWooProductData.hasOwnProperty(product_id)) { 6544 if (window.pysWooProductData[product_id].hasOwnProperty('facebook')) { 6545 6546 Utils.copyProperties(window.pysWooProductData[product_id][this.tag()].params, event.params) 6547 this.fireEvent(options.woo.affiliateEventName, event); 6548 6549 } 6550 } 6551 6552 }, 6553 6554 onWooPayPalEvent: function (event) { 6555 this.fireEvent(event.name, event); 6556 }, 6557 6558 onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) { 6559 if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 6560 return; 6561 var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]); 6562 6563 if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) { 6564 6565 var index; 6566 6567 if (price_index) { 6568 index = download_id + '_' + price_index; 6569 } else { 6570 index = download_id; 6571 } 6572 6573 if (window.pysEddProductData[download_id].hasOwnProperty(index)) { 6574 if (window.pysEddProductData[download_id][index].hasOwnProperty('facebook')) { 6575 6576 Utils.copyProperties(window.pysEddProductData[download_id][index]['facebook']["params"], event.params) 6577 6578 // maybe customize value option 6579 if (options.edd.addToCartOnButtonValueEnabled && options.edd.addToCartOnButtonValueOption !== 'global') { 6580 event.params.value = event.params.value * qty; 6581 } 6582 6583 // update contents qty param 6584 var contents = event.params.contents; 6585 contents[0].quantity = qty; 6586 event.params.contents = contents; 6587 6588 this.fireEvent(event.name,event); 6589 6590 } 6591 } 6592 6593 } 6594 6595 }, 6596 6597 onEddRemoveFromCartEvent: function (event) { 6598 this.fireEvent(event.name, event); 6599 }, 6600 onPageScroll: function (event) { 6601 this.fireEvent(event.name, event); 6602 }, 6603 onTime: function (event) { 6604 this.fireEvent(event.name, event); 6605 }, 6606 }; 6607 6608 }(options); 6609 6610 var Analytics = function (options) { 6611 6612 var initialized = false; 6613 var isAllowEnhancedConversions = false; 6614 6615 /** 6616 * Fires event 6617 * 6618 * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/sending-data 6619 * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/events 6620 * @link: https://developers.google.com/gtagjs/reference/event 6621 * @link: https://developers.google.com/gtagjs/reference/parameter 6622 * 6623 * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/custom-dim
6623s-mets 6624 * 6625 * @param name 6626 * @param data 6627 */ 6628 function fireEvent(name, event) { 6629 if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name,'ga')) { 6630 return; 6631 } 6632 6633 var eventParams = event.params; 6634 var valuesArray = Object.values(event.trackingIds); 6635 var ids = valuesArray.filter(function (pixelId) { 6636 return loadTags.some(loadTag => pixelId.startsWith(loadTag)) && !Utils.hideMatchingPixel(pixelId, 'ga') && !Utils.hideMatchingPixel(pixelId, 'google_ads'); 6637 }) 6638 6639 // Generate unique event_id for deduplication 6640 eventParams.event_id = Utils.generateUniqueId(event); 6641 event.eventID = Utils.generateUniqueId(event); 6642 6643 Utils.copyProperties(Utils.getRequestParams(), eventParams); 6644 6645 var copyParams = Utils.copyProperties(eventParams, {}); // copy params because mapParamsTov4 can modify
6645it 6646 var params = mapParamsTov4(ids,name,copyParams) 6647 6648 var _fireEvent = function (tracking_ids,name,params) { 6649 6650 // Firing without a destination would park the hit inside gtag.js 6651 // instead of sending it. Skip, but keep the reason visible: an 6652 // empty list means the event's trackingIds and the tags actually 6653 // configured on this page went out of sync. 6654 if (!Utils.hasSendToTarget(tracking_ids)) { 6655 if (options.debug) { 6656 console.warn('[Google Analytics] ' + name + ' skipped: no tracking ID left after filtering', { 6657 eventTrackingIds: Object.values(event.trackingIds), 6658 loadedTags: loadTags 6659 }); 6660 } 6661 return; 6662 } 6663 6664 params['send_to'] = tracking_ids; 6665 6666 if (options.debug) { 6667 console.log('[Google Analytics #' + tracking_ids + '] ' + name, params); 6668 } 6669 6670 // Send via gtag 6671 Utils.gtagPush('event', name, params); 6672 6673 var customEvent = new CustomEvent('gtag_event_sent', { 6674 detail: { 6675 eventName: name, 6676 params: params, 6677 pixel_ids: tracking_ids 6678 } 6679 }); 6680 window.dispatchEvent(customEvent); 6681 }; 6682 6683 6684 delete params.analytics_storage; 6685 delete params.ad_storage; 6686 delete params.ad_user_data; 6687 delete params.ad_personalization; 6688 6689 if(options.hasOwnProperty('google_ads')){ 6690 var conversion_event_name = event.e_id; 6691 switch ( conversion_event_name ) { 6692 case "woo_add_to_cart_on_cart_page": 6693 case "woo_add_to_cart_on_checkout_page": 6694 case "woo_add_to_cart_on_button_click": 6695 conversion_event_name = 'woo_add_to_cart'; 6696 break; 6697 case "edd_add_to_cart_on_cart_page": 6698 case "edd_add_to_cart_on_checkout_page": 6699 case "edd_add_to_cart_on_button_click": 6700 conversion_event_name = 'edd_add_to_cart'; 6701 break; 6702 case "automatic_event_adsense": 6703 case "automatic_event_comment": 6704 case "automatic_event_download": 6705 case "automatic_event_email_link": 6706 case "automatic_event_form": 6707 case "automatic_event_internal_link": 6708 case "automatic_event_outbound_link": 6709 case "automatic_event_scroll": 6710 case "automatic_event_tel_link": 6711 case "automatic_event_time_on_page": 6712 case "automatic_event_video": 6713 conversion_event_name = 'automatic_event'; 6714 break; 6715 } 6716 6717 if (conversion_event_name !== 'automatic_event') { 6718 // We replace the event name for the settings key if it is woo_view_item_list 6719 const trackEventName = conversion_event_name === 'woo_view_item_list' 6720 ? 'woo_view_category' 6721 : conversion_event_name; 6722 6723 if (typeof options.google_ads[trackEventName + '_conversion_track'] !== 'undefined') { 6724 const conversionTrack = options.google_ads[trackEventName + '_conversion_track']; 6725 6726 if (conversionTrack === 'conversion') { 6727 ids = ids.map(id => id.split('/')[0]); 6728 } 6729 } 6730 } 6731 } 6732 6733 6734 _fireEvent(ids, name, params); 6735 if(event.hasOwnProperty('unify') && event.unify){ 6736 isTrackEventForGA.push(name); 6737 } 6738 } 6739 6740 function normalizeEventName(eventName) { 6741 6742 var matches = { 6743 ViewContent: 'view_item', 6744 AddToCart: 'add_to_cart', 6745 AddToWishList: 'add_to_wishlist',
6746 InitiateCheckout: 'begin_checkout', 6747 Purchase: 'purchase', 6748 Lead: 'generate_lead', 6749 CompleteRegistration: 'sign_up', 6750 AddPaymentInfo: 'set_checkout_option' 6751 }; 6752 6753 return matches.hasOwnProperty(eventName) ? matches[eventName] : eventName; 6754 6755 } 6756 6757 function mapParamsTov4(tag,name,param) { 6758 //GA4 automatically collects a number of parameters for all events 6759 var hasGA4Tag = false; 6760 6761 // end 6762 if (Array.isArray(tag)) { 6763 hasGA4Tag = tag.some(function (element) { 6764 return isv4(element); 6765 }); 6766 } else if(isv4(tag)) { 6767 // tag is a string and matches GA4 6768 hasGA4Tag = true; 6769 } 6770 if(hasGA4Tag) { 6771 delete param.event_category; 6772 delete param.event_label; 6773 delete param.ecomm_prodid; 6774 delete param.ecomm_pagetype; 6775 delete param.ecomm_totalvalue; 6776 if(name === 'search') { 6777 param['search'] = param.search_term; 6778 delete param.search_term; 6779 delete param.dynx_itemid; 6780 delete param.dynx_pagetype; 6781 delete param.dynx_totalvalue; 6782 } 6783 } 6784 return param; 6785 } 6786 6787 function isv4(tag) { 6788 return tag.indexOf('G') === 0; 6789 } 6790 6791 /** 6792 * Public API 6793 */ 6794 return { 6795 tag: function() { 6796 return "ga"; 6797 }, 6798 isEnabled: function () { 6799 return options.hasOwnProperty('ga'); 6800 }, 6801 getHidePixel: function(){ 6802 if(this.isEnabled() && options.ga.hasOwnProperty('hide_pixels')) 6803 { 6804 return options.ga.hide_pixels; 6805 } 6806 return []; 6807 }, 6808 disable: function () { 6809 initialized = false; 6810 }, 6811 updateEnhancedConversionData : function () { 6812 if (!initialized || !this.isEnabled()) { 6813 return; 6814 } 6815 if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnable) { 6816 var advanced = Utils.getAdvancedMergeFormData(); 6817 if (Object.keys(advanced).length > 0) { 6818 Utils.gtagPush('set', 'user_data', advanced); 6819 } 6820 } 6821 }, 6822 loadPixel: function () { 6823 if (initialized || !this.isEnabled() || !Utils.consentGiven('analytics')) { 6824 return; 6825 } 6826 6827 6828 for (var i = 0; i < options.ga.trackingIds.length; i++) { 6829 var trackingId = options.ga.trackingIds[i]; 6830 if (!Utils.hideMatchingPixel(trackingId, 'ga')) { 6831 Utils.loadGoogleTag(trackingId); 6832 break; 6833 } 6834 } 6835 6836 6837 if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnable){ 6838 var advanced = Utils.getAdvancedMergeFormData(); 6839 if(Object.keys(advanced).length > 0){ 6840 Utils.gtagPush('set', 'user_data', advanced); 6841 } 6842 } 6843 6844 var config = {}; 6845 6846 if(options.user_id && options.user_id != 0) { 6847 config.user_id = options.user_id; 6848 } 6849 config.url_passthrough = options.ga.url_passthrough; 6850 // Cross-Domain tracking 6851 if (options.ga.crossDomainEnabled) { 6852 config.linker = { 6853 accept_incoming: options.ga.crossDomainAcceptIncoming, 6854 domains: options.ga.crossDomainDomains 6855 }; 6856 } 6857 6858 var ids = options.ga.trackingIds.filter(function (pixelId) { 6859 return !Utils.hideMatchingPixel(pixelId, 'ga'); 6860 }); 6861 loadTags.push(...ids); 6862 ids.forEach(function (trackingId,index) { 6863 6864 var obj = options.ga.isDebugEnabled; 6865 var searchValue = "index_"+index; 6866 var config_for_tag = Object.assign({}, config); 6867 config_for_tag.debug_mode = false; 6868 config_for_tag.send_page_view = !options.ga.custom_page_view_event; 6869 for (var key in obj) { 6870 if (obj[key] === searchValue) { 6871 config_for_tag.debug_mode = true; 6872 break; 6873 } 6874 } 6875 if(!config_for_tag.debug_mode) 6876 { 6877 delete config_for_tag.debug_mode; 6878 } 6879 6880 if(isv4(trackingId)) { 6881 if(options.ga.disableAdvertisingPersonalization) { 6882 config_for_tag.allow_ad_personalization_signals = false 6883 } 6884 } 6885 if(options.ga.hasOwnProperty('additionalConfig')){ 6886 if(options.ga.additionalConfig.hasOwnProperty(trackingId) && options.ga.additionalConfig[trackingId]){ 6887 config_for_tag.first_party_collection = options.ga.additionalConfig[trackingId].first_party_collection; 6888 } 6889 6890 } 6891 if(options.ga.hasOwnProperty('serverContainerUrls')){ 6892 if(options.ga.serverContainerUrls.hasOwnProperty(trackingId) && options.ga.serverContainerUrls[trackingId].enable_server_container != false){ 6893 if(options.ga.serverContainerUrls[trackingId].server_container_url != ''){ 6894 config_for_tag.server_container_url = options.ga.serverContainerUrls[trackingId].server_container_url; 6895 } 6896 if(options.ga.serverContainerUrls[trackingId].transport_url != ''){ 6897 config_for_tag.transport_url = options.ga.serverContainerUrls[trackingId].transport_url; 6898 } 6899 } 6900 } 6901 if (options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined') { 6902 var cookiebot_consent_category = options.gdpr['cookiebot_analytics_consent_category']; 6903 if (options.gdpr['analytics_prior_consent_enabled']) { 6904 if (Cookiebot.consented === true && Cookiebot.consent[cookiebot_consent_category]) { 6905 Utils.gtagPush('config', trackingId, config_for_tag); 6906 } 6907 } else { 6908 if (Cookiebot.consent[cookiebot_consent_category]) { 6909 Utils.gtagPush('config', trackingId, config_for_tag); 6910 } 6911 } 6912 } 6913 else 6914 { 6915 Utils.gtagPush('config', trackingId, config_for_tag); 6916 } 6917 }); 6918 if(!isAdsLoad && GAds.isEnabled() && options.google_ads.conversion_ids.length > 0 && Utils.consentGiven('google_ads')) { 6919 for (var i = 0; i < options.google_ads.conversion_ids.length; i++) { 6920 var trackingId = options.google_ads.conversion_ids[i]; 6921 if (!Utils.hideMatchingPixel(trackingId, 'google_ads')) { 6922 Utils.loadGoogleTag(trackingId); 6923 break; 6924 } 6925 } 6926 var ids = options.google_ads.conversion_ids.filter(function (pixelId) { 6927 return !Utils.hideMatchingPixel(pixelId, 'google_ads'); 6928 }); 6929 loadTags.push(...ids); 6930 // configure conversion ids 6931 ids.forEach(function (conversion_id,index) { 6932 6933 Utils.gtagPush('config', conversion_id); 6934 6935 if(options.google_ads.enhanced_conversion.includes("index_"+index)) { 6936 isAllowEnhancedConversions = true 6937 Utils.gtagPush('config', conversion_id,{ 'allow_enhanced_conversions':true });
6938 } 6939 6940 Utils.registerGadsPhoneConversion( conversion_id ); 6941 6942 }); 6943 isAdsLoad = true; 6944 } 6945 6946 initialized = true; 6947 6948 Utils.fireStaticEvents('ga'); 6949 $( document).trigger( "analytics_initialized") 6950 }, 6951 6952 fireEvent: function (name, data) { 6953 6954 if (!initialized || !this.isEnabled()) { 6955 return false; 6956 } 6957 6958 data.delay = data.delay || 0; 6959 data.params = data.params || {}; 6960 6961 if (data.delay === 0) { 6962 6963 fireEvent(name, data); 6964 6965 } else { 6966 6967 setTimeout(function (name, params) { 6968 fireEvent(name, params); 6969 }, data.delay * 1000, name, data); 6970 6971 } 6972 6973 return true; 6974 6975 }, 6976 6977 onAdSenseEvent: function (event) { 6978 this.fireEvent(event.name, event); 6979 }, 6980 6981 onClickEvent: function (event) { 6982 this.fireEvent(event.name, event); 6983 }, 6984 6985 onWatchVideo: function (event) { 6986 if(!event.hasOwnProperty("youtube_disabled") 6987 || !event.youtube_disabled 6988 || event.params.video_type !== "youtube") { 6989 this.fireEvent(event.name, event); 6990 } 6991 }, 6992 6993 onCommentEvent: function (event) { 6994 6995 this.fireEvent(event.name, event); 6996 6997 }, 6998 6999 onFormEvent: function (event) { 7000 7001 this.fireEvent(event.name, event); 7002 7003 }, 7004 7005 onDownloadEvent: function (event) { 7006 7007 this.fireEvent(event.name, event); 7008 7009 }, 7010 7011 onWooAddToCartOnButtonEvent: function (product_id, prod_info = null) { 7012 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7013 return; 7014 7015 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7016 if (window.pysWooProductData[product_id].hasOwnProperty('ga')) { 7017 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 7018 Utils.copyProperties(window.pysWooProductData[product_id]['ga'].params, event.params) 7019 event.trackingIds = window.pysWooProductData[product_id]['ga']['trackingIds']; 7020 if(prod_info) 7021 { 7022 if(prod_info['pys_list_name_productlist_id']) 7023 { 7024 event.params.items[0]['item_list_id'] = prod_info['pys_list_name_productlist_id'] 7025 } 7026 if(prod_info['pys_list_name_productlist_name']) 7027 { 7028 event.params.items[0]['item_list_name'] =prod_info['pys_list_name_productlist_name'] 7029 } 7030 } 7031 this.fireEvent(event.name, event); 7032 } 7033 } 7034 7035 }, 7036 7037 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form, prod_info) { 7038 7039 window.pysWooProductData = window.pysWooProductData || []; 7040 7041 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7042 return; 7043 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 7044 7045 if (product_type === Utils.PRODUCT_VARIABLE && !options.ga.wooVariableAsSimple) { 7046 product_id = parseInt($form.find('input[name="variation_id"]').val()); 7047 } 7048 7049 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7050 if (window.pysWooProductData[product_id].hasOwnProperty('ga')) { 7051 7052 Utils.copyProperties(window.pysWooProductData[product_id]['ga'].params, event.params); 7053 7054 7055 if(product_type === Utils.PRODUCT_GROUPED ) { 7056 var groupValue = 0; 7057 $form.find(".woocommerce-grouped-product-list .qty").each(function(index){ 7058 var childId = $(this).attr('name').replaceAll("quantity[","").replaceAll("]",""); 7059 var quantity = parseInt($(this).val()); 7060 if(isNaN(quantity)) { 7061 quantity = 0; 7062 } 7063 var childItem = window.pysWooProductData[product_id]['ga'].grouped[childId];
7064 event.params.items.forEach(function(el,index,array) { 7065 if(el.id == childItem.content_id) { 7066 if(quantity > 0){ 7067 el.quantity = quantity; 7068 el.price = childItem.price; 7069 } else { 7070 array.splice(index, 1); 7071 } 7072 } 7073 }); 7074 groupValue += childItem.price * quantity; 7075 }); 7076 7077 if(options.woo.addToCartOnButtonValueEnabled && 7078 options.woo.addToCartOnButtonValueOption !== 'global' && 7079 event.params.hasOwnProperty('value')) { 7080 event.params.value = groupValue; 7081 } 7082 7083 if(groupValue == 0) return; // skip if no items selected 7084 } else { 7085 // update items qty param 7086 event.params.items[0].quantity = qty; 7087 } 7088 7089 // maybe customize value option 7090 if (options.woo.addToCartOnButtonValueEnabled && 7091 options.woo.addToCartOnButtonValueOption !== 'global' && 7092 product_type !== Utils.PRODUCT_GROUPED) 7093 { 7094 if(event.params.hasOwnProperty('value')) { 7095 event.params.value = event.params.items[0].price * qty; 7096 } 7097 } 7098 7099 if(prod_info) 7100 { 7101 if(prod_info['pys_list_name_productlist_id']) 7102 { 7103 event.params.items[0]['item_list_id'] = prod_info['pys_list_name_productlist_id'] 7104 } 7105 if(prod_info['pys_list_name_productlist_name']) 7106 { 7107 event.params.items[0]['item_list_name'] =prod_info['pys_list_name_productlist_name'] 7108 } 7109 } 7110 7111 var eventName = is_external ? options.woo.affiliateEventName : event.name; 7112 eventName = normalizeEventName(eventName); 7113 7114 this.fireEvent(eventName, event); 7115 7116 } 7117 } 7118 7119 }, 7120 7121 onWooCheckoutProgressStep: function (event) { 7122 this.fireEvent(event.name, event); 7123 }, 7124 7125 onWooSelectContent: function (event) { 7126 const select_prod_list = {}; 7127 7128 if (event.params.items[0].item_list_name !== undefined) { 7129 select_prod_list.list_name = event.params.items[0].item_list_name; 7130 } 7131 7132 if (event.params.items[0].item_list_id !== undefined) { 7133 select_prod_list.list_id = event.params.items[0].item_list_id; 7134 } 7135 const url = new URL(window.location.href); 7136 select_prod_list.url = url.origin + url.pathname; 7137 Cookies.set('select_prod_list', JSON.stringify(select_prod_list), { expires: 1,path: '/' }); 7138 this.fireEvent(event.name, event); 7139 }, 7140 7141 onWooRemoveFromCartEvent: function (event) { 7142 this.fireEvent(event.name, event); 7143 }, 7144 7145 onWooAffiliateEvent: function (product_id) { 7146 if(!options.dynamicEvents.woo_affiliate.hasOwnProperty(this.tag())) 7147 return; 7148 var event = options.dynamicEvents.woo_affiliate[this.tag()]; 7149 7150 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7151 if (window.pysWooProductData[product_id].hasOwnProperty('ga')) { 7152 7153 event = Utils.clone(event ); 7154 Utils.copyProperties(window.pysWooProductData[product_id][this.tag()], event.params) 7155 this.fireEvent(normalizeEventName(options.woo.affiliateEventName), event); 7156 7157 } 7158 } 7159 7160 }, 7161 7162 onWooPayPalEvent: function (event) { 7163 this.fireEvent(event.name, event); 7164 }, 7165 7166 onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) { 7167 if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7168 return; 7169 var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]); 7170 7171 7172 if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) { 7173 7174 var index; 7175 7176 if (price_index) { 7177 index = download_id + '_' + price_index; 7178 } else { 7179 index = download_id; 7180 } 7181 7182 if (window.pysEddProductData[download_id].hasOwnProperty(index)) { 7183 if (window.pysEddProductData[download_id][index].hasOwnProperty('ga')) { 7184 7185 Utils.copyProperties(window.pysEddProductData[download_id][index]['ga'].params, event.params); 7186 7187 // update items qty param 7188 event.params.items[0].quantity = qty; 7189 7190 this.fireEvent(event.name,event); 7191 7192 } 7193 } 7194 7195 } 7196 7197 }, 7198 7199 onEddRemoveFromCartEvent: function (event) { 7200 this.fireEvent(event.name, event); 7201 }, 7202 7203 onPageScroll: function (event) { 7204 if (initialized && this.isEnabled()) { 7205 this.fireEvent(event.name, event); 7206 } 7207 }, 7208 onTime: function (event) { 7209 if (initialized && this.isEnabled()) { 7210 this.fireEvent(event.name, event); 7211 } 7212 }, 7213 }; 7214 7215 }(options); 7216 7217 var GAds = function (options) { 7218 7219 var initialized = false; 7220 var isAllowEnhancedConversions = false 7221 /** 7222 * Fires event 7223 * 7224 * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/sending-data 7225 * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/events 7226 * @link: https://developers.google.com/gtagjs/reference/event 7227 * @link: https://developers.google.com/gtagjs/reference/parameter 7228 */ 7229 function fireEvent(name, data) { 7230 if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(event_name,'google_ads')) { 7231 return; 7232 } 7233 var _params = Utils.copyProperties(data.params,{}); 7234 7235 _params.event_id = Utils.generateUniqueId(data); 7236 7237 var ids = data.ids.filter(function (pixelId) { 7238 return !Utils.hideMatchingPixel(pixelId, 'google_ads'); 7239 }); 7240 7241 var conversionIds = data.hasOwnProperty('conversion_ids') ? data.conversion_ids.filter(function (conversion_id) { 7242 return !Utils.hideMatchingPixel(conversion_id, 'google_ads'); 7243 }) : []; 7244 var conversion_labels = data.hasOwnProperty('conversion_labels') ? data.conversion_labels.filter(function (conversion_label) { 7245 conversion_id = conversion_label.split('/')[0]; 7246 return !Utils.hideMatchingPixel(conversion_id, 'google_ads'); 7247 }) : []; 7248
7249 Utils.copyProperties(Utils.getRequestParams(), _params); 7250 var _fireEvent = function (conversion_id,event_name) { 7251 7252 // Same rule as in the Analytics module: no destination means the 7253 // hit is parked inside gtag.js, not sent. Reaching here with an 7254 // empty list means every configured conversion ID was filtered 7255 // out (hidden pixel, or the conversionIds fallback was empty). 7256 if (!Utils.hasSendToTarget(conversion_id)) { 7257 if (options.debug) { 7258 console.warn('[Google Ads] ' + event_name + ' skipped: no conversion ID left after filtering', { 7259 ids: data.ids, 7260 conversionIds: data.conversion_ids, 7261 conversionLabels: data.conversion_labels 7262 }); 7263 } 7264 return; 7265 } 7266 7267 params = Utils.copyProperties(_params, { send_to: conversion_id }); 7268 7269 if("conversion" === event_name) { 7270 delete params.items; 7271 delete params.ecomm_pagetype; 7272 delete params.ecomm_prodid; 7273 delete params.ecomm_totalvalue; 7274 } 7275 7276 if (options.debug) { 7277 console.log('[Google Ads #' + conversion_id + '] ' + event_name, params); 7278 } 7279 Utils.gtagPush('event', event_name, params); 7280 var customEvent = new CustomEvent('gtag_event_sent', { 7281 detail: { 7282 eventName: event_name, 7283 params: params, 7284 pixel_ids: conversion_id 7285 } 7286 }); 7287 window.dispatchEvent(customEvent); 7288 7289 }; 7290 7291 7292 if ( conversion_labels.length > 0 ) { 7293 ids = conversion_labels; 7294 if ( !isTrackEventForGA.includes( name ) ) { 7295 _fireEvent( ids, name ); 7296 } 7297 } else { 7298 var conversion_event_name = data.e_id; 7299 switch ( conversion_event_name ) { 7300 case "woo_add_to_cart_on_cart_page": 7301 case "woo_add_to_cart_on_checkout_page": 7302 case "woo_add_to_cart_on_button_click": 7303 conversion_event_name = 'woo_add_to_cart'; 7304 break; 7305 case "edd_add_to_cart_on_cart_page": 7306 case "edd_add_to_cart_on_checkout_page": 7307 case "edd_add_to_cart_on_button_click": 7308 conversion_event_name = 'edd_add_to_cart'; 7309 break; 7310 case "automatic_event_adsense": 7311 case "automatic_event_comment": 7312 case "automatic_event_download": 7313 case "automatic_event_email_link": 7314 case "automatic_event_form": 7315 case "automatic_event_internal_link": 7316 case "automatic_event_outbound_link": 7317 case "automatic_event_scroll": 7318 case "automatic_event_tel_link": 7319 case "automatic_event_time_on_page": 7320 case "automatic_event_video": 7321 conversion_event_name = 'automatic_event'; 7322 break; 7323 } 7324 7325 if (ids.length) { 7326 if (conversion_event_name !== 'automatic_event' && options.hasOwnProperty('google_ads')) { 7327 // In the same way, we determine the correct name for the settings key. 7328 const trackEventName = conversion_event_name === 'woo_view_item_list' 7329 ? 'woo_view_category' 7330 : conversion_event_name; 7331 7332 const conversionTrack = options.google_ads[trackEventName + '_conversion_track']; 7333 7334 if (conversionTrack === 'conversion') { 7335 _fireEvent(ids, "conversion"); 7336 ids = conversionIds; 7337 } 7338 } 7339 } else { 7340 ids = conversionIds; 7341 } 7342
7343 if ( !isTrackEventForGA.includes( name ) ) { 7344 _fireEvent( ids, name ); 7345 } 7346 } 7347 } 7348 7349 function normalizeEventName(eventName) { 7350 7351 var matches = { 7352 ViewContent: 'view_item', 7353 AddToCart: 'add_to_cart', 7354 AddToWishList: 'add_to_wishlist', 7355 InitiateCheckout: 'begin_checkout', 7356 Purchase: 'purchase', 7357 Lead: 'generate_lead', 7358 CompleteRegistration: 'sign_up', 7359 AddPaymentInfo: 'set_checkout_option' 7360 }; 7361 7362 return matches.hasOwnProperty(eventName) ? matches[eventName] : eventName; 7363 7364 } 7365 7366 /** 7367 * Public API 7368 */ 7369 return { 7370 tag: function() { 7371 return "google_ads"; 7372 }, 7373 isEnabled: function () { 7374 return options.hasOwnProperty('google_ads'); 7375 }, 7376 getHidePixel: function(){ 7377 if(this.isEnabled() && options.google_ads.hasOwnProperty('hide_pixels')) 7378 { 7379 return options.google_ads.hide_pixels; 7380 } 7381 return []; 7382 }, 7383 disable: function () { 7384 initialized = false; 7385 }, 7386 7387 updateEnhancedConversionData : function () { 7388 7389 if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnable){ 7390 var advanced = Utils.getAdvancedMergeFormData() 7391 Utils.gtagPush('set', 'user_data', advanced); 7392 } 7393 }, 7394 7395 loadPixel: function () { 7396 7397 if (initialized || !this.isEnabled() || !Utils.consentGiven('google_ads')) { 7398 return; 7399 } 7400 var ids = options.google_ads.conversion_ids.filter(function (pixelId) { 7401 return !Utils.hideMatchingPixel(pixelId, 'google_ads'); 7402 }); 7403 loadTags.push(...ids); 7404 if(!isAdsLoad && options.google_ads.conversion_ids.length > 0){ 7405 for (var i = 0; i < options.google_ads.conversion_ids.length; i++) { 7406 var trackingId = options.google_ads.conversion_ids[i]; 7407 if (!Utils.hideMatchingPixel(trackingId, 'google_ads')) { 7408 Utils.loadGoogleTag(trackingId); 7409 break; 7410 } 7411 } 7412 7413 // configure conversion ids 7414 ids.forEach(function (conversion_id,index) { 7415 7416 let config = {}; 7417 7418 if(options.google_ads.enhanced_conversion.includes("index_"+index)) { 7419 isAllowEnhancedConversions = true; 7420 config.allow_enhanced_conversions = true 7421 } 7422 7423 // Cross-Domain tracking 7424 if (options.google_ads.crossDomainEnabled) { 7425 config.linker = { 7426 accept_incoming: options.google_ads.crossDomainAcceptIncoming, 7427 domains: options.google_ads.crossDomainDomains 7428 }; 7429 } 7430 7431 Utils.gtagPush('config', conversion_id, config ); 7432 7433 Utils.registerGadsPhoneConversion( conversion_id ); 7434 7435 if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnableuserDataEnable){ 7436 var advanced = Utils.getAdvancedMergeFormData() 7437 Utils.gtagPush('set', 'user_data', advanced); 7438 } 7439 7440 }); 7441 isAdsLoad = true; 7442 } 7443 7444 initialized = true; 7445 7446 Utils.fireStaticEvents('google_ads'); 7447 7448 }, 7449 7450 fireEvent: function (name, data) { 7451 7452 if (!initialized || !this.isEnabled()) { 7453 return false; 7454 } 7455 7456 data.delay = data.delay || 0; 7457 data.params = data.params || {}; 7458 data.ids = data.ids || []; 7459 data.conversion_labels = data.conversion_labels || []; 7460 7461 if (data.delay === 0) { 7462 fireEvent(name, data); 7463 } else { 7464 setTimeout(function (name, data) { 7465 fireEvent(name, data); 7466 }, data.delay * 1000, name, data); 7467 } 7468 7469 return true; 7470 7471 }, 7472 7473 onAdSenseEvent: function (event) { 7474 this.fireEvent(event.name, event); 7475 }, 7476 7477 onClickEvent: function (action, params) { 7478 //disabled 7479 }, 7480 7481 onWatchVideo: function (event) { 7482 this.fireEvent(event.name, event); 7483 }, 7484 7485 onCommentEvent: function (event) { 7486 this.fireEvent(event.name, event); 7487 }, 7488 7489 onFormEvent: function (event) { 7490 this.fireEvent(event.name, event); 7491 }, 7492 7493 onDownloadEvent: function (event) { 7494 this.fireEvent(event.name, event); 7495 }, 7496 7497 7498 7499 7500 onWooAddToCartOnButtonEvent: function (product_id) { 7501 7502 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7503 return; 7504 7505 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7506 if (window.pysWooProductData[product_id].hasOwnProperty('google_ads')) { 7507 7508 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]) 7509 Utils.copyProperties(window.pysWooProductData[product_id]['google_ads']['params'].params, event.params) 7510 event["ids"] = window.pysWooProductData[product_id]['google_ads']['ids'] 7511 event["conversion_labels"] = window.pysWooProductData[product_id]['google_ads']['conversion_labels'] 7512 this.fireEvent(event.name, event); 7513 } 7514 } 7515 7516 }, 7517 7518 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form) { 7519 7520 window.pysWooProductData = window.pysWooProductData || []; 7521 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7522 return; 7523 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 7524 7525 if (product_type === Utils.PRODUCT_VARIABLE && !options.google_ads.wooVariableAsSimple) { 7526 product_id = parseInt($form.find('input[name="variation_id"]').val()); 7527 } 7528 7529 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7530 if (window.pysWooProductData[product_id].hasOwnProperty('google_ads')) { 7531 7532 Utils.copyProperties(window.pysWooProductData[product_id]['google_ads']["params"], event.params); 7533 event["ids"] = window.pysWooProductData[product_id]['google_ads']['ids'] 7534 event["conversion_labels"] = window.pysWooProductData[product_id]['google_ads']['conversion_labels'] 7535 7536 var groupValue = 0; 7537 if(product_type === Utils.PRODUCT_GROUPED ) { 7538 $form.find(".woocommerce-grouped-product-list .qty").each(function(index){ 7539 var childId = $(this).attr('name').replaceAll("quantity[","").replaceAll("]",""); 7540 var quantity = parseInt($(this).val()); 7541 if(isNaN(quantity)) { 7542 quantity = 0; 7543 } 7544 7545 var childItem = window.pysWooProductData[product_id]['google_ads'].grouped[childId]; 7546 7547 if(options.woo.addToCartOnButtonValueEnabled && 7548 options.woo.addToCartOnButtonValueOption !== 'global') { 7549
7550 event.params.items.forEach(function(el,index,array) { 7551 if(el.id == childItem.content_id) { 7552 if(quantity > 0){ 7553 el.quantity = quantity; 7554 el.price = childItem.price; 7555 } else { 7556 array.splice(index, 1); 7557 } 7558 } 7559 }); 7560 } 7561 groupValue += childItem.price * quantity; 7562 }); 7563 if(groupValue == 0) return; 7564 event.params.value = groupValue; 7565 } else { 7566 // update items qty param 7567 event.params.items[0].quantity = qty; 7568 } 7569 7570 7571 7572 // maybe customize value option 7573 if (options.woo.addToCartOnButtonValueEnabled && 7574 options.woo.addToCartOnButtonValueOption !== 'global' && 7575 product_type !== Utils.PRODUCT_GROUPED) { 7576 event.params.value = event.params.value * qty; 7577 } 7578 7579 7580 7581 var eventName = is_external ? options.woo.affiliateEventName : event.name; 7582 eventName = normalizeEventName(eventName); 7583 7584 this.fireEvent(eventName, event); 7585 7586 } 7587 } 7588 7589 }, 7590 7591 onWooRemoveFromCartEvent: function (event) { 7592 this.fireEvent(event.name, event); 7593 }, 7594 7595 onWooAffiliateEvent: function (product_id) { 7596 if(!options.dynamicEvents.woo_affiliate.hasOwnProperty(this.tag())) 7597 return; 7598 var event = options.dynamicEvents.woo_affiliate[this.tag()]; 7599 7600 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7601 if (window.pysWooProductData[product_id].hasOwnProperty('google_ads')) { 7602 7603 event = Utils.clone(event) 7604 Utils.copyProperties(window.pysWooProductData[product_id][this.tag()], event.params) 7605 this.fireEvent(normalizeEventName(options.woo.affiliateEventName), event); 7606 7607 } 7608 } 7609 7610 }, 7611 7612 onWooPayPalEvent: function (event) { 7613 this.fireEvent(event.name, event); 7614 }, 7615 7616 onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) { 7617 if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7618 return; 7619 var event = options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]; 7620 7621 7622 if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) { 7623 7624 var index; 7625 7626 if (price_index) { 7627 index = download_id + '_' + price_index; 7628 } else { 7629 index = download_id; 7630 } 7631 7632 if (window.pysEddProductData[download_id].hasOwnProperty(index)) { 7633 if (window.pysEddProductData[download_id][index].hasOwnProperty('google_ads')) { 7634 7635 event = Utils.clone(event) 7636 Utils.copyProperties(window.pysEddProductData[download_id][index]['google_ads']['params'], event.params); 7637 event.ids = window.pysEddProductData[download_id][index]['google_ads']['ids'] 7638 // update items qty param 7639 //params.items[0].quantity = qty; 7640 7641 this.fireEvent(event.name, event); 7642 7643 } 7644 } 7645 7646 } 7647 7648 }, 7649 7650 onEddRemoveFromCartEvent: function (event) { 7651 this.fireEvent(event.name, event); 7652 }, 7653 onPageScroll: function (event) { 7654 if (initialized && this.isEnabled()) { 7655 this.fireEvent(event.name, event); 7656 } 7657 }, 7658 onTime: function (event) { 7659 if (initialized && this.isEnabled()) { 7660 this.fireEvent(event.name, event); 7661 } 7662 }, 7663 }; 7664 7665 }(options); 7666 7667 var GTM = function (options) { 7668 7669 var initialized = false; 7670 7671 var datalayer_name = 'dataLayer'; 7672 /* 7673 * @param name 7674 * @param data 7675 */ 7676 function fireEvent(name, event) { 7677 if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name,'gtm')) { 7678 return; 7679 } 7680 7681
7682 var eventParams = event.params; 7683 var data = event.params; 7684 var valuesArray = Object.values(event.trackingIds); 7685 var ids = valuesArray.filter(function (pixelId) { 7686 return !Utils.hideMatchingPixel(pixelId, 'gtm'); 7687 }) 7688 Utils.copyProperties(Utils.getRequestParams(), eventParams); 7689 var _fireEvent = function (tracking_id,name,params, event=null) { 7690 7691 var eventData = {}; 7692 var ContainerCodeHasTag = !options.gtm.gtm_just_data_layer && tracking_id.length > 0; 7693 if(ContainerCodeHasTag) { 7694 params['send_to'] = tracking_id; 7695 } 7696 else if(!options.gtm.gtm_just_data_layer){ 7697 return 7698 } 7699 7700 if (params.hasOwnProperty('ecommerce')) { 7701 eventData.ecommerce = params.ecommerce; 7702 delete params.ecommerce; 7703 } 7704 7705 var automatedParams = { ...params }; 7706 [params['manualName'], 'manualName', 'triggerType'].forEach(key => delete automatedParams[key]); 7707 if(event && (!event.hasOwnProperty('hasAutoParam') || (event.hasOwnProperty('hasAutoParam') && event.hasAutoParam))) { 7708 eventData.automatedParameters = automatedParams; 7709 } 7710 7711// Move custom parameters to eventData 7712 7713 if (params.hasOwnProperty(params['manualName'])) { 7714 eventData[params['manualName']] = params[params['manualName']]; 7715 delete params[params['manualName']]; 7716 } 7717 7718 ['manualName','triggerType'].forEach(key => { 7719 if (params.hasOwnProperty(key)) { 7720 eventData[key] = params[key]; 7721 delete params[key]; 7722 } 7723 }); 7724 7725 eventData.manualDataLayer = Utils.resolveGtmDataLayerName(); 7726 7727 eventData.event = name; 7728 7729 if (options.debug) { 7730 if (ContainerCodeHasTag){ 7731 console.log('[Google GTM #' + tracking_id + '] ' + name, eventData); 7732 } 7733 else { 7734 console.log('[Google GTM push to "'+datalayer_name+'"] ' + name, eventData); 7735 } 7736 } 7737 window[datalayer_name].push(eventData); 7738 7739 var customEvent = new CustomEvent('gtm_event_sent', { 7740 detail: { 7741 eventName: name, 7742 params: eventData, 7743 pixel_ids: tracking_id 7744 } 7745 }); 7746 window.dispatchEvent(customEvent); 7747 7748 }; 7749 7750 var copyParams = Utils.copyProperties(eventParams, {}); // copy params because mapParamsToGTM can modify it 7751 7752 var params = mapParamsToGTM(ids,name,copyParams) 7753 7754 params.event_id = Utils.generateUniqueId(event); 7755 7756 _fireEvent(ids, name, params, event); 7757 } 7758 7759 function normalizeEventName(eventName) { 7760 7761 var matches = { 7762 ViewContent: 'view_item', 7763 AddToCart: 'add_to_cart', 7764 AddToWishList: 'add_to_wishlist', 7765 InitiateCheckout: 'begin_checkout', 7766 Purchase: 'purchase', 7767 Lead: 'generate_lead', 7768 CompleteRegistration: 'sign_up', 7769 AddPaymentInfo: 'set_checkout_option' 7770 }; 7771 7772 return matches.hasOwnProperty(eventName) ? matches[eventName] : eventName; 7773 7774 } 7775 7776 function mapParamsToGTM(tag,name,param) { 7777 var hasGTM = false; 7778 7779 // end 7780 if (Array.isArray(tag)) { 7781 hasGTM = tag.some(function (element) { 7782 return isGTM(element); 7783 }); 7784 } else if(isGTM(tag)) { 7785 hasGTM = true; 7786 } 7787 if(hasGTM) { 7788 delete param.event_category; 7789 delete param.event_label; 7790 7791 delete param.analytics_storage; 7792 delete param.ad_storage; 7793 delete param.ad_user_data; 7794 delete param.ad_personalization; 7795 7796 if(name === 'search') { 7797 param['search'] = param.search_term; 7798 delete param.search_term; 7799 delete param.dynx_itemid; 7800 delete param.dynx_pagetype; 7801 delete param.dynx_totalvalue; 7802 } 7803 } 7804 return param; 7805 } 7806 7807 function isGTM(tag) { 7808 return tag.indexOf('GTM') === 0; 7809 } 7810 /** 7811 * Public API 7812 */ 7813 return { 7814 tag: function() { 7815 return "gtm"; 7816 }, 7817 isEnabled: function () { 7818 return options.hasOwnProperty('gtm'); 7819 }, 7820 getHidePixel: function(){ 7821 if(this.isEnabled() && options.gtm.hasOwnProperty('hide_pixels')) 7822 { 7823 return options.gtm.hide_pixels; 7824 } 7825 return []; 7826 }, 7827 disable: function () { 7828 initialized = false; 7829 }, 7830 updateEnhancedConversionData : function () { 7831 if (!initialized || !this.isEnabled()) { 7832 return; 7833 } 7834 if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnable) { 7835 var advanced = Utils.getAdvancedMergeFormData(true); 7836 if (Object.keys(advanced).length > 0) { 7837 window[datalayer_name].push({'user_data' : advanced}); 7838 } 7839 } 7840 }, 7841 loadPixel: function () { 7842 7843 if (initialized || !this.isEnabled() || !Utils.consentGiven('analytics')) { 7844 return; 7845 } 7846 datalayer_name = Utils.resolveGtmDataLayerName(); 7847 7848 for (var i = 0; i < options.gtm.trackingIds.length; i++) { 7849 var trackingId = options.gtm.trackingIds[i]; 7850 if (!Utils.hideMatchingPixel(trackingId, 'gtm') && !options.gtm.gtm_just_data_layer) { 7851 console.log('[PYS] Google Tag Manager container code loaded'); 7852 Utils.loadGTMScript(trackingId); 7853 break; 7854 } 7855 } 7856 7857 if(options.gtm.gtm_just_data_layer) { 7858 console.warn && console.warn("[PYS] Google Tag Manager container code placement set to OFF !!!"); 7859 console.warn && console.warn("[PYS] Data layer codes are active but GTM container must be loaded using custom coding !!!"); 7860 // Send the consent state even when GTM script loading is handled 7861 // externally: gtm_just_data_layer with tracking IDs configured skips 7862 // loadGTMScript() entirely. When trackingIds is empty, loadGTMScript() 7863 // below runs too and would reach this again â pushGTMConsentState() 7864 // is idempotent per page load, so the container still sees one command. 7865 Utils.pushGTMConsentState( datalayer_name ); 7866 if(options.gtm.trackingIds.length == 0){ 7867 Utils.loadGTMScript(); 7868 } 7869 } 7870 7871 if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnable){ 7872 var advanced = Utils.getAdvancedMergeFormData(true); 7873 if(Object.keys(advanced).length > 0){ 7874 window[datalayer_name].push({'user_data' : advanced}); 7875 } 7876 } 7877 7878 7879 var config = {}; 7880 7881 if(options.user_id && options.user_id != 0) { 7882 config.user_id = options.user_id; 7883 } 7884 7885 // Cross-Domain tracking 7886 7887 var ids = options.gtm.trackingIds.filter(function (pixelId) { 7888 return !Utils.hideMatchingPixel(pixelId, 'gtm'); 7889 }); 7890 7891 7892 initialized = true; 7893 7894 Utils.fireStaticEvents('gtm'); 7895 }, 7896 7897 fireEvent: function (name, data) { 7898 7899 if (!initialized || !this.isEnabled()) { 7900 return false; 7901 } 7902 7903 data.delay = data.delay || 0; 7904 data.params = data.params || {}; 7905 7906 if (data.delay === 0) { 7907 7908 fireEvent(name, data); 7909 7910 } else { 7911 7912 setTimeout(function (name, params) { 7913 fireEvent(name, params); 7914 }, data.delay * 1000, name, data); 7915 7916 } 7917 7918 return true; 7919 7920 }, 7921 7922 onAdSenseEvent: function (event) { 7923 this.fireEvent(event.name, event); 7924 }, 7925 7926 onClickEvent: function (event) { 7927 this.fireEvent(event.name, event); 7928 }, 7929 7930 onWatchVideo: function (event) { 7931 if(!event.hasOwnProperty("youtube_disabled") 7932 || !event.youtube_disabled 7933 || event.params.video_type !== "youtube") { 7934 this.fireEvent(event.name, event); 7935 } 7936 }, 7937 7938 onCommentEvent: function (event) { 7939 7940 this.fireEvent(event.name, event); 7941 7942 }, 7943 7944 onFormEvent: function (event) { 7945 7946 this.fireEvent(event.name, event); 7947 7948 }, 7949 7950 onDownloadEvent: function (event) { 7951 7952 this.fireEvent(event.name, event); 7953 7954 }, 7955 7956 onWooAddToCartOnButtonEvent: function (product_id, prod_info = null) { 7957 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7958 return; 7959 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7960 if (window.pysWooProductData[product_id].hasOwnProperty('gtm')) { 7961 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 7962 Utils.copyProperties(window.pysWooProductData[product_id]['gtm'].params, event.params) 7963 event.trackingIds = window.pysWooProductData[product_id]['gtm']['trackingIds']; 7964 7965 7966 if(prod_info) 7967 { 7968 item = event.params.hasOwnProperty('ecommerce') ? event.params.ecommerce.items[0] : event.params.items[0]; 7969 7970 if(prod_info['pys_list_name_productlist_id']) 7971 { 7972 item['item_list_id'] = prod_info['pys_list_name_productlist_id'] 7973 } 7974 if(prod_info['pys_list_name_productlist_name']) 7975 { 7976 item['item_list_name'] =prod_info['pys_list_name_productlist_name'] 7977 } 7978 } 7979 this.fireEvent(event.name, event); 7980 } 7981 } 7982 7983 }, 7984 7985 onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form, prod_info) { 7986 window.pysWooProductData = window.pysWooProductData || []; 7987 7988 if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 7989 return; 7990 var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]); 7991 7992 if (product_type === Utils.PRODUCT_VARIABLE && !options.gtm.wooVariableAsSimple) { 7993 product_id = parseInt($form.find('input[name="variation_id"]').val()); 7994 } 7995 7996 if (window.pysWooProductData.hasOwnProperty(product_id)) { 7997 if (window.pysWooProductData[product_id].hasOwnProperty('gtm')) { 7998 7999 Utils.copyProperties(window.pysWooProductData[product_id]['gtm'].params, event.params); 8000 8001 params = event.params.hasOwnProperty('ecommerce') ? event.params.ecommerce : event.params; 8002 8003 if(product_type === Utils.PRODUCT_GROUPED ) { 8004 var groupValue = 0; 8005 $form.find(".woocommerce-grouped-product-list .qty").each(function(index){ 8006 var childId = $(this).attr('name').replaceAll("quantity[","").replaceAll("]",""); 8007 var quantity = parseInt($(this).val()); 8008 if(isNaN(quantity)) { 8009 quantity = 0; 8010 } 8011 var childItem = window.pysWooProductData[product_id]['gtm'].grouped[childId]; 8012
8013 params.items.forEach(function(el,index,array) { 8014 if(el.id == childItem.content_id) { 8015 if(quantity > 0){ 8016 el.quantity = quantity; 8017 el.price = childItem.price; 8018 } else { 8019 array.splice(index, 1); 8020 } 8021 } 8022 }); 8023 groupValue += childItem.price * quantity; 8024 }); 8025 8026 8027 8028 if(options.woo.addToCartOnButtonValueEnabled && 8029 options.woo.addToCartOnButtonValueOption !== 'global' && 8030 params.hasOwnProperty('value')) { 8031 params.value = groupValue; 8032 } 8033 8034 if(groupValue == 0) return; // skip if no items selected 8035 } else { 8036 // update items qty param 8037 params.items[0].quantity = qty; 8038 } 8039 8040 // maybe customize value option 8041 if (options.woo.addToCartOnButtonValueEnabled && 8042 options.woo.addToCartOnButtonValueOption !== 'global' && 8043 product_type !== Utils.PRODUCT_GROUPED) 8044 { 8045 if(params.hasOwnProperty('value')) { 8046 params.value = params.items[0].price * qty; 8047 } 8048 } 8049 8050 if(prod_info) 8051 { 8052 if(prod_info['pys_list_name_productlist_id']) 8053 { 8054 params.items[0]['item_list_id'] = prod_info['pys_list_name_productlist_id'] 8055 } 8056 if(prod_info['pys_list_name_productlist_name']) 8057 { 8058 params.items[0]['item_list_name'] =prod_info['pys_list_name_productlist_name'] 8059 } 8060 } 8061 8062 var eventName = is_external ? options.woo.affiliateEventName : event.name; 8063 eventName = normalizeEventName(eventName); 8064 8065 this.fireEvent(eventName, event); 8066 8067 } 8068 } 8069 8070 }, 8071 8072 onWooCheckoutProgressStep: function (event) { 8073 this.fireEvent(event.name, event); 8074 }, 8075 8076 onWooSelectContent: function (event) { 8077 const select_prod_list = {}; 8078 8079 if (event.params.items[0].item_list_name !== undefined) { 8080 select_prod_list.list_name = event.params.items[0].item_list_name; 8081 } 8082 8083 if (event.params.items[0].item_list_id !== undefined) { 8084 select_prod_list.list_id = event.params.items[0].item_list_id; 8085 } 8086 const url = new URL(window.location.href); 8087 select_prod_list.url = url.origin + url.pathname; 8088 Cookies.set('select_prod_list', JSON.stringify(select_prod_list), { expires: 1,path: '/' }); 8089 this.fireEvent(event.name, event); 8090 }, 8091 8092 onWooRemoveFromCartEvent: function (event) { 8093 this.fireEvent(event.name, event); 8094 }, 8095 8096 onWooAffiliateEvent: function (product_id) { 8097 if(!options.dynamicEvents.woo_affiliate.hasOwnProperty(this.tag())) 8098 return; 8099 var event = options.dynamicEvents.woo_affiliate[this.tag()]; 8100 8101 if (window.pysWooProductData.hasOwnProperty(product_id)) { 8102 if (window.pysWooProductData[product_id].hasOwnProperty('gtm')) { 8103 8104 event = Utils.clone(event ); 8105 Utils.copyProperties(window.pysWooProductData[product_id][this.tag()], event.params) 8106 this.fireEvent(normalizeEventName(options.woo.affiliateEventName), event); 8107 8108 } 8109 } 8110 8111 }, 8112 8113 onWooPayPalEvent: function (event) { 8114 this.fireEvent(event.name, event); 8115 }, 8116 8117 onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) { 8118 if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag())) 8119 return; 8120 var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]); 8121 8122 8123 if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) { 8124 8125 var index; 8126 8127 if (price_index) { 8128 index = download_id + '_' + price_index; 8129 } else { 8130 index = download_id; 8131 } 8132 8133 if (window.pysEddProductData[download_id].hasOwnProperty(index)) { 8134 if (window.pysEddProductData[download_id][index].hasOwnProperty('gtm')) { 8135 8136 Utils.copyProperties(window.pysEddProductData[download_id][index]['gtm'].params, event.params); 8137 item = event.params.hasOwnProperty('ecommerce') ? event.params.ecommerce.items[0] : event.params.items[0]; 8138 // update items qty param 8139 item.quantity = qty; 8140 8141 this.fireEvent(event.name,event); 8142 8143 } 8144 } 8145 8146 } 8147 8148 }, 8149 8150 onEddRemoveFromCartEvent: function (event) { 8151 this.fireEvent(event.name, event); 8152 }, 8153 8154 onPageScroll: function (event) { 8155 if (initialized && this.isEnabled()) { 8156 this.fireEvent(event.name, event); 8157 } 8158 }, 8159 onTime: function (event) { 8160 if (initialized && this.isEnabled()) { 8161 this.fireEvent(event.name, event); 8162 } 8163 }, 8164 }; 8165 8166 }(options); 8167 8168 var getPixelBySlag = function getPixelBySlag(slug) { 8169 switch (slug) { 8170 case "facebook": return window.pys.Facebook; 8171 case "ga": return window.pys.Analytics; 8172 case "gtm": return window.pys.GTM; 8173 case "google_ads": return window.pys.GAds; 8174 case "bing": return window.pys.Bing; 8175 case "pinterest": return window.pys.Pinterest; 8176 case "tiktok": return window.pys.TikTok; 8177 case "openai": return window.pys.OpenAI; 8178 case "reddit": return window.pys.Reddit; 8179 } 8180 } 8181
8182 window.pys = window.pys || {}; 8183 window.pys.Facebook = Facebook; 8184 window.pys.Analytics = Analytics; 8185 window.pys.GAds = GAds; 8186 window.pys.GTM = GTM; 8187 window.pys.Utils = Utils; 8188 window.pys.TikTok = TikTok; 8189 window.pys.OpenAI = OpenAI; 8190 window.pys.getPixelBySlag = getPixelBySlag; 8191 8192 // Allow external scripts (e.g. generatePixelCode fragments) to refresh 8193 // the uniqueId cache for a given event key, so that each new add_to_cart 8194 // action gets a fresh shared ID across all pixels. 8195 window.pys.setEventUniqueId = function(idKey, newId) { 8196 uniqueId[idKey] = newId; 8197 }; 8198 8199 window.pys.fireAjaxTriggerEvent = function(slug, eventData) { 8200 var pixel = getPixelBySlag(slug); 8201 if (!pixel) return; 8202 8203 // Extract the eventId the same way PHP does (custom_event_post_id takes priority over e_id). 8204 var eventId = (eventData.custom_event_post_id || eventData.e_id) || null; 8205 // Mirror fireTriggerEvent: respect "once per page" frequency setting. 8206 if (eventId && options.triggerFrequency && options.triggerFrequency[eventId] === 'once') { 8207 if (pysEventFiredOnce[eventId]) return; 8208 pysEventFiredOnce[eventId] = true; 8209 } 8210 8211 // Mirror fireTriggerEvent: respect the time-window setting (data already in eventData). 8212 if (!Utils.isEventInTimeWindow(eventData.name, eventData, 'ajax_' + slug)) return; 8213 8214 pixel.fireEvent(eventData.name, eventData); 8215 }; 8216 8217 // Export getPixelBySlag globally for backward compatibility 8218 window.getPixelBySlag = getPixelBySlag; 8219 8220 function pysNormalInit() { 8221 8222 if($("#pys_late_event").length > 0) { 8223 var dirAttr = $("#pys_late_event").attr("dir"); 8224 if (dirAttr) { 8225 try { 8226 var events = JSON.parse(dirAttr); 8227 } catch (e) { 8228 console.warn("Invalid JSON in pys_late_event dir attribute:", e); 8229 } 8230 } else { 8231 console.warn("pys_late_event dir attribute is undefined or empty"); 8232 } 8233 if (events) { 8234 for (var platform in events) { 8235 if (events.hasOwnProperty(platform)) { 8236 var platformEvents = events[platform]; 8237 platformEvents.forEach(function (event) { 8238 var eventData = {}; 8239 eventData[event.e_id] = [event]; 8240 if (options.staticEvents.hasOwnProperty(platform)) { 8241 Object.assign(options.staticEvents[platform], eventData); 8242 } else { 8243 options.staticEvents[platform] = eventData; 8244 } 8245 }); 8246 } 8247 } 8248 } 8249 } 8250 8251 var Pinterest = Utils.setupPinterestObject(); 8252 var Bing = Utils.setupBingObject(); 8253 var Reddit = Utils.setupRedditObject(); 8254 if(options.hasOwnProperty('cookie')) 8255 { 8256 if(options.cookie.externalID_disabled_by_api || options.cookie.disabled_all_cookie) 8257 { 8258 Cookies.remove('pbid', { path: '/',domain: cookieDomain() }) 8259 } 8260 if(options.cookie.disabled_advanced_form_data_cookie || options.cookie.disabled_all_cookie) 8261 { 8262 // Removed twice on purpose: PHP writes this cookie host-only 8263 // until pys_cd is reported (or permanently, on a site whose 8264 // front end never reaches manageCookies()), while this script 8265 // writes/deletes it at cookieDomain(). A host-only cookie and 8266 // a domain-scoped one of the same name are separate jar 8267 // entries, and a delete only matches the scope it names â so 8268 // both scopes are cleared rather than guessed at. 8269 // `domain: ''` is deliberate: js-cookie 2.1.3 treats a falsy 8270 // Domain as "omit the attribute" (host-only delete), so it's 8271 // written explicitly rather than left off. 8272 Cookies.remove('pys_advanced_form_data', { path: '/',domain: cookieDomain() }) 8273 Cookies.remove('pys_advanced_form_data', { path: '/',domain: '' }) 8274 } 8275 8276 if(options.cookie.disabled_landing_page_cookie || !options.enable_lading_page_param || options.cookie.disabled_all_cookie) 8277 { 8278 Cookies.remove('pys_landing_page', { path: '/',domain: cookieDomain() }) 8279 Cookies.remove('last_pys_landing_page', { path: '/',domain: cookieDomain() }) 8280 } 8281 if(options.cookie.disabled_trafficsource_cookie || options.cookie.disabled_all_cookie) 8282 { 8283 Cookies.remove('pysTrafficSource', { path: '/',domain: cookieDomain() }) 8284 Cookies.remove('last_pysTrafficSource', { path: '/',domain: cookieDomain() }) 8285 } 8286 if(options.cookie.disabled_first_visit_cookie || options.cookie.disabled_all_cookie) 8287 { 8288 Cookies.remove('pys_first_visit', { path: '/',domain: cookieDomain() }) 8289 8290 } 8291 if(options.cookie.disabled_utmTerms_cookie || options.cookie.disabled_all_cookie) 8292 { 8293 $.each(Utils.utmTerms, function (index, name) { 8294 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 8295 }); 8296 $.each(Utils.utmTerms, function (index, name) { 8297 Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() }) 8298 }); 8299 } 8300 if(options.cookie.disabled_utmId_cookie || options.cookie.disabled_all_cookie) 8301 { 8302 $.each(Utils.utmId,function(index,name) { 8303 Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() }) 8304 }) 8305 $.each(Utils.utmId,function(index,name) { 8306 Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() }) 8307 }); 8308 } 8309 } 8310 8311 // Exactly one manageCookies() per init, and under a consent 8312 // integration only once consent is on record. The old fall-through 8313 // ran it a second time under CookieYes, racing two pbid requests, and 8314 // ran it before any consent when no CookieYes cookie existed yet. 8315 // external_id itself does not depend on this: the visitor key is 8316 // computed server-side without a cookie. 8317 var cookiesManaged = false; 8318 8319 if (options.gdpr.cookie_law_info_integration_enabled) { 8320 var cli_cookie = Cookies.get('cookieyes-consent') ?? Cookies.get('wt_consent') ?? Cookies.get('viewed_cookie_policy'); 8321 8322 if (typeof cli_cookie !== 'undefined') { 8323 if (cli_cookie === Cookies.get('cookieyes-consent') || cli_cookie === Cookies.get('wt_consent')) { 8324 if (getCookieYes('analytics') === 'yes') { 8325 Utils.manageCookies(); 8326 cookiesManaged = true; 8327 } 8328 } else if (cli_cookie === Cookies.get('viewed_cookie_policy') && cli_cookie == 'yes') { 8329 Utils.manageCookies(); 8330 cookiesManaged = true; 8331 } 8332 } 8333 } 8334 8335 if ( ! cookiesManaged ) { 8336 if ( options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined" ) { 8337 if ( CS_Data.cs_script_cat.pys == CS_Data.cs_necessary_cat_id || CS_Data.cs_script_cat.pys == 0 ) { 8338 Utils.manageCookies(); 8339 } else if ( Cookies.get( 'cs_enabled_cookie_term' + CS_Data.test_prefix + '_' + CS_Data.cs_script_cat.pys ) == 'yes' ) { 8340 Utils.manageCookies(); 8341 } 8342 } else if ( ! options.gdpr.cookie_law_info_integration_enabled ) { 8343 Utils.manageCookies(); 8344 } 8345 } 8346
8347 Utils.initializeRequestParams(); 8348 Utils.setupGdprCallbacks(); 8349 8350 if ( options.enable_auto_save_advance_matching ) { 8351 let override = options.data_persistency == 'recent_data'; 8352 // Setup Advanced Form Data 8353 if ( options.advance_matching_form.enable_advance_matching_forms ) { 8354 8355 // Handle Email fields 8356 $( document ).on( "blur", "input[type='email']", function () { 8357 // SECURITY: Ignore fields located inside the Authorize.Net payment block 8358 if ( $( this ).closest('.payment_method_anet').length > 0 ) return; 8359 8360 let email = $( this ).val().trim().toLowerCase(); 8361 if ( Utils.validateEmail( email ) ) { 8362 Utils.saveAdvancedFormData( email, null, null, null, override ); 8363 } 8364 } ) 8365 8366 // Handle Telephone fields (This is where Credit Card numbers were being captured) 8367 $( document ).on( "blur", "input[type='tel']", function () { 8368 // SECURITY: Ignore fields located inside the Authorize.Net payment block 8369 if ( $( this ).closest('.payment_method_anet').length > 0 ) return; 8370 8371 let phone = $( this ).val().trim().replace( /\D/g, "" ); 8372 if ( phone.length > 5 ) { 8373 Utils.saveAdvancedFormData( null, phone, null, null, override ); 8374 } 8375 } ) 8376 8377 // Handle Text fields 8378 $( document ).on( "blur", "input[type='text']", function () { 8379 // SECURITY: Ignore fields located inside the Authorize.Net payment block 8380 if ( $( this ).closest('.payment_method_anet').length > 0 ) return; 8381 8382 let name; 8383 if ( $( this ).attr( "name" ) && $( this ).attr( "name" ) != '' ) { 8384 name = $( this ).attr( "name" ).trim() 8385 } 8386 if ( name && options.advance_matching_form.advance_matching_fn_names.includes( name ) ) { 8387 let value = $( this ).val().trim(); 8388 if ( value.length > 0 ) { 8389 Utils.saveAdvancedFormData( null, null, value, null, override ); 8390 } 8391 } 8392 if ( name && options.advance_matching_form.advance_matching_ln_names.includes( name ) ) { 8393 let value = $( this ).val().trim(); 8394 if ( value.length > 0 ) { 8395 Utils.saveAdvancedFormData( null, null, null, value, override ); 8396 } 8397 } 8398 if ( name && options.advance_matching_form.advance_matching_tel_names.includes( name ) ) { 8399 let value = $( this ).val().trim(); 8400 if ( value.length > 0 ) { 8401 Utils.saveAdvancedFormData( null, value, null, null, override ); 8402 } 8403 } 8404 if ( name && options.advance_matching_form.advance_matching_em_names.includes( name ) ) { 8405 let email = $( this ).val().trim().toLowerCase(); 8406 8407 if ( Utils.validateEmail( email ) ) { 8408 Utils.saveAdvancedFormData( email, null, null, null, false ); 8409 } 8410 } 8411 } ) 8412 } 8413 8414 //Setup Advanced Url Data 8415 if ( Object.keys( options.advance_matching_url ).length > 0 && options.advance_matching_url.enable_advance_matching_url ) { 8416 const url_params = new URLSearchParams( window.location.search ); 8417 url_params.forEach( ( value, key ) => { 8418 if ( options.advance_matching_url.advance_matching_fn_names.includes( key ) ) { 8419 Utils.saveAdvancedFormData( null, null, value.trim(), null, override ); 8420 } 8421 8422 if ( options.advance_matching_url.advance_matching_ln_names.includes( key ) ) { 8423 Utils.saveAdvancedFormData( null, null, null, value.trim(), override ); 8424 } 8425 8426 if ( options.advance_matching_url.advance_matching_tel_names.includes( key ) ) { 8427 Utils.saveAdvancedFormData( null, value.trim(), null, null, override ); 8428 } 8429 8430 if ( options.advance_matching_url.advance_matching_em_names.includes( key ) ) { 8431 let email = value.trim().toLowerCase() 8432 if ( Utils.validateEmail( email ) ) { 8433 Utils.saveAdvancedFormData( email, null, null, null, override ); 8434 } 8435 } 8436 } ); 8437 } 8438 } 8439 8440 // setup Click Event 8441 if ( 8442 options.dynamicEvents.hasOwnProperty("automatic_event_internal_link") || 8443 options.dynamicEvents.hasOwnProperty("automatic_event_outbound_link") || 8444 options.dynamicEvents.hasOwnProperty("automatic_event_tel_link") || 8445 options.dynamicEvents.hasOwnProperty("automatic_event_email_link") || 8446 options.dynamicEvents.hasOwnProperty("automatic_event_download") 8447 ) { 8448 8449 $(document).onFirst('click', 'a, button, input[type="button"], input[type="submit"]', function (e) { 8450 8451 var $elem = $(this); 8452 8453 8454 // Download 8455 if(options.dynamicEvents.hasOwnProperty("automatic_event_download")) { 8456 var isFired = false; 8457 if ($elem.is('a')) { 8458 var href = $elem.attr('href'); 8459 if (typeof href !== "string") { 8460 return; 8461 } 8462 href = href.trim(); 8463 var extension = Utils.getLinkExtension(href); 8464 var track_download = false; 8465 8466 if (extension.length > 0) { 8467 8468 if(options.dynamicEvents.hasOwnProperty("automatic_event_download") ) { 8469 var pixels = Object.keys(options.dynamicEvents.automatic_event_download); 8470 for (var i = 0; i < pixels.length; i++) { 8471 var event = Utils.clone(options.dynamicEvents.automatic_event_download[pixels[i]]); 8472 var extensions = event.extensions; 8473 if (extensions.includes(extension)) { 8474 8475 if(pixels[i] == "tiktok" || pixels[i] == "reddit") { 8476 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 8477 } else { 8478 if (options.enable_remove_download_url_param) { 8479 href = stripUrlQuery(href); 8480 } 8481 event.params.download_url = href; 8482 event.params.download_type = extension; 8483 event.params.download_name = Utils.getLinkFilename(href); 8484 getPixelBySlag(pixels[i]).onDownloadEvent(event); 8485 } 8486 8487 isFired = true; 8488 } 8489 } 8490 } 8491 } 8492 } 8493 if(isFired) { // prevent duplicate events on the same element 8494 return; 8495 } 8496 } 8497 8498 8499 if (!e.hasOwnProperty('originalEvent')) { 8500 return; 8501 } 8502 if($elem.hasClass("add_to_cart_button") || 8503 $elem.hasClass("single_add_to_cart_button") ) { // add_to_cart_button fire in woo 8504 return; 8505 } 8506 8507 if(options.dynamicEvents.hasOwnProperty("wcf_add_to_cart_on_next_step_click") 8508 && $elem.hasClass("wcf-next-step-link")) { 8509 return;// add_to_cart_button fire in woo cf 8510 } 8511 if(options.dynamicEvents.hasOwnProperty("wcf_add_to_cart_on_bump_click") 8512 && $elem.hasClass("wcf-bump-order-cb")) { 8513 return;// add_to_cart_button fire in woo cf 8514 } 8515 8516 if($elem.hasClass("remove_from_cart_button") ) { // cancel remove from cart 8517 return; 8518 } 8519 if($elem.hasClass("remove") ) { // cancel remove from cart 8520 if($elem.parents('.cart_item').length || $elem.parents('.mini_cart_item').length) 8521 return; 8522 } 8523 if($elem.attr("name") == "update_cart" || $elem.attr("name") == "apply_coupon") { // cancel update c
8523art or coupon button 8524 return; 8525 } 8526 8527 8528 if ($elem.hasClass('pys_block')) { 8529 return; // avoiding fake double clicks from Affiliate event 8530 } 8531 var text = ""; 8532 var target_url = ""; 8533 var linkType = "Internal Click"; 8534 8535 if ($elem.is('a')) { 8536 var href = $elem.attr('href'); 8537 8538 // fixes #112 8539 if (typeof href !== "string") { 8540 return; 8541 } 8542 href = href.trim(); 8543 8544 text = $elem.contents().filter(function() { 8545 return this.nodeType === 3; // Filter text nodes only 8546 }).text().trim(); 8547 if(options.enable_remove_target_url_param) { 8548 target_url = stripUrlQuery(href); 8549 } else { 8550 target_url = href 8551 } 8552 8553 8554 //Email Event 8555 if ( href.startsWith( 'mailto:' ) ) { 8556 if ( options.triggerEventTypes.hasOwnProperty( "email_link" ) ) { 8557 return; 8558 } else { 8559 setupEmailLinks(); 8560 } 8561 8562 return; // not fire next 8563 } 8564 8565 // Phone 8566 if (href.startsWith('tel:')) { 8567 if(options.dynamicEvents.hasOwnProperty("automatic_event_tel_link")) { 8568 var pixels = Object.keys(options.dynamicEvents.automatic_event_tel_link); 8569 for(var i = 0;i<pixels.length;i++) { 8570 var event = Utils.clone(options.dynamicEvents.automatic_event_tel_link[pixels[i]]); 8571 if ( pixels[i] !== 'tiktok' && pixels[i] !== 'reddit') { 8572 Utils.copyProperties(Utils.getRequestParam
8572s(), event.params); 8573 } 8574 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 8575 } 8576 } 8577 return; // not fire next 8578 } 8579 8580 if (href.startsWith('http')) { 8581 // link type 8582 var host = $elem.context != undefined ? $elem.context.host : $elem[0].host; 8583 if (document.location.host != host) { 8584 linkType = 'External Click'; 8585 } 8586 } 8587 } else if ($elem.is('button')) { 8588 if( $elem.hasClass("forminator-button-submit")) { 8589 //disable duplicate events 8590 return; 8591 } 8592 text = $elem.contents().filter(function() { 8593 return this.nodeType === 3; // Filter text nodes only 8594 }).text().trim(); 8595 } else if ($elem.is('input[type="button"]')) { 8596 text = $elem.val(); 8597 } else if ($elem.is('input[type="submit"]')) { 8598 if ( $elem.parents("form.comment-form")) { 8599 //disable duplicate events 8600 return; 8601 } 8602 if ($elem.parents("form")) { 8603 //disable duplicate events 8604 return; 8605 } 8606 text = $elem.val(); 8607 } else { 8608 return; 8609 } 8610 8611 8612 8613 text = Utils.filterEmails(text); 8614 8615 8616 if( linkType === "Internal Click" 8617 && options.dynamicEvents.hasOwnProperty("automatic_event_internal_link") 8618 ) { 8619 var pixels = Object.keys(options.dynamicEvents.automatic_event_internal_link); 8620 8621 for(var i = 0;i<pixels.length;i++) { 8622 var event = Utils.clone(options.dynamicEvents.automatic_event_internal_link[pixels[i]]); 8623 8624 if(pixels[i] !== "tiktok" && pixels[i] !== "reddit") { // TT and reddit don't support custom parameters 8625 event.params["text"] = text; 8626 if(target_url){ 8627 event.params["target_url"] = target_url; 8628 } 8629 Utils.copyProperties(Utils.getRequestParam
8629s(), event.params); 8630 } 8631 8632 8633 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 8634 } 8635 } 8636 8637 if( linkType === "External Click" 8638 && options.dynamicEvents.hasOwnProperty("automatic_event_outbound_link") 8639 ) { 8640 var pixels = Object.keys(options.dynamicEvents.automatic_event_outbound_link); 8641 8642 for(var i = 0;i<pixels.length;i++) { 8643 var event = Utils.clone(options.dynamicEvents.automatic_event_outbound_link[pixels[i]]); 8644 8645 if(pixels[i] !== "tiktok" && pixels[i] !== "reddit") { // TT and reddit don't support custom parameters 8646 event.params["text"] = text; 8647 if(target_url){ 8648 event.params["target_url"] = target_url; 8649 } 8650 Utils.copyProperties(Utils.getRequestParams(), event.params); 8651 } 8652 8653 8654 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 8655 } 8656 } 8657 8658 8659 8660 8661 }); 8662 8663 } 8664 8665 // setup AdSense Event 8666 if (options.dynamicEvents.hasOwnProperty("automatic_event_adsense")) { 8667 8668 var isOverGoogleAd = false; 8669 8670 $(document) 8671 .on('mouseover', 'ins iframe', function () { //class adsbygoogle adsbygoogle-noablate ?? 8672 isOverGoogleAd = true; 8673 }) 8674 .on('mouseout', 'iframe', function () { 8675 isOverGoogleAd = false; 8676 }); 8677 8678 $(window) 8679 .on( "blur",function () { 8680 if (isOverGoogleAd) { 8681 if(options.dynamicEvents.hasOwnProperty("automatic_event_adsense")) { 8682 var pixels = Object.keys(options.dynamicEvents.automatic_event_adsense); 8683 for (var i = 0; i < pixels.length; i++) { 8684 var event = Utils.clone(options.dynamicEvents.automatic_event_adsense[pixels[i]]); 8685 if ( pixels[i] !== 'tiktok' && pixels[i] !== 'reddit') { 8686 Utils.copyProperties(Utils.getRequestParam
8686s(), event.params); 8687 } 8688 getPixelBySlag(pixels[i]).onAdSenseEvent(event); 8689 } 8690 } 8691 8692 $.each(options.triggerEventTypes, function (triggerType, events) { 8693 $.each(events, function (eventId, triggers) { 8694 switch (triggerType) { 8695 case 'ad_sense_click': 8696 Utils.handleTriggerFired(eventId, triggerType); 8697 break; 8698 } 8699 }); 8700 }); 8701 } 8702 }) 8703 .trigger("focus"); 8704 8705 } 8706 8707 //setup adsense for custom events 8708 var dynamicAdsenseEventsTriggers = 0 8709 $.each(options.triggerEventTypes, function (triggerType, events) { 8710 if(triggerType == "ad_sense_click") { 8711 dynamicAdsenseEventsTriggers++; 8712 } 8713 }); 8714 if (dynamicAdsenseEventsTriggers > 0) { 8715 8716 var isOverGoogleAd = false; 8717 8718 $(document) 8719 .on('mouseover', 'ins > ins > iframe', function () { 8720 isOverGoogleAd = true; 8721 }) 8722 .on('mouseout', 'iframe', function () { 8723 isOverGoogleAd = false; 8724 }); 8725 8726 $(window) 8727 .on( "blur",function () { 8728 if (isOverGoogleAd) { 8729 8730 $.each(options.triggerEventTypes, function (triggerType, events) { 8731 $.each(events, function (eventId, triggers) { 8732 switch (triggerType) { 8733 case 'ad_sense_click': 8734 Utils.handleTriggerFired(eventId, triggerType); 8735 break; 8736 } 8737 }); 8738 }); 8739 } 8740 }) 8741 .trigger("focus"); 8742 8743 } 8744 8745 8746 // page scroll event 8747 if (options.dynamicEvents.hasOwnProperty("automatic_event_scroll") 8748 ) { 8749 8750 var singlePageScroll = function () { 8751 8752 8753 var docHeight = $(document).height() - $(window).height(); 8754 var isFired = false; 8755 8756 if (options.dynamicEvents.hasOwnProperty("automatic_event_scroll")) { 8757 var pixels = Object.keys(options.dynamicEvents.automatic_event_scroll); 8758 for(var i = 0;i<pixels.length;i++) { 8759 var event = Utils.clone(options.dynamicEvents.automatic_event_scroll[pixels[i]]); 8760 var scroll = Math.round(docHeight * event.scroll_percent / 100)// convert % to absolute positions 8761 8762 if(scroll < $(window).scrollTop()) { 8763 if ( pixels[i] !== 'tiktok' && pixels[i] !== 'reddit') { 8764 Utils.copyProperties(Utils.getRequestParam
8764s(), event.params); 8765 } 8766 getPixelBySlag(pixels[i]).onPageScroll(event); 8767 isFired = true 8768 } 8769 } 8770 } 8771 if(isFired) { 8772 $(document).off("scroll",singlePageScroll); 8773 } 8774 } 8775 $(document).on("scroll",singlePageScroll); 8776 } 8777 8778 8779 if (options.dynamicEvents.hasOwnProperty("automatic_event_time_on_page")) { 8780 var pixels = Object.keys(options.dynamicEvents.automatic_event_time_on_page); 8781 var time = options.dynamicEvents.automatic_event_time_on_page[pixels[0]].time_on_page; // the same for all pixel 8782 setTimeout(function(){ 8783 for(var i = 0;i<pixels.length;i++) { 8784 var event = Utils.clone(options.dynamicEvents.automatic_event_time_on_page[pixels[i]]); 8785 if ( pixels[i] !== 'tiktok' && pixels[i] !== 'reddit') { 8786 Utils.copyProperties(Utils.getRequestParam
8786s(), event.params); 8787 } 8788 getPixelBySlag(pixels[i]).onTime(event); 8789 } 8790 },time*1000); 8791 } 8792 8793 // setup Dynamic events 8794 8795 $.each(options.triggerEventTypes, function (triggerType, events) { 8796 8797 $.each(events, function (eventId, triggers) { 8798 switch (triggerType) { 8799 case 'url_click': 8800 //@see: Utils.setupURLClickEvents() 8801 break; 8802 8803 case 'css_click': 8804 var cssTriggerData = Array.isArray(triggers) ? { selectors: triggers, click_count: 1, click_time_limit: 0 } : triggers; 8805 var cssSelectors = cssTriggerData.selectors || triggers; 8806 var cssClickCount = cssTriggerData.click_count || 1; 8807 var cssTimeLimit = parseInt(cssTriggerData.click_time_limit, 10) || 0; 8808 Utils.setupCSSClickEvents(eventId, cssSelectors, triggerType, cssClickCount, cssTimeLimit); 8809 break; 8810 8811 case 'css_mouseover': 8812 Utils.setupMouseOverClickEvents(eventId, triggers, triggerType); 8813 break; 8814 8815 case 'scroll_pos': 8816 Utils.setupScrollPosEvents(eventId, triggers, triggerType); 8817 break; 8818 case 'comment': 8819 Utils.setupCommentEvents(eventId, triggers); 8820 break; 8821 case 'video_view': 8822 //@see: Utils.checkYouTubeCompletion() and Utils.addYouTubeEvents() 8823 break; 8824 8825 case 'form_field': 8826 Utils.setupFormFieldEvents(eventId, triggers, triggerType); 8827 break; 8828 8829 case 'copy_element': 8830 Utils.setupCopyElementEvents(eventId, triggers); 8831 break; 8832 8833 case 'video_speed': 8834 Utils.setupVideoSpeedEvents(eventId, triggers); 8835 break; 8836 } 8837 8838 }); 8839 8840 if ( triggerType == "email_link" ) { 8841 Utils.setupEmailLinkEvents( events ) 8842 } 8843 8844 }); 8845 8846 // Global RageClick tracking 8847 if (options.dynamicEvents.hasOwnProperty('automatic_event_rage_click')) { 8848 (function() { 8849 var RAGE_THRESHOLD = Math.max(2, parseInt(options.rage_click_threshold, 10) || 3); 8850 // rage_click_time_limit from admin in seconds; allow floats (e.g. 1.5); minimum 0.5s. 8851 var RAGE_TIMEOUT = Math.max(0.5, parseFloat(options.rage_click_time_limit) || 2) * 1000; 8852 var rageMap = typeof WeakMap !== 'undefined' ? new WeakMap() : null; 8853 8854 if (!rageMap) return; 8855 8856 // Expanded interactive selector â includes developer-marked elements. 8857 var INTERACTIVE_SELECTOR = 8858 'button, a, input, select, textarea, label, ' + 8859 '[role="button"], [role="link"], [onclick], [tabindex], [data-track], [data-testid]'; 8860 8861 // Walk up to the nearest meaningful interactive ancestor. 8862 // Returns null when none found â non-interactive clicks are silently ignored. 8863 // Note: having an id alone does NOT make an element interactive 8864 // (e.g. div#comments, div#reviews are structural, not clickable). 8865 function findClickTarget(el) { 8866 while (el && el !== document.body) { 8867 if (el.matches && el.matches(INTERACTIVE_SELECTOR)) { 8868 return el; 8869 } 8870 el = el.parentElement; 8871 } 8872 return null; 8873 } 8874 8875 // Build a breadcrumb path: up to 3 DOM nodes ending at el. 8876 // Example: "nav#menu > ul.links > button.cta" 8877 function buildElementPath(el, depth) { 8878 depth = depth || 3; 8879 var parts = []; 8880 var cur = el; 8881 while (cur && cur !== document.body && parts.length < depth) { 8882 var tag = cur.tagName ? cur.tagName.toLowerCase() : ''; 8883 var id = cur.id ? '#' + cur.id : ''; 8884 var rawCls = cur.className && typeof cur.className === 'string' 8885 ? cur.className 8886 : (cur.className && cur.className.baseVal ? cur.className.baseVal : ''); 8887 var cls = rawCls ? '.' + rawCls.trim().split(/\s+/).slice(0, 2).join('.') : ''; 8888 parts.unshift(tag + id + cls); 8889 cur = cur.parentElement; 8890 } 8891 return parts.join(' > '); 8892 } 8893 8894 function describeElement(el) { 8895 var raw = el.className && (typeof el.className === 'string' 8896 ? el.className 8897 : (el.className.baseVal || '')); 8898 var classes = raw ? raw.trim().split(/\s+/).slice(0, 3).join(' ') : ''; 8899 8900 var text = (el.innerText || el.value || el.getAttribute('aria-label') || '') 8901 .trim().replace(/\s+/g, ' ').slice(0, 80); 8902 8903 var href = el.tagName === 'A' ? (el.getAttribute('href') || '') : ''; 8904 var tag = el.tagName ? el.tagName.toLowerCase() : ''; 8905 var path = buildElementPath(el); 8906
8907 // Only include fields with non-empty values â don't pollute the event 8908 // payload with empty strings that add noise to dashboards. 8909 var result = {}; 8910 if (tag) { result.element_tag = tag; result.element = tag; } // element = backward-compat alias 8911 if (el.id) result.element_id = el.id; 8912 if (classes) result.element_classes = classes; 8913 if (text) result.element_text = text; 8914 if (href) result.element_href = href; 8915 if (path) result.element_path = path; 8916 return result; 8917 } 8918 8919 document.addEventListener('click', function(event) { 8920 // Resolve to the nearest meaningful interactive ancestor. 8921 // Non-interactive elements (bare <div>, <span>, <h2> etc.) are silently 8922 // ignored â no fallback to event.target. 8923 var target = findClickTarget(event.target); 8924 if (!target) return; 8925 8926 var now = Date.now(); 8927 var st = rageMap.get(target) || { count: 0, last: 0, timer: null }; 8928 8929 if (now - st.last > RAGE_TIMEOUT) st.count = 0; 8930 st.count++; 8931 st.last = now; 8932 8933 if (st.timer) clearTimeout(st.timer); 8934 st.timer = setTimeout(function() { rageMap.delete(target); }, RAGE_TIMEOUT); 8935 rageMap.set(target, st); 8936 8937 if (st.count >= RAGE_THRESHOLD) { 8938 rageMap.delete(target); 8939 var params = describeElement(target); // contains only non-empty fields 8940 8941 Object.keys(options.dynamicEvents.automatic_event_rage_click).forEach(function(slug) { 8942 var pysEvent = Utils.clone(options.dynamicEvents.automatic_event_rage_click[slug]); 8943 Utils.copyProperties(params, pysEvent.params); 8944 var px = getPixelBySlag(slug); 8945 if (px) px.fireEvent(pysEvent.name, pysEvent); 8946 }); 8947 } 8948 }, true); 8949 })(); 8950 } 8951 8952 // Global Video Speed tracking 8953 if (options.dynamicEvents.hasOwnProperty('automatic_event_video_speed')) { 8954 (function() { 8955 var pysFireVideoSpeed = function(videoEl, currRate) { 8956 var html5Url = videoEl ? (videoEl.src || videoEl.currentSrc || '') : ''; 8957 var params = { 8958 video_url: html5Url, 8959 video_type: Utils.getVideoTypeFromUrl(html5Url), 8960 video_title: Utils.getHtml5VideoTitle(html5Url), 8961 playback_speed: currRate, 8962 element: videoEl ? (videoEl.tagName ? videoEl.tagName.toLowerCase() : 'video') : 'video' 8963 }; 8964 Object.keys(options.dynamicEvents.automatic_event_video_speed).forEach(function(slug) { 8965 var pysEvent = Utils.clone(options.dynamicEvents.automatic_event_video_speed[slug]); 8966 Utils.copyProperties(params, pysEvent.params); 8967 var px = getPixelBySlag(slug); 8968 if (px) px.fireEvent(pysEvent.name, pysEvent); 8969 }); 8970 }; 8971 8972 // HTML5 <video> 8973 document.addEventListener('ratechange', function(event) { 8974 var video = event.target; 8975 if (!video || video.tagName !== 'VIDEO') return; 8976 var prev = video._pysGlobalSpeedRate !== undefined ? video._pysGlobalSpeedRate : 1; 8977 var curr = video.playbackRate; 8978 if (prev === curr) return; 8979 video._pysGlobalSpeedRate = curr; 8980 pysFireVideoSpeed(video, curr); 8981 }, true); 8982 })(); 8983 } 8984 8985 // setup WooCommerce events 8986 if (options.woo.enabled) { 8987 8988 // Woo CartFlow AddToCart 8989 if (options.dynamicEvents.hasOwnProperty("wcf_add_to_cart_on_next_step_click")) { 8990 $("body").on("click",'.wcf-next-step-link',function () { 8991 var pixels = Object.keys(options.dynamicEvents.wcf_add_to_cart_on_next_step_click); 8992 for(var i = 0;i<pixels.length;i++) { 8993 var event = Utils.clone(options.dynamicEvents.wcf_add_to_cart_on_next_step_click[pixels[i]]) 8994 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 8995 } 8996 }); 8997 } 8998 // Woo CartFlow Bump AddToCart 8999 if(options.dynamicEvents.hasOwnProperty("wcf_add_to_cart_on_bump_click") || 9000 options.dynamicEvents.hasOwnProperty("wcf_bump")) { 9001 $("body").on('change','.wcf-bump-order-cb',function () { 9002 9003 if(this.checked) { 9004 if(options.dynamicEvents.hasOwnProperty("wcf_add_to_cart_on_bump_click")) { 9005 var pixels = Object.keys(options.dynamicEvents.wcf_add_to_cart_on_bump_click); 9006 for(var i = 0;i<pixels.length;i++) { 9007 var event = Utils.clone(options.dynamicEvents.wcf_add_to_cart_on_bump_click[pixels[i]]); 9008 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 9009 } 9010 } 9011 9012 9013 if(options.dynamicEvents.hasOwnProperty("wcf_bump")) { 9014 var pixels = Object.keys(options.dynamicEvents.wcf_bump); 9015 for(var i = 0;i<pixels.length;i++) { 9016 var event = Utils.clone(options.dynamicEvents.wcf_bump[pixels[i]]); 9017 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 9018 } 9019 } 9020 9021 } else { 9022 if(options.dynamicEvents.hasOwnProperty("wcf_remove_from_cart_on_bump_click")) { 9023 var pixels = Object.keys(options.dynamicEvents.wcf_remove_from_cart_on_bump_click); 9024 for(var i = 0;i<pixels.length;i++) { 9025 var event = Utils.clone(options.dynamicEvents.wcf_remove_from_cart_on_bump_click[pixels[i]]); 9026 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 9027 } 9028 } 9029 9030 } 9031 9032 9033 }); 9034 } 9035 9036 9037 // WooCommerce AddToCart 9038 if (options.dynamicEvents.hasOwnProperty("woo_add_to_cart_on_button_click") 9039 && options.woo.hasOwnProperty("addToCartCatchMethod") 9040 && options.woo.addToCartCatchMethod === "add_cart_js" 9041 ) { 9042 // Loop, any kind of "simple" product, except external 9043 $('.add_to_cart_button:not(.product_type_variable,.product_type_bundle,.single_add_to_cart_button)').on("click",function (e) { 9044 9045 var $button = $(this); 9046 var $prod_info = $button.closest('.product').find('.pys_list_name_productdata').data(); 9047 var product_id = $(this).data('product_id'); 9048 Cookies.set('productlist', JSON.stringify($prod_info), { expires: 1,path: '/' }) 9049 var product_id = $(this).data('product_id'); 9050 if (typeof product_id !== 'undefined') { 9051 9052 if(options.dynamicEvents.hasOwnProperty("woo_add_to_cart_on_button_click")){ 9053 var tmpEventID = pys_generate_token(); 9054 $.each(options.dynamicEvents.woo_add_to_cart_on_button_click, function (i, tag) { 9055 tag.eventID = tmpEventID; 9056 // Also refresh the uniqueId cache so server-API path 9057 // (ajaxForServerStaticEvent) gets the same fresh ID. 9058 var idKey = tag.hasOwnProperty('custom_event_post_id') ? tag.custom_event_post_id : tag.e_id; 9059 uniqueId[idKey] = tmpEventID; 9060 }); 9061 } 9062 9063 Facebook.onWooAddToCartOnButtonEvent(product_id,$(this)); 9064 Analytics.onWooAddToCartOnButtonEvent(product_id, $prod_info); 9065 GTM.onWooAddToCartOnButtonEvent(product_id, $prod_info); 9066 GAds.onWooAddToCartOnButtonEvent(product_id); 9067 Pinterest.onWooAddToCartOnButtonEvent(product_id); 9068 Bing.onWooAddToCartOnButtonEvent(product_id); 9069 TikTok.onWooAddToCartOnButtonEvent(product_id); 9070 Reddit.onWooAddToCartOnButtonEvent(product_id); 9071 OpenAI.onWooAddToCartOnButtonEvent(product_id); 9072 } 9073 9074 }); 9075 9076 // Single Product 9077 // tap try to https://stackoverflow.com/questions/30990967/on-tap-click-event-firing-twice-how-to-avoid-it 9078 // $(document) not work 9079 $('body').onFirst('click','button.single_add_to_cart_button,.single_add_to_cart_button',function (e) { 9080 9081 var $button = $(this); 9082 var $prod_info = $button.siblings('.pys_list_name_productdata').data(); 9083 var product_id = $(this).data('product_id'); 9084 Cookies.set('productlist', JSON.stringify($prod_info), { expires: 1,path: '/' }) 9085 if ($button.hasClass('disabled')) { 9086 return; 9087 } 9088 9089 var $form = $button.closest('form'); 9090 9091 var product_type = Utils.PRODUCT_SIMPLE; 9092 var is_external = false; 9093 9094 if ($form.length === 0) { 9095 is_external = true; 9096 } else if ($form.hasClass('variations_form')) { 9097 product_type = Utils.PRODUCT_VARIABLE; 9098 } else if($form.hasClass('bundle_form')) { 9099 product_type = Utils.PRODUCT_BUNDLE; 9100 } else if($form.hasClass('grouped_form')) { 9101 product_type = Utils.PRODUCT_GROUPED; 9102 } 9103 9104 9105 9106 var product_id; 9107 var qty = 1; 9108 if (product_type === Utils.PRODUCT_GROUPED) { 9109 qty = 1; 9110 product_id = parseInt($form.find('*[name="add-to-cart"]').val()); 9111 } else if (product_type === Utils.PRODUCT_VARIABLE) { 9112 product_id = parseInt($form.find('*[name="add-to-cart"]').val()); 9113 var qtyTag = $form.find('input[name="quantity"]'); 9114 if(qtyTag.length <= 0) { 9115 qtyTag = $form.find('select[name="quantity"]'); 9116 } 9117 qty = parseInt(qtyTag.val()); 9118 } else if (is_external) { 9119 product_id = options.woo.singleProductId; 9120 qty = 1; 9121 } else { 9122 product_id = parseInt($form.find('*[name="add-to-cart"]').val()); 9123 var qtyTag = $form.find('input[name="quantity"]'); 9124 if(qtyTag.length <= 0) { 9125 qtyTag = $form.find('select[name="quantity"]'); 9126 } 9127 qty = parseInt(qtyTag.val()); 9128 } 9129 9130 // 9131 if(options.dynamicEvents.hasOwnProperty("woo_add_to_cart_on_button_click")){ 9132 var tmpEventID = pys_generate_token(); 9133 $.each(options.dynamicEvents.woo_add_to_cart_on_button_click, function (i, tag) { 9134 tag.eventID = tmpEventID; 9135 // Also refresh the uniqueId cache so server-API path 9136 // (ajaxForServerStaticEvent) gets the same fresh ID. 9137 var idKey = tag.hasOwnProperty('custom_event_post_id') ? tag.custom_event_post_id : tag.e_id; 9138 uniqueId[idKey] = tmpEventID; 9139 }); 9140 } 9141 9142 Facebook.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form); 9143 Analytics.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form, $prod_info); 9144 GTM.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form, $prod_info); 9145 GAds.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form); 9146 Pinterest.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form); 9147 Bing.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form); 9148 TikTok.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form); 9149 Reddit.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form); 9150 OpenAI.onWooAddToCartOnSingleEvent(product_id, qty, product_type, is_external, $form); 9151 }); 9152 9153 } else { 9154 $('.add_to_cart_button:not(.product_type_variable,.product_type_bundle,.single_add_to_cart_button)').on("click",function (e) { 9155 var $button = $(this); 9156 var $prod_info = $button.siblings('.pys_list_name_productdata').data(); 9157 var product_id = $(this).data('product_id'); 9158 Cookies.set('productlist', JSON.stringify($prod_info), { expires: 1,path: '/' }) 9159 }); 9160 9161 // Single Product 9162 // tap try to https://stackoverflow.com/questions/30990967/on-tap-click-event-firing-twice-how-to-avoid-it 9163 // $(document) not work 9164 $('body').onFirst('click','button.single_add_to_cart_button,.single_add_to_cart_button',function (e) { 9165 9166 var $button = $(this); 9167 var $prod_info = $button.siblings('.pys_list_name_productdata').data(); 9168 Cookies.set('productlist', $prod_info, { expires: 1,path: '/' }) 9169 }); 9170 } 9171 9172 // WooCommerce Affiliate 9173 if (options.dynamicEvents.hasOwnProperty("woo_affiliate")) { 9174 9175 // Loop, external 9176 $('.product_type_external').on("click",function (e) { 9177 9178 var product_id = $(this).data('product_id'); 9179 9180 if (typeof product_id !== 'undefined') { 9181 Facebook.onWooAffiliateEvent(product_id); 9182 Analytics.onWooAffiliateEvent(product_id); 9183 GTM.onWooAffiliateEvent(product_id); 9184 GAds.onWooAffiliateEvent(product_id); 9185 Pinterest.onWooAffiliateEvent(product_id); 9186 Bing.onWooAffiliateEvent(product_id); 9187 } 9188 9189 }); 9190 9191 } 9192 9193 // WooCommerce RemoveFromCart 9194 if (options.dynamicEvents.hasOwnProperty("woo_remove_from_cart")) { 9195 9196 $('body').on('click', options.woo.removeFromCartSelector, function (e) { 9197 9198 var $a = $(e.currentTarget), 9199 href = $a.attr('href'); 9200 9201 // extract cart item hash from remove button URL 9202 var regex = new RegExp("[\\?&]remove_item=([^&#]*)"), 9203 results = regex.exec(href); 9204 9205 if (results !== null) { 9206 9207 var item_hash = results[1]; 9208 9209 if (options.dynamicEvents["
9209woo_remove_from_cart"].hasOwnProperty(item_hash)) { 9210 var events = options.dynamicEvents["woo_remove_from_cart"][item_hash]; 9211 Utils.fireEventForAllPixel("onWooRemoveFromCartEvent",events) 9212 } 9213 9214 } 9215 9216 }); 9217 9218 } 9219 9220 // WooCommerce PayPal 9221 if (options.dynamicEvents.hasOwnProperty("woo_paypal")) { 9222 9223 // Non-default binding used to avoid situations when some code in external js 9224 // stopping events propagation, eg. returns false, and our handler will never called 9225 $(document).onFirst('submit click', '#place_order', function (e) { 9226 9227 var method = $('form[name="checkout"] input[name="payment_method"]:checked').val(); 9228 9229 if (method !== 'paypal') { 9230 return; 9231 } 9232 var events = options.dynamicEvents.woo_paypal; 9233 Utils.fireEventForAllPixel("onWooPayPalEvent",events) 9234 9235 }); 9236 9237 } 9238 9239 // WooCommerce checkout progress 9240 if(options.dynamicEvents.hasOwnProperty("woo_initiate_checkout_progress_f") ) { 9241 9242 $(document).on("change",".woocommerce-validated #billing_first_name",function () { 9243 var pixels = Object.keys(options.dynamicEvents.woo_initiate_checkout_progress_f); 9244 for (var i = 0; i < pixels.length; i++) { 9245 var event = Utils.clone(options.dynamicEvents.woo_initiate_checkout_progress_f[pixels[i]]); 9246 getPixelBySlag(pixels[i]).onWooCheckoutProgressStep(event); 9247 } 9248 }); 9249 } 9250 if(options.dynamicEvents.hasOwnProperty("woo_initiate_checkout_progress_l")) { 9251 9252 $(document).on("change",".woocommerce-validated #billing_last_name",function () { 9253 var pixels = Object.keys(options.dynamicEvents.woo_initiate_checkout_progress_l); 9254 for (var i = 0; i < pixels.length; i++) { 9255 var event = Utils.clone(options.dynamicEvents.woo_initiate_checkout_progress_l[pixels[i]]); 9256 getPixelBySlag(pixels[i]).onWooCheckoutProgressStep(event); 9257 } 9258 }); 9259 } 9260 9261 if(options.dynamicEvents.hasOwnProperty("woo_initiate_checkout_progress_e")) { 9262 9263 $(document).on("change",".woocommerce-validated #billing_email",function () { 9264 var pixels = Object.keys(options.dynamicEvents.woo_initiate_checkout_progress_e); 9265 for (var i = 0; i < pixels.length; i++) { 9266 var event = Utils.clone(options.dynamicEvents.woo_initiate_checkout_progress_e[pixels[i]]); 9267 getPixelBySlag(pixels[i]).onWooCheckoutProgressStep(event); 9268 } 9269 }); 9270 } 9271 if(options.dynamicEvents.hasOwnProperty("woo_initiate_checkout_progress_o")) { 9272 $(document).onFirst('submit click', '#place_order', function () { 9273 var shippingMethodFull = $('[name="shipping_method[0]"]:checked').val(); 9274 var shippingMethod = shippingMethodFull ? shippingMethodFull.split(':')[0] : ''; 9275 var pixels = Object.keys(options.dynamicEvents.woo_initiate_checkout_progress_o); 9276 for (var i = 0; i < pixels.length; i++) { 9277 var event = Utils.clone(options.dynamicEvents.woo_initiate_checkout_progress_o[pixels[i]]); 9278 // Add shipping_method to event params 9279 event.params = event.params || {}; 9280 event.params.checkout_option = shippingMethod; 9281 getPixelBySlag(pixels[i]).onWooCheckoutProgressStep(event); 9282 } 9283 }); 9284 } 9285 9286 9287 // WooCommerce 9288 9289 $('.product.type-product a.woocommerce-loop-product__link').onFirst('click', function (evt) { 9290 var productId = $(this).closest('.product').find(".add_to_cart_button").attr("data-product_id"); 9291 var eventTypes = ["woo_select_content_search", "woo_select_content_shop", "woo_select_content_tag", "woo_select_content_single", "woo_select_content_category"]; 9292 var isEventFired = false; 9293 9294 for (var i = 0; i < eventTypes.length; i++) { 9295 var eventType = eventTypes[i];
9296 if (options.dynamicEvents.hasOwnProperty(eventType) && options.dynamicEvents[eventType].hasOwnProperty(productId)) { 9297 if(Analytics.isEnabled()){ 9298 Analytics.onWooSelectContent(options.dynamicEvents[eventType][productId][Analytics.tag()]); 9299 } 9300 if(GTM.isEnabled()){ 9301 GTM.onWooSelectContent(options.dynamicEvents[eventType][productId][GTM.tag()]); 9302 } 9303 isEventFired = true; 9304 break; 9305 } 9306 } 9307 9308 if (!isEventFired) { 9309 let prod_info = $(this).parent().find('.pys_list_name_productdata').data(); 9310 if (prod_info) { 9311 const select_prod_list = {}; 9312 if (prod_info['pys_list_name_productlist_name']) { 9313 select_prod_list.list_name = prod_info['pys_list_name_productlist_name'] 9314 } 9315 if (prod_info['pys_list_name_productlist_id']) { 9316 select_prod_list.list_id = prod_info['pys_list_name_productlist_id'] 9317 } 9318 const url = new URL(window.location.href); 9319 select_prod_list.url = url.origin + url.pathname; 9320 Cookies.set('select_prod_list', JSON.stringify(select_prod_list), { expires: 1,path: '/' }); 9321 } 9322 } 9323 }); 9324 9325 } 9326 9327 // setup EDD events 9328 if (options.edd.enabled) { 9329 9330 // EDD AddToCart 9331 if (options.dynamicEvents.hasOwnProperty("edd_add_to_cart_on_button_click")) { 9332 9333 $('form.edd_download_purchase_form .edd-add-to-cart').on("click",function (e) { 9334 9335 var $button = $(this); 9336 var $form = $button.closest('form'); 9337 var variable_price = $button.data('variablePrice'); // yes/no 9338 var price_mode = $button.data('priceMode'); // single/multi 9339 var ids = []; 9340 var quantities = []; 9341 var qty; 9342 var id; 9343 9344 if (variable_price === 'yes' && price_mode === 'multi') { 9345 9346 id = $form.find('input[name="download_id"]').val(); 9347 9348 // get selected variants 9349 $.each($form.find('input[name="edd_options[price_id][]"]:checked'), function (i, el) { 9350 ids.push(id + '_' + $(el).val()); 9351 }); 9352 9353 // get qty for selected variants 9354 $.each(ids, function (i, variant_id) { 9355 9356 var variant_index = variant_id.split('_', 2); 9357 qty = $form.find('input[name="edd_download_quantity_' + variant_index[1] + '"]').val(); 9358 9359 if (typeof qty !== 'undefined') { 9360 quantities.push(qty); 9361 } else { 9362 quantities.push(1); 9363 } 9364 9365 }); 9366 9367 } else if (variable_price === 'yes' && price_mode === 'single') { 9368 9369 id = $form.find('input[name="download_id"]').val(); 9370 ids.push(id + '_' + $form.find('input[name="edd_options[price_id][]"]:checked').val()); 9371 9372 qty = $form.find('input[name="edd_download_quantity"]').val(); 9373 9374 if (typeof qty !== 'undefined') { 9375 quantities.push(qty); 9376 } else { 9377 quantities.push(1); 9378 } 9379 9380 } else { 9381 9382 ids.push($button.data('downloadId')); 9383 9384 qty = $form.find('input[name="edd_download_quantity"]').val(); 9385 9386 if (typeof qty !== 'undefined') { 9387 quantities.push(qty); 9388 } else { 9389 quantities.push(1); 9390 } 9391 } 9392 9393 // fire event for each download/variant 9394 $.each(ids, function (i, download_id) { 9395 9396 var q = parseInt(quantities[i]); 9397 var variant_index = download_id.toString().split('_', 2); 9398 var price_index; 9399 9400 if (variant_index.length === 2) { 9401 download_id = variant_index[0]; 9402 price_index = variant_index[1]; 9403 } 9404 9405 Facebook.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9406 Analytics.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9407 GTM.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9408 GAds.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9409 Pinterest.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9410 Bing.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9411 TikTok.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9412 Reddit.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9413 OpenAI.onEddAddToCartOnButtonEvent(download_id, price_index, q); 9414 9415 }); 9416 9417 }); 9418 9419 } 9420 9421 // EDD RemoveFromCart 9422 if (options.dynamicEvents.hasOwnProperty("edd_remove_from_cart") ) { 9423 9424 $('form#edd_checkout_cart_form .edd_cart_remove_item_btn').on("click",function (e) { 9425 9426 var href = $(this).attr('href'); 9427 var key = href.substring(href.indexOf('=') + 1).charAt(0);
9428 9429 if (options.dynamicEvents.edd_remove_from_cart.hasOwnProperty(key)) { 9430 var events = options.dynamicEvents.edd_remove_from_cart[key]; 9431 Utils.fireEventForAllPixel("onEddRemoveFromCartEvent",events) 9432 } 9433 9434 }); 9435 9436 } 9437 9438 } 9439 9440 Utils.setupURLClickEvents(); 9441 9442 // setup Comment Event 9443 if (options.dynamicEvents.hasOwnProperty("automatic_event_comment") 9444 ) { 9445 9446 $('form.comment-form').on("submit",function () { 9447 if (options.dynamicEvents.hasOwnProperty("automatic_event_comment")) { 9448 var pixels = Object.keys(options.dynamicEvents.automatic_event_comment); 9449 for (var i = 0; i < pixels.length; i++) { 9450 var event = Utils.clone(options.dynamicEvents.automatic_event_comment[pixels[i]]); 9451 if ( pixels[i] !== 'tiktok' && pixels[i] !== 'reddit') { 9452 Utils.copyProperties(Utils.getRequestParam
9452s(), event.params); 9453 } 9454 getPixelBySlag(pixels[i]).onCommentEvent(event); 9455 } 9456 } 9457 }); 9458 9459 } 9460 9461 // setup Form Event 9462 if ( options.dynamicEvents.hasOwnProperty("automatic_event_form")) { 9463 9464 $(document).onFirst('submit', 'form', function (e) { 9465 9466 var $form = $(this); 9467 9468 // exclude WP forms 9469 const formClass = $form.attr('class') || ''; 9470 const formRole = ($form.attr('role') || '').toLowerCase(); 9471 const formId = $form.attr('id'); 9472 9473 if ( 9474 $form.hasClass('comment-form') || 9475 $form.hasClass('search-form') || 9476 $form.hasClass('woocommerce-ordering') || 9477 formClass.toLowerCase().includes('search') || formClass.toLowerCase().includes('add-to-cart') || 9478 formRole === 'search' || 9479 formId === 'adminbarsearch' 9480 ) { 9481 return; 9482 } 9483 9484 // exclude Woo forms 9485 if ($form.hasClass('woocommerce-product-search') || $form.hasClass('cart') || $form.hasClass('woocommerce-cart-form') || 9486 $form.hasClass('woocommerce-shipping-calculator') || $form.hasClass('checkout') || $form.hasClass('checkout_coupon')) { 9487 return; 9488 } 9489 9490 // exclude EDD forms 9491 if ($form.hasClass('edd_form') || $form.hasClass('edd_download_purchase_form')) { 9492 return; 9493 } 9494 // exclude CF7 forms 9495 if ($form.hasClass('wpcf7-form')) { 9496 return; 9497 } 9498 // exclude Gravity forms 9499 if ($form.attr('id') && $form.attr('id').includes('gform')) { 9500 var target = $form.attr('target'); 9501 if (target && target.indexOf('gform_ajax_frame') !== -1) { 9502 return; 9503 } 9504 } 9505 // exclude Forminator forms 9506 if ($form.hasClass('forminator-custom-form') || $form.hasClass('forminator_ajax')) { 9507 return; 9508 } 9509 // exclude WPforms forms 9510 if ($form.hasClass('wpforms-form') || $form.hasClass('wpforms-ajax-form')) { 9511 return; 9512 } 9513 // exclude Formidable forms 9514 /*if ($form.hasClass('frm-show-form')) { 9515 return; 9516 }*/ 9517 // exclude Ninja Forms forms 9518 if ($form.parent().hasClass('nf-form-layout')) { 9519 return; 9520 } 9521 // exclude Fluent forms 9522 if ($form.hasClass('frm-fluent-form')) { 9523 return; 9524 } 9525 9526 // exclude WS form forms 9527 if ($form.hasClass('wsf-form') ) { 9528 return; 9529 } 9530 9531 // exclude WS form forms 9532 if ( $form.hasClass( 'elementor-form' ) ) { 9533 return; 9534 } 9535 9536 if(!options.enable_success_send_form) { 9537 var params = { 9538 form_id: $form.attr('id'), 9539 form_class: $form.attr('class') ? $form.attr('class') : $form.attr('id'), 9540 text: $form.find('[type="submit"]').is('input') ? 9541 $form.find('[type="submit"]').val() : $form.find('[type="submit"]').text() 9542 }; 9543 9544 if (options.dynamicEvents.hasOwnProperty("automatic_event_form")) { 9545 var pixels = Object.keys(options.dynamicEvents.automatic_event_form); 9546 for (var i = 0; i < pixels.length; i++) { 9547 var event = Utils.clone(options.dynamicEvents.automatic_event_form[pixels[i]]); 9548 9549 if (pixels[i] === "tiktok" || pixels[i] === "reddit") { 9550 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 9551 } else { 9552 Utils.copyProperties(params, event.params,) 9553 Utils.copyProperties(Utils.getRequestParam
9553s(), event.params); 9554 getPixelBySlag(pixels[i]).onFormEvent(event); 9555 } 9556 } 9557 } 9558 } 9559 }); 9560 9561 9562 } 9563 9564 jQuery(document).on('elementor/popup/show', function(event) { 9565 for (var i = 0; i < jQuery(".wpcf7-form").length; i++) { 9566 wpcf7.init(jQuery(".wpcf7-form")[i]); 9567 } 9568 }); 9569 9570 document.addEventListener( 'wpcf7mailsent', function ( event ) { 9571 let form_id = event.detail.contactFormId, 9572 sendEventId = null, 9573 disabled_form_action = false, 9574 form_submit_mode = 'both'; 9575 if ( options.triggerEventTypes.hasOwnProperty( 'CF7' ) ) { 9576 $.each( options.triggerEventTypes.CF7, function ( eventId, triggers ) { 9577 $.each( triggers, function ( index, trigger ) { 9578 $.each( trigger.forms, function ( i, value ) { 9579 if ( value == form_id ) { 9580 sendEventId = eventId; 9581 disabled_form_action = trigger.disabled_form_action; 9582 form_submit_mode = trigger.form_submit_mode || 'both'; 9583 } 9584 } ) 9585 } ); 9586 } ); 9587 } 9588 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9589 Utils.handleTriggerFired( sendEventId, 'CF7' ); 9590 } 9591 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9592 9593 }, false ); 9594 9595 //GravityForm 9596 jQuery( document ).on( 'gform_confirmation_loaded', function ( event, formId ) { 9597 let form_id = formId, 9598 sendEventId = null, 9599 disabled_form_action = false, 9600 form_submit_mode = 'both'; 9601 if ( options.triggerEventTypes.hasOwnProperty( 'gravity' ) ) { 9602 $.each( options.triggerEventTypes.gravity, function ( eventId, triggers ) { 9603 $.each( triggers, function ( index, trigger ) { 9604 $.each( trigger.forms, function ( i, value ) { 9605 if ( value == form_id ) { 9606 disabled_form_action = trigger.disabled_form_action; 9607 sendEventId = eventId; 9608 form_submit_mode = trigger.form_submit_mode || 'both'; 9609 } 9610 } ) 9611 } ); 9612 } ); 9613 } 9614 9615 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9616 Utils.handleTriggerFired( sendEventId, 'gravity' ); 9617 } 9618 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9619 jQuery( document ).off( 'gform_confirmation_loaded' ); 9620 } ); 9621 //Forminator 9622 $( document ).on( 'forminator:form:submit:success', function ( event ) { 9623 9624 let form_id = $( event.target ).find( 'input[name="form_id"]' ).val(), 9625 sendEventId = null, 9626 disabled_form_action = false, 9627 form_submit_mode = 'both'; 9628 if ( options.triggerEventTypes.hasOwnProperty( 'forminator' ) ) { 9629 $.each( options.triggerEventTypes.forminator, function ( eventId, triggers ) { 9630 $.each( triggers, function ( index, trigger ) { 9631 $.each( trigger.forms, function ( i, value ) { 9632 if ( value == form_id ) { 9633 disabled_form_action = trigger.disabled_form_action; 9634 sendEventId = eventId; 9635 form_submit_mode = trigger.form_submit_mode || 'both'; 9636 } 9637 } ) 9638 } ); 9639 } ); 9640 } 9641 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9642 Utils.handleTriggerFired( sendEventId, 'forminator' ); 9643 } 9644 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9645 } ); 9646 9647 //WPForm 9648 $( 'form.wpforms-form' ).on( 'wpformsAjaxSubmitSuccess', ( event ) => { 9649 9650 let form_id = $( event.target ).attr( 'data-formid' ), 9651 sendEventId = null, 9652 disabled_form_action = false, 9653 form_submit_mode = 'both'; 9654 9655 if ( options.triggerEventTypes.hasOwnProperty( 'wpforms' ) ) { 9656 $.each( options.triggerEventTypes.wpforms, function ( eventId, triggers ) { 9657 $.each( triggers, function ( index, trigger ) { 9658 $.each( trigger.forms, function ( i, value ) { 9659 if ( value == form_id ) { 9660 sendEventId = eventId; 9661 disabled_form_action = trigger.disabled_form_action; 9662 form_submit_mode = trigger.form_submit_mode || 'both'; 9663 } 9664 } ) 9665 } ); 9666 } ); 9667 } 9668 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9669 Utils.handleTriggerFired( sendEventId, 'wpforms' ); 9670 } 9671 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9672 } ) 9673 9674 $( document ).on( 'frmFormComplete', function ( event, form, response ) { 9675 const form_id = $( form ).find( 'input[name="form_id"]' ).val(); 9676 let sendEventId = null, 9677 disabled_form_action = false, 9678 form_submit_mode = 'both'; 9679 if ( options.triggerEventTypes.hasOwnProperty( 'formidable' ) ) { 9680 $.each( options.triggerEventTypes.formidable, function ( eventId, triggers ) { 9681 $.each( triggers, function ( index, trigger ) { 9682 $.each( trigger.forms, function ( i, value ) { 9683 if ( value == form_id ) { 9684 disabled_form_action = trigger.disabled_form_action; 9685 sendEventId = eventId; 9686 form_submit_mode = trigger.form_submit_mode || 'both'; 9687 } 9688 } ) 9689 } ); 9690 } ); 9691 } 9692 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9693 Utils.handleTriggerFired( sendEventId, 'formidable' ); 9694 } 9695 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9696 9697 } ); 9698 9699 // Ninja Forms 9700 $( document ).onFirst( 'nfFormSubmitResponse', function ( event, data ) { 9701 const form_id = data.response.data.form_id; 9702 let sendEventId = null, 9703 disabled_form_action = false, 9704 form_submit_mode = 'both'; 9705 if ( options.triggerEventTypes.hasOwnProperty( 'ninjaform' ) ) { 9706 $.each( options.triggerEventTypes.ninjaform, function ( eventId, triggers ) { 9707 $.each( triggers, function ( index, trigger ) { 9708 $.each( trigger.forms, function ( i, value ) { 9709 if ( value == form_id ) { 9710 disabled_form_action = trigger.disabled_form_action; 9711 sendEventId = eventId; 9712 form_submit_mode = trigger.form_submit_mode || 'both'; 9713 } 9714 } ) 9715 } ); 9716 } ); 9717 } 9718 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9719 Utils.handleTriggerFired( sendEventId, 'ninjaform' ); 9720 } 9721 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9722 } ); 9723 9724 9725 $(document).on('fluentform_submission_success', function ( event, data ) { 9726 if(!data || !data.form){ 9727 return; 9728 } 9729 let $form = data.form; // Submitted form 9730 let config = data.config; // Form configuration 9731 let response = data.response; // Server response 9732 9733 9734 let $formItem = $form; 9735 let form_id = config.id; 9736 let sendEventId = null, 9737 disabled_form_action = false, 9738 form_submit_mode = 'both'; 9739 if ( options.triggerEventTypes.hasOwnProperty( 'fluentform' ) ) { 9740 $.each( options.triggerEventTypes.fluentform, function ( eventId, triggers ) { 9741 $.each( triggers, function ( index, trigger ) { 9742 $.each( trigger.forms, function ( i, value ) { 9743 if ( value == form_id ) { 9744 disabled_form_action = trigger.disabled_form_action; 9745 sendEventId = eventId; 9746 form_submit_mode = trigger.form_submit_mode || 'both'; 9747 } 9748 } ) 9749 } ); 9750 } ); 9751 } 9752 9753 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9754 Utils.handleTriggerFired( sendEventId, 'fluentform' ); 9755 } 9756 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9757 } ); 9758 9759 //WSForm 9760 $( document ).on( 'wsf-submit-complete', ( event, form_object, form_id ) => { 9761 let sendEventId = null, 9762 disabled_form_action = false, 9763 form_submit_mode = 'both'; 9764 if ( options.triggerEventTypes.hasOwnProperty( 'wsform' ) ) { 9765 $.each( options.triggerEventTypes.wsform, function ( eventId, triggers ) { 9766 $.each( triggers, function ( index, trigger ) { 9767 $.each( trigger.forms, function ( i, value ) { 9768 if ( value == form_id ) { 9769 disabled_form_action = trigger.disabled_form_action; 9770 sendEventId = eventId; 9771 form_submit_mode = trigger.form_submit_mode || 'both'; 9772 } 9773 } ) 9774 } ); 9775 } ); 9776 } 9777 9778 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9779 Utils.handleTriggerFired( sendEventId, 'wsform' ); 9780 } 9781 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9782 } ) 9783 9784 // Elementor Forms 9785 let forms = document.querySelectorAll( '.elementor-form' ); 9786 // Apply the observer to each form
9787 forms.forEach( function ( form ) { 9788 observeElementorForm( form ); 9789 } ); 9790 9791 // watching Elementor Popup forms 9792 let observer = new MutationObserver( function ( mutationsList, observer ) { 9793 for ( let i = 0; i < mutationsList.length; i++ ) { 9794 let popupForm = document.querySelector( '[class*="elementor-popup"] form' ); 9795 if ( popupForm ) { 9796 observeElementorForm( popupForm ); 9797 observer.disconnect(); 9798 break; 9799 } 9800 } 9801 } ); 9802 observer.observe( document.body, { childList: true, subtree: true } ); 9803 9804 // load pixel APIs 9805 Utils.loadPixels(); 9806 9807 // setup Enrich content 9808 if(Utils.isCheckoutPage()) { 9809 Utils.addCheckoutFields(); 9810 } 9811 if(options.woo.hasOwnProperty('woo_view_content_variation_is_selected') && options.woo.woo_view_content_variation_is_selected) { 9812 handleWooViewContentVariation() 9813 } 9814 } 9815 9816 $(document).ready(function () { 9817 if ( options.dynamicDataUrl ) { 9818 fetch( options.dynamicDataUrl, { 9819 method: 'GET', 9820 credentials: 'same-origin', 9821 cache: 'no-store', 9822 headers: { 'Accept': 'application/json' } 9823 } ) 9824 .then( function(r) { return r.ok ? r.json() : Promise.reject(r.status); } ) 9825 .then( function(dynamic) { 9826 options.ajax_event = dynamic.ajax_event; 9827 options.user_id = dynamic.user_id; 9828 options.cache_bypass = dynamic.cache_bypass; 9829 options.tracking_analytics = dynamic.tracking_analytics; 9830 if ( dynamic.gdpr_dynamic && options.gdpr ) { 9831 Object.assign( options.gdpr, dynamic.gdpr_dynamic ); 9832 if ( dynamic.gdpr_dynamic.analytics_storage_value !== null ) 9833 options.gdpr.analytics_storage.value = dynamic.gdpr_dynamic.analytics_storage_value; 9834 if ( dynamic.gdpr_dynamic.ad_storage_value !== null ) 9835 options.gdpr.ad_storage.value = dynamic.gdpr_dynamic.ad_storage_value; 9836 if ( dynamic.gdpr_dynamic.ad_user_data_value !== null ) 9837 options.gdpr.ad_user_data.value = dynamic.gdpr_dynamic.ad_user_data_value; 9838 if ( dynamic.gdpr_dynamic.ad_personalization_value !== null ) 9839 options.gdpr.ad_personalization.value = dynamic.gdpr_dynamic.ad_personalization_value; 9840 } 9841 options.cookie = dynamic.cookie; 9842 // Per-pixel user data (advanced matching) that was emptied out of 9843 // the cached HTML. An inline value that still holds something wins: 9844 // on pages we never strip (order received / EDD success) the HTML 9845 // carries the order's own data, which beats the account-level data 9846 // this endpoint can see. 9847 if ( dynamic.pixels ) { 9848 for ( var slug in dynamic.pixels ) { 9849 if ( ! dynamic.pixels.hasOwnProperty( slug ) || ! options[ slug ] ) continue; 9850 for ( var key in dynamic.pixels[ slug ] ) { 9851 if ( ! dynamic.pixels[ slug ].hasOwnProperty( key ) ) continue; 9852 var inline = options[ slug ][ key ]; 9853 if ( ! inline || Object.keys( inline ).length === 0 ) { 9854 options[ slug ][ key ] = dynamic.pixels[ slug ][ key ]; 9855 } 9856 } 9857 } 9858 } 9859 9860 options.dynamicDataApplied = true; 9861 9862 if ( window.pys && window.pys.OpenAI 9863 && typeof window.pys.OpenAI.refreshAdvancedMatching === 'function' ) { 9864 window.pys.OpenAI.refreshAdvancedMatching(); 9865 } 9866 9867 pysNormalInit(); 9868 } ) 9869 .catch( function(err) { 9870 if ( options.debug ) console.warn( '[PYS] dynamic-options fetch failed:', err ); 9871 9872 options.dynamicDataApplied = true; 9873 pysNormalInit(); 9874 } ); 9875 } else { 9876 pysNormalInit(); 9877 } 9878 });
9879 9880 9881 function handleWooViewContentVariation() { 9882 var form = $('form.variations_form'); 9883 9884 form.on('change', 'select', function() { 9885 var selectedAttributes = {}; 9886 form.find('select').each(function() { 9887 var attributeName = $(this).data('attribute_name') || $(this).attr('name'); 9888 var attributeValue = $(this).val(); 9889 if (attributeValue) { 9890 selectedAttributes[attributeName] = attributeValue; 9891 } 9892 }); 9893 9894 $.ajax({ 9895 url: options.ajaxUrl, 9896 type: 'POST', 9897 data: { 9898 action: 'get_variation_info', 9899 product_id: form.data('product_id'), // Pass the main product ID 9900 attributes: selectedAttributes 9901 }, 9902 success: function(response) { 9903 if (response.success) { 9904 $.each(response.data, function(slug) { 9905 $.each(response.data[slug], function(index, event) { 9906 event.isVariationTrigger = true; 9907 getPixelBySlag(slug).fireEvent(event.name, event); 9908 }); 9909 }); 9910 } else { 9911 console.log('Error retrieving data:', response.data); 9912 } 9913 } 9914 }); 9915 }); 9916 } 9917 var sendFormAction = function (form_target, formId, customEventId = null, disabled = false, form_submit_mode = 'both'){ 9918 // Check submit mode - skip automatic event if reload_only 9919 if (disabled == true || form_submit_mode === 'reload_only' ) { 9920 return; 9921 } 9922 9923 var params = { 9924 form_id: formId, 9925 text: form_target.find('[type="submit"]').is('input') ? form_target.find('[type="submit"]').val() : 9926 form_target.find('.forminator-button-submit').text() != '' ? form_target.find('.forminator-button-submit').text() : 9927 form_target.find('[type="submit"]').text() 9928 }; 9929 9930 if (options.dynamicEvents.hasOwnProperty("automatic_event_form")) { 9931 var pixels = Object.keys(options.dynamicEvents.automatic_event_form); 9932 for (var i = 0; i < pixels.length; i++) { 9933 9934 if ( disabled && customEventId && typeof options.triggerEvents[customEventId][pixels[i]] !== 'undefined') { 9935 continue; 9936 } 9937 var event = options.dynamicEvents.automatic_event_form[pixels[i]]; 9938 if (pixels[i] === "tiktok" || pixels[i] === "reddit") { 9939 getPixelBySlag(pixels[i]).fireEvent(event.name, event); 9940 } else { 9941 Utils.copyProperties(params, event.params)
9942 Utils.copyProperties(Utils.getRequestParams(), event.params); 9943 getPixelBySlag(pixels[i]).onFormEvent(event); 9944 } 9945 } 9946 } 9947 } 9948 9949 var setupEmailLinks = function ( disabled_for_pixels = [] ) { 9950 if ( options.dynamicEvents.hasOwnProperty( "automatic_event_email_link" ) ) { 9951 var pixels = Object.keys( options.dynamicEvents.automatic_event_email_link ); 9952 for ( var i = 0; i < pixels.length; i++ ) { 9953 if ( disabled_for_pixels.indexOf(pixels[ i ]) === -1 ) { 9954 var event = Utils.clone( options.dynamicEvents.automatic_event_email_link[ pixels[ i ] ] ); 9955 if ( pixels[ i ] !== 'tiktok' && pixels[ i ] !== 'reddit' ) { 9956 Utils.copyProperties( Utils.getRequestParams(), event.params ); 9957 } 9958 getPixelBySlag( pixels[ i ] ).fireEvent( event.name, event ); 9959 } 9960 } 9961 } 9962 } 9963 9964 //Elementor Forms Observer 9965 var observeElementorForm = function ( form ) { 9966 let observer = new MutationObserver( function ( mutations ) { 9967 9968 for ( let i = 0; i < mutations.length; i++ ) { 9969 let mutation = mutations[ i ]; 9970 9971 if ( mutation.type === 'childList' || mutation.type === 'attributes' ) { 9972 if ( $( form ).find( '.elementor-message-success' ).length > 0 ) { 9973 9974 let form_id = $( form ).attr( 'id' ); 9975 if ( !form_id ) { 9976 form_id = $( form ).find( 'input[name="form_id"]' ).val(); 9977 } 9978 9979 let sendEventId = null, 9980 disabled_form_action = false, 9981 form_submit_mode = 'both'; 9982 if ( options.triggerEventTypes.hasOwnProperty( 'elementor_form' ) ) { 9983 $.each( options.triggerEventTypes.elementor_form, function ( eventId, triggers ) { 9984 $.each( triggers, function ( index, trigger ) { 9985 $.each( trigger.forms, function ( i, value ) { 9986 if ( value == form_id ) { 9987 disabled_form_action = trigger.disabled_form_action; 9988 sendEventId = eventId; 9989 form_submit_mode = trigger.form_submit_mode || 'both'; 9990 } 9991 }) 9992 } ); 9993 } ); 9994 } 9995 if ( sendEventId != null && ( form_submit_mode === 'both' || form_submit_mode === 'ajax_only' ) ) { 9996 Utils.handleTriggerFired( sendEventId, 'elementor_form' ); 9997 } 9998 sendFormAction( $( event.target ), form_id, sendEventId, disabled_form_action, form_submit_mode ); 9999 10000 observer.disconnect(); 10001 break; 10002 } 10003 } 10004 } 10005 } ); 10006 10007 // Start observing the form for changes 10008 observer.observe( form, { 10009 attributes: true, 10010 childList: true, 10011 subtree: true 10012 } ); 10013 } 10014 10015}(jQuery, pysOptions); 10016 10017 10018function pys_generate_token() { 10019 return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => 10020 (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 10021 ); 10022} 10023function getBundlePriceOnSingleProduct(data) { 10024 var items_sum = 0; 10025 jQuery(".bundle_form .bundled_product").each(function(index){ 10026 var id = jQuery(this).find(".cart").data("bundled_item_id"); 10027 var item_price = data.prices[id]; 10028 var item_quantity = jQuery(this).find(".bundled_qty").val(); 10029 if(!jQuery(this).hasClass("bundled_item_optional") || 10030 jQuery(this).find(".bundled_product_optional_checkbox input").prop('checked')) { 10031 items_sum += item_price*item_quantity; 10032 } 10033 });
10034 return items_sum; 10035} 10036function getUrlParameter(sParam) { 10037 var sPageURL = window.location.search.substring(1), 10038 sURLVariables = sPageURL.split('&'), 10039 sParameterName, 10040 i; 10041 10042 for (i = 0; i < sURLVariables.length; i++) { 10043 sParameterName = sURLVariables[i].split('='); 10044 10045 if (sParameterName[0] === sParam) { 10046 // Twin of ServerEventHelper::getUrlParameter(). A parameter with 10047 // no '=' has no value; skip it rather than inventing one, so 10048 // ?fbclid&fbclid=REAL still finds the real one. 10049 if (sParameterName[1] === undefined) { 10050 continue; 10051 } 10052 try { 10053 return decodeURIComponent(sParameterName[1]); 10054 } catch (e) { 10055 // decodeURIComponent throws on a malformed escape ("%", 10056 // "%zz", "a%2"). Return the raw value instead of throwing, 10057 // matching PHP's urldecode(), which is lenient about it. 10058 return sParameterName[1]; 10059 } 10060 } 10061 } 10062 return false; 10063}; 10064function inArray(needle, haystack) { 10065 var length = haystack.length; 10066 for(var i = 0; i < length; i++) { 10067 if(haystack[i] == needle) return true; 10068 } 10069 return false; 10070} 10071 10072function getCookieYes(key) { 10073 const cookiesObj = document.cookie 10074 .split(";") 10075 .reduce((ac, cv) => { 10076 const [k, v] = cv.split("="); 10077 if (k && v) ac[k.trim()] = v; 10078 return ac; 10079 }, {}); 10080 const consentCookie = cookiesObj["cookieyes-consent"] || cookiesObj["wt_consent"]; 10081 if (!consentCookie) return undefined; 10082 const { [key]: value } = consentCookie 10083 .split(",") 10084 .reduce((obj, pair) => { 10085 const [k, v] = pair.split(":"); 10086 if (k && v) obj[k] = v; 10087 return obj; 10088 }, {}); 10089 return value; 10090} 10091 10092function getRootDomain(useSubdomain = false) { 10093 const hostname = window.location.hostname; // Get the current hostname 10094 // Check if tldjs is defined before using it 10095 if (typeof tldjs === "undefined") { 10096 console.warn("tldjs is not defined"); 10097 return hostname; // Return hostname as a fallback 10098 } 10099 10100 const rootDomain = tldjs.getDomain(hostname); // Use tldjs to extract the root domain 10101 10102 // Return the root domain with or without a leading dot based on useSubdomain 10103 return rootDomain ? (useSubdomain ? '.' + rootDomain : rootDomain) : hostname; 10104}
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.