PageSourceSearch

https://loja.santinhoz.com.br/wp-content/plugins/pixelyoursite-pro/dist/scripts/public.js?ver=12.6.7.1

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

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.