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