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