PageSourceSearch

https://mutargy.com/wp-content/plugins/pixelyoursite-pro/dist/scripts/public.js?ver=12.6.4

js mutargy.com collected 2026-09-24 10:06:37 UTC 391,582 bytes, 8,598 lines download raw bytes

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

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.