PageSourceSearch

https://mlodziezowy.pl/wp-content/plugins/pixelyoursite-pro/dist/scripts/public.js?ver=12.6.6

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.