PageSourceSearch

https://kitboxclub.com.br/wp-content/plugins/pixelyoursite-pro/dist/scripts/public.js?ver=12.6.7

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

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